Skip to content

Commit b7647e5

Browse files
committed
CSHARP-4427: Negative array indexes should throw ExpressionNotSupportedException.
1 parent baffee0 commit b7647e5

File tree

5 files changed

+56
-63
lines changed

5 files changed

+56
-63
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/Misc/ArraySerializerHelper.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
using System;
17+
using System.Linq.Expressions;
1718
using MongoDB.Bson.Serialization;
1819

1920
namespace MongoDB.Driver.Linq.Linq3Implementation.Misc
@@ -38,5 +39,24 @@ public static IBsonSerializer GetItemSerializer(IBsonSerializer serializer)
3839
throw new InvalidOperationException($"{serializer.GetType().FullName} must implement IBsonArraySerializer to be used with LINQ.");
3940
}
4041
}
42+
43+
public static IBsonSerializer GetItemSerializer(Expression expression, IBsonSerializer serializer)
44+
{
45+
if (serializer is IBsonArraySerializer arraySerializer)
46+
{
47+
if (arraySerializer.TryGetItemSerializationInfo(out var itemSerializationInfo))
48+
{
49+
return itemSerializationInfo.Serializer;
50+
}
51+
else
52+
{
53+
throw new ExpressionNotSupportedException(expression, because: $"{serializer.GetType().FullName}.TryGetItemSerializationInfo returned false");
54+
}
55+
}
56+
else
57+
{
58+
throw new ExpressionNotSupportedException(expression, because: $"{serializer.GetType().FullName} must implement IBsonArraySerializer to be used with LINQ");
59+
}
60+
}
4161
}
4262
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/ArrayIndexExpressionToFilterFieldTranslator.cs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
using System.Linq.Expressions;
17+
using MongoDB.Bson.Serialization;
1718
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
1819
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
1920
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
@@ -27,25 +28,31 @@ public static AstFilterField Translate(TranslationContext context, BinaryExpress
2728
if (expression.NodeType == ExpressionType.ArrayIndex)
2829
{
2930
var arrayExpression = expression.Left;
30-
var arrayField = ExpressionToFilterFieldTranslator.Translate(context, arrayExpression);
3131
var indexExpression = expression.Right;
32-
var index = indexExpression.GetConstantValue<int>(containingExpression: expression);
3332

34-
if (index < 0)
33+
return Translate(context, expression, fieldExpression: arrayExpression, indexExpression);
34+
}
35+
36+
throw new ExpressionNotSupportedException(expression);
37+
}
38+
39+
public static AstFilterField Translate(TranslationContext context, Expression expression, Expression fieldExpression, Expression indexExpression)
40+
{
41+
var field = ExpressionToFilterFieldTranslator.TranslateEnumerable(context, fieldExpression);
42+
var index = indexExpression.GetConstantValue<int>(containingExpression: expression);
43+
var itemSerializer = ArraySerializerHelper.GetItemSerializer(fieldExpression, field.Serializer);
44+
45+
if (index < 0)
46+
{
47+
var reason = "negative indexes are not valid";
48+
if (index == -1)
3549
{
36-
var reason = "negative indexes are not valid";
37-
if (index == -1)
38-
{
39-
reason += ". To use the positional operator $ use FirstMatchingElement instead of an index value of -1"; // closing period is added by exception
40-
}
41-
throw new ExpressionNotSupportedException(expression, because: reason);
50+
reason += ". To use the positional operator $ use FirstMatchingElement instead of an index value of -1"; // closing period is added by exception
4251
}
43-
44-
var itemSerializer = ArraySerializerHelper.GetItemSerializer(arrayField.Serializer);
45-
return arrayField.SubField(index.ToString(), itemSerializer);
52+
throw new ExpressionNotSupportedException(expression, because: reason);
4653
}
4754

48-
throw new ExpressionNotSupportedException(expression);
55+
return field.SubField(index.ToString(), itemSerializer);
4956
}
5057
}
5158
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/ElementAtMethodToFilterFieldTranslator.cs

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@
1414
*/
1515

1616
using System.Linq.Expressions;
17-
using MongoDB.Bson.Serialization;
1817
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
19-
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
2018
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
2119
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
2220

@@ -32,27 +30,9 @@ public static AstFilterField Translate(TranslationContext context, MethodCallExp
3230
if (method.Is(EnumerableMethod.ElementAt))
3331
{
3432
var sourceExpression = arguments[0];
35-
var field = ExpressionToFilterFieldTranslator.Translate(context, sourceExpression);
36-
3733
var indexExpression = arguments[1];
38-
var index = indexExpression.GetConstantValue<int>(containingExpression: expression);
39-
40-
if (index < 0)
41-
{
42-
var reason = "negative indexes are not valid";
43-
if (index == -1)
44-
{
45-
reason += ". To use the positional operator $ use FirstMatchingElement instead of an index value of -1"; // closing period is added by exception
46-
}
47-
throw new ExpressionNotSupportedException(expression, because: reason);
48-
}
4934

50-
if (field.Serializer is IBsonArraySerializer arraySerializer &&
51-
arraySerializer.TryGetItemSerializationInfo(out var itemSerializationInfo))
52-
{
53-
var itemSerializer = itemSerializationInfo.Serializer;
54-
return field.SubField(index.ToString(), itemSerializer);
55-
}
35+
return ArrayIndexExpressionToFilterFieldTranslator.Translate(context, expression, fieldExpression: sourceExpression, indexExpression);
5636
}
5737

5838
throw new ExpressionNotSupportedException(expression);

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/GetItemMethodToFilterFieldTranslator.cs

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
using MongoDB.Bson.Serialization.Serializers;
2222
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
2323
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
24-
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
2524

2625
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ToFilterFieldTranslators
2726
{
@@ -42,40 +41,22 @@ public static AstFilterField Translate(TranslationContext context, MethodCallExp
4241

4342
if (indexExpression.Type == typeof(int))
4443
{
45-
var index = indexExpression.GetConstantValue<int>(containingExpression: expression);
46-
return TranslateWithIntIndex(context, expression, method, fieldExpression, index);
44+
return ArrayIndexExpressionToFilterFieldTranslator.Translate(context, expression, fieldExpression, indexExpression);
4745
}
4846

4947
if (indexExpression.Type == typeof(string))
5048
{
51-
var key = indexExpression.GetConstantValue<string>(containingExpression: expression);
52-
return TranslateWithStringIndex(context, expression, method, fieldExpression, key);
49+
return TranslateWithStringIndex(context, expression, method, fieldExpression, indexExpression);
5350
}
5451
}
5552

5653
throw new ExpressionNotSupportedException(expression);
5754
}
5855

59-
private static AstFilterField TranslateWithIntIndex(TranslationContext context, MethodCallExpression expression, MethodInfo method, Expression fieldExpression, int index)
60-
{
61-
var field = ExpressionToFilterFieldTranslator.TranslateEnumerable(context, fieldExpression);
62-
63-
if (field.Serializer is IBsonArraySerializer arraySerializer &&
64-
arraySerializer.TryGetItemSerializationInfo(out var itemSerializationInfo))
65-
{
66-
var itemSerializer = itemSerializationInfo.Serializer;
67-
if (method.ReturnType.IsAssignableFrom(itemSerializer.ValueType))
68-
{
69-
return field.SubField(index.ToString(), itemSerializer);
70-
}
71-
}
72-
73-
throw new ExpressionNotSupportedException(expression);
74-
}
75-
76-
private static AstFilterField TranslateWithStringIndex(TranslationContext context, MethodCallExpression expression, MethodInfo method, Expression fieldExpression, string key)
56+
private static AstFilterField TranslateWithStringIndex(TranslationContext context, MethodCallExpression expression, MethodInfo method, Expression fieldExpression, Expression indexExpression)
7757
{
7858
var field = ExpressionToFilterFieldTranslator.Translate(context, fieldExpression);
59+
var key = indexExpression.GetConstantValue<string>(containingExpression: expression);
7960

8061
if (typeof(BsonValue).IsAssignableFrom(field.Serializer.ValueType))
8162
{

tests/MongoDB.Driver.Tests/UpdateDefinitionBuilderTests.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -244,13 +244,13 @@ public void Inc_Typed()
244244
public void Incorrect_index_should_throw_expected_exception_with_set()
245245
{
246246
var subject = CreateSubject<Person>();
247-
string expectedErrorMessage = "Array indexes must be greater than or equal to -1.";
247+
string expectedErrorMessage = "because negative indexes are not valid";
248248

249249
#pragma warning disable 251
250-
AssertThrow<Person, IndexOutOfRangeException>(subject.Set(x => x.FavoriteColors[-2], "yellow"), expectedErrorMessage);
250+
AssertThrow<Person, ExpressionNotSupportedException>(subject.Set(x => x.FavoriteColors[-2], "yellow"), expectedErrorMessage, LinqProvider.V3);
251251
#pragma warning restore
252-
AssertThrow<Person, IndexOutOfRangeException>(subject.Set(x => x.Pets[-2].Name, "Fluffencutters"), expectedErrorMessage);
253-
AssertThrow<Person, IndexOutOfRangeException>(subject.Set(x => x.Pets.ElementAt(-2).Name, "Fluffencutters"), expectedErrorMessage);
252+
AssertThrow<Person, ExpressionNotSupportedException>(subject.Set(x => x.Pets[-2].Name, "Fluffencutters"), expectedErrorMessage, LinqProvider.V3);
253+
AssertThrow<Person, ExpressionNotSupportedException>(subject.Set(x => x.Pets.ElementAt(-2).Name, "Fluffencutters"), expectedErrorMessage, LinqProvider.V3);
254254
}
255255

256256
[Fact]
@@ -681,9 +681,14 @@ private void Assert<TDocument>(UpdateDefinition<TDocument> update, string expect
681681

682682
private void AssertThrow<TDocument, TException>(UpdateDefinition<TDocument> update, string errorMessage) where TException : Exception
683683
{
684-
var exception = Record.Exception(() => { Render(update); });
684+
AssertThrow<TDocument, TException>(update, errorMessage, LinqProvider.V2);
685+
}
686+
687+
private void AssertThrow<TDocument, TException>(UpdateDefinition<TDocument> update, string errorMessage, LinqProvider linqProvider) where TException : Exception
688+
{
689+
var exception = Record.Exception(() => { Render(update, linqProvider); });
685690
exception.Should().BeOfType<TException>();
686-
exception.Message.Should().Be(errorMessage);
691+
exception.Message.Should().Contain(errorMessage);
687692
}
688693

689694
private UpdateDefinitionBuilder<TDocument> CreateSubject<TDocument>()

0 commit comments

Comments
 (0)