Skip to content

Commit 1934a14

Browse files
authored
Add option to ParsingConfig to allow the Equals and ToString methods on object (#875)
* Add option to ParsingConfig to allow the Equals and ToString methods on object * . * --- * ReferenceEquals * .
1 parent fca802e commit 1934a14

File tree

12 files changed

+158
-35
lines changed

12 files changed

+158
-35
lines changed

src-blazor/BlazorAppServer/BlazorAppServer.csproj

-2
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
1818
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
1919
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
20-
<PackageReference Include="Oqtane.Shared" Version="3.1.4" />
2120
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
22-
<!--<PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.19" />-->
2321
</ItemGroup>
2422

2523
<ItemGroup>

src/System.Linq.Dynamic.Core/Compatibility/TypeExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#if NETSTANDARD1_3
1+
#if NETSTANDARD1_3 || UAP10_0
22
using System.Linq;
33

44
namespace System.Reflection;

src/System.Linq.Dynamic.Core/DynamicClassFactory.cs

+3-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
#if !(UAP10_0)
1+
#if !UAP10_0
22
using System.Collections;
33
using System.Collections.Concurrent;
44
using System.Collections.Generic;
55
using System.Diagnostics;
6+
using System.Linq.Dynamic.Core.Parser;
67
using System.Linq.Dynamic.Core.Validation;
78
using System.Reflection;
89
using System.Reflection.Emit;
@@ -27,12 +28,6 @@ public static class DynamicClassFactory
2728

2829
private static readonly ConstructorInfo ObjectCtor = typeof(object).GetConstructor(Type.EmptyTypes)!;
2930

30-
#if UAP10_0 || NETSTANDARD
31-
private static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public)!;
32-
#else
33-
private static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null)!;
34-
#endif
35-
3631
private static readonly ConstructorInfo StringBuilderCtor = typeof(StringBuilder).GetConstructor(Type.EmptyTypes)!;
3732
#if UAP10_0 || NETSTANDARD
3833
private static readonly MethodInfo StringBuilderAppendString = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), [typeof(string)])!;
@@ -419,7 +414,7 @@ private static Type EmitType(IList<DynamicProperty> properties, bool createParam
419414
ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendString);
420415
ilgeneratorToString.Emit(OpCodes.Pop);
421416
ilgeneratorToString.Emit(OpCodes.Ldloc_0);
422-
ilgeneratorToString.Emit(OpCodes.Callvirt, ObjectToString);
417+
ilgeneratorToString.Emit(OpCodes.Callvirt, PredefinedMethodsHelper.ObjectToString);
423418
ilgeneratorToString.Emit(OpCodes.Ret);
424419

425420
EmitEqualityOperators(typeBuilder, equals);

src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1840,9 +1840,9 @@ private Expression ParseMemberAccess(Type? type, Expression? expression, string?
18401840

18411841
case 1:
18421842
var method = (MethodInfo)methodBase!;
1843-
if (!PredefinedTypesHelper.IsPredefinedType(_parsingConfig, method.DeclaringType!))
1843+
if (!PredefinedTypesHelper.IsPredefinedType(_parsingConfig, method.DeclaringType!) && !PredefinedMethodsHelper.IsPredefinedMethod(_parsingConfig, method))
18441844
{
1845-
throw ParseError(errorPos, Res.MethodsAreInaccessible, TypeHelper.GetTypeName(method.DeclaringType!));
1845+
throw ParseError(errorPos, Res.MethodIsInaccessible, id, TypeHelper.GetTypeName(method.DeclaringType!));
18461846
}
18471847

18481848
MethodInfo methodToCall;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System.Collections.Generic;
2+
using System.Linq.Dynamic.Core.Validation;
3+
using System.Reflection;
4+
5+
namespace System.Linq.Dynamic.Core.Parser;
6+
7+
internal static class PredefinedMethodsHelper
8+
{
9+
internal static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null)!;
10+
internal static readonly MethodInfo ObjectInstanceEquals = typeof(object).GetMethod(nameof(Equals), BindingFlags.Instance | BindingFlags.Public, null, [typeof(object)], null)!;
11+
internal static readonly MethodInfo ObjectStaticEquals = typeof(object).GetMethod(nameof(Equals), BindingFlags.Static | BindingFlags.Public, null, [typeof(object), typeof(object)], null)!;
12+
internal static readonly MethodInfo ObjectStaticReferenceEquals = typeof(object).GetMethod(nameof(ReferenceEquals), BindingFlags.Static | BindingFlags.Public, null, [typeof(object), typeof(object)], null)!;
13+
14+
15+
private static readonly HashSet<MemberInfo> ObjectToStringAndObjectEquals =
16+
[
17+
ObjectToString,
18+
ObjectInstanceEquals,
19+
ObjectStaticEquals,
20+
ObjectStaticReferenceEquals
21+
];
22+
23+
public static bool IsPredefinedMethod(ParsingConfig config, MemberInfo member)
24+
{
25+
Check.NotNull(config);
26+
Check.NotNull(member);
27+
28+
return config.AllowEqualsAndToStringMethodsOnObject && ObjectToStringAndObjectEquals.Contains(member);
29+
}
30+
}

src/System.Linq.Dynamic.Core/ParsingConfig.cs

+7
Original file line numberDiff line numberDiff line change
@@ -312,4 +312,11 @@ public IQueryableAnalyzer QueryableAnalyzer
312312
/// Default value is <c>false</c>.
313313
/// </summary>
314314
public bool RestrictOrderByToPropertyOrField { get; set; }
315+
316+
/// <summary>
317+
/// When set to <c>true</c>, the parser will allow the use of the Equals(object obj), Equals(object objA, object objB), ReferenceEquals(object objA, object objB) and ToString() methods on the <see cref="object"/> type.
318+
///
319+
/// Default value is <c>false</c>.
320+
/// </summary>
321+
public bool AllowEqualsAndToStringMethodsOnObject { get; set; }
315322
}

src/System.Linq.Dynamic.Core/Res.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ internal static class Res
5050
public const string InvalidStringLength = "String '{0}' should have at least {1} characters.";
5151
public const string IsNullRequiresTwoArgs = "The 'isnull' function requires two arguments";
5252
public const string MethodIsVoid = "Method '{0}' in type '{1}' does not return a value";
53-
public const string MethodsAreInaccessible = "Methods on type '{0}' are not accessible";
53+
public const string MethodIsInaccessible = "Method '{0}' on type '{1}' is not accessible.";
5454
public const string MinusCannotBeAppliedToUnsignedInteger = "'-' cannot be applied to unsigned integers.";
5555
public const string MissingAsClause = "Expression is missing an 'as' clause";
5656
public const string NeitherTypeConvertsToOther = "Neither of the types '{0}' and '{1}' converts to the other";

test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs

+7-3
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,15 @@ public void DynamicClassArray_Issue593_Fails()
281281
isValid.Should().BeFalse(); // This should actually be true, but fails. For solution see Issue593_Solution1 and Issue593_Solution2.
282282
}
283283

284-
// [SkipIfGitHubActions]
285-
[Fact(Skip = "867")]
284+
[SkipIfGitHubActions]
286285
public void DynamicClassArray_Issue593_Solution1()
287286
{
288287
// Arrange
288+
var config = new ParsingConfig
289+
{
290+
AllowEqualsAndToStringMethodsOnObject = true
291+
};
292+
289293
var field = new
290294
{
291295
Name = "firstName",
@@ -308,7 +312,7 @@ public void DynamicClassArray_Issue593_Solution1()
308312
var query = dynamicClasses.AsQueryable();
309313

310314
// Act
311-
var isValid = query.Any("firstName.ToString() eq \"firstValue\"");
315+
var isValid = query.Any(config, "firstName.ToString() eq \"firstValue\"");
312316

313317
// Assert
314318
isValid.Should().BeTrue();

test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs

+12-2
Original file line numberDiff line numberDiff line change
@@ -1058,13 +1058,23 @@ public void DynamicExpressionParser_ParseLambda_StringLiteral_QuotationMark()
10581058
Assert.Equal(expectedRightValue, rightValue);
10591059
}
10601060

1061-
[Fact(Skip = "867")]
1061+
[Fact]
10621062
public void DynamicExpressionParser_ParseLambda_TupleToStringMethodCall_ReturnsStringLambdaExpression()
10631063
{
1064+
// Arrange
1065+
var config = new ParsingConfig
1066+
{
1067+
AllowEqualsAndToStringMethodsOnObject = true
1068+
};
1069+
1070+
// Act
10641071
var expression = DynamicExpressionParser.ParseLambda(
1072+
config,
10651073
typeof(Tuple<int>),
10661074
typeof(string),
10671075
"it.ToString()");
1076+
1077+
// Assert
10681078
Assert.Equal(typeof(string), expression.ReturnType);
10691079
}
10701080

@@ -1147,7 +1157,7 @@ public void DynamicExpressionParser_ParseLambda_CustomMethod_WhenClassDoesNotHav
11471157
Action action = () => DynamicExpressionParser.ParseLambda(typeof(CustomClassWithMethod), null, expression);
11481158

11491159
// Assert
1150-
action.Should().Throw<ParseException>().WithMessage("Methods on type 'CustomClassWithMethod' are not accessible");
1160+
action.Should().Throw<ParseException>().WithMessage("Method 'GetAge' on type 'CustomClassWithMethod' is not accessible.");
11511161
}
11521162

11531163
// [Fact]

test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,8 @@ public void Parse_NullableShouldReturnNullable(string expression, object resultT
346346
[Theory]
347347
[InlineData("it.MainCompany.Name != null", "(company.MainCompany.Name != null)")]
348348
[InlineData("@MainCompany.Companies.Count() > 0", "(company.MainCompany.Companies.Count() > 0)")]
349-
// [InlineData("Company.Equals(null, null)", "Equals(null, null)")] issue 867
349+
[InlineData("Company.Equals(null, null)", "Equals(null, null)")]
350+
[InlineData("Equals(null)", "company.Equals(null)")]
350351
[InlineData("MainCompany.Name", "company.MainCompany.Name")]
351352
[InlineData("Name", "company.Name")]
352353
[InlineData("company.Name", "company.Name")]
@@ -357,7 +358,8 @@ public void Parse_When_PrioritizePropertyOrFieldOverTheType_IsTrue(string expres
357358
var config = new ParsingConfig
358359
{
359360
IsCaseSensitive = true,
360-
CustomTypeProvider = _dynamicTypeProviderMock.Object
361+
CustomTypeProvider = _dynamicTypeProviderMock.Object,
362+
AllowEqualsAndToStringMethodsOnObject = true
361363
};
362364
ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "company") };
363365
var sut = new ExpressionParser(parameters, expression, null, config);

test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs

+84-7
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,93 @@ namespace System.Linq.Dynamic.Core.Tests.Parser;
77

88
public class MethodFinderTest
99
{
10-
[Fact(Skip = "867")]
11-
public void MethodsOfDynamicLinqAndSystemLinqShouldBeEqual()
10+
[Fact]
11+
public void Method_ToString_OnDynamicLinq_And_SystemLinq_ShouldBeEqual()
1212
{
13+
// Arrange
14+
var config = new ParsingConfig
15+
{
16+
AllowEqualsAndToStringMethodsOnObject = true
17+
};
18+
1319
Expression<Func<int?, string?>> expr = x => x.ToString();
14-
20+
1521
var selector = "ToString()";
1622
var prm = Parameter(typeof(int?));
17-
var parser = new ExpressionParser([prm], selector, [], ParsingConfig.Default);
18-
var expr1 = parser.Parse(null);
19-
20-
Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expr1).Method.DeclaringType);
23+
var parser = new ExpressionParser([prm], selector, [], config);
24+
25+
// Act
26+
var expression = parser.Parse(null);
27+
28+
// Assert
29+
Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expression).Method.DeclaringType);
30+
}
31+
32+
[Fact]
33+
public void Method_Equals1_OnDynamicLinq_And_SystemLinq_ShouldBeEqual()
34+
{
35+
// Arrange
36+
var config = new ParsingConfig
37+
{
38+
AllowEqualsAndToStringMethodsOnObject = true
39+
};
40+
41+
Expression<Func<int?, bool>> expr = x => x.Equals("a");
42+
43+
var selector = "Equals(\"a\")";
44+
var prm = Parameter(typeof(int?));
45+
var parser = new ExpressionParser([prm], selector, [], config);
46+
47+
// Act
48+
var expression = parser.Parse(null);
49+
50+
// Assert
51+
Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expression).Method.DeclaringType);
52+
}
53+
54+
[Fact]
55+
public void Method_Equals2_OnDynamicLinq_And_SystemLinq_ShouldBeEqual()
56+
{
57+
// Arrange
58+
var config = new ParsingConfig
59+
{
60+
AllowEqualsAndToStringMethodsOnObject = true
61+
};
62+
63+
// ReSharper disable once RedundantNameQualifier
64+
Expression<Func<int?, bool>> expr = x => object.Equals("a", "b");
65+
66+
var selector = "object.Equals(\"a\", \"b\")";
67+
var prm = Parameter(typeof(int?));
68+
var parser = new ExpressionParser([prm], selector, [], config);
69+
70+
// Act
71+
var expression = parser.Parse(null);
72+
73+
// Assert
74+
Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expression).Method.DeclaringType);
75+
}
76+
77+
[Fact]
78+
public void Method_ReferenceEquals_OnDynamicLinq_And_SystemLinq_ShouldBeEqual()
79+
{
80+
// Arrange
81+
var config = new ParsingConfig
82+
{
83+
AllowEqualsAndToStringMethodsOnObject = true
84+
};
85+
86+
// ReSharper disable once RedundantNameQualifier
87+
Expression<Func<int?, bool>> expr = x => object.ReferenceEquals("a", "b");
88+
89+
var selector = "object.ReferenceEquals(\"a\", \"b\")";
90+
var prm = Parameter(typeof(int?));
91+
var parser = new ExpressionParser([prm], selector, [], config);
92+
93+
// Act
94+
var expression = parser.Parse(null);
95+
96+
// Assert
97+
Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expression).Method.DeclaringType);
2198
}
2299
}

test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public void MethodsShouldOnlyBeCallableOnPredefinedTypes_Test1()
3131
Action action = () => baseQuery.OrderBy(predicate);
3232

3333
// Assert
34-
action.Should().Throw<ParseException>().WithMessage("Methods on type 'Object' are not accessible");
34+
action.Should().Throw<ParseException>().WithMessage("Method 'GetType' on type 'Object' is not accessible.");
3535
}
3636

3737
[Fact]
@@ -48,19 +48,19 @@ public void MethodsShouldOnlyBeCallableOnPredefinedTypes_Test2()
4848
);
4949

5050
// Assert
51-
action.Should().Throw<ParseException>().WithMessage($"Methods on type 'Object' are not accessible");
51+
action.Should().Throw<ParseException>().WithMessage("Method 'GetType' on type 'Object' is not accessible.");
5252
}
5353

5454
[Theory]
55-
[InlineData(typeof(FileStream), "Close()", "Stream")]
56-
[InlineData(typeof(Assembly), "GetName().Name.ToString()", "Assembly")]
57-
public void DynamicExpressionParser_ParseLambda_IllegalMethodCall_ThrowsException(Type itType, string expression, string type)
55+
[InlineData(typeof(FileStream), "Close()", "Method 'Close' on type 'Stream' is not accessible.")]
56+
[InlineData(typeof(Assembly), "GetName().Name.ToString()", "Method 'GetName' on type 'Assembly' is not accessible.")]
57+
public void DynamicExpressionParser_ParseLambda_IllegalMethodCall_ThrowsException(Type itType, string expression, string errorMessage)
5858
{
5959
// Act
6060
Action action = () => DynamicExpressionParser.ParseLambda(itType, null, expression);
6161

6262
// Assert
63-
action.Should().Throw<ParseException>().WithMessage($"Methods on type '{type}' are not accessible");
63+
action.Should().Throw<ParseException>().WithMessage(errorMessage);
6464
}
6565

6666
[Theory]
@@ -79,7 +79,7 @@ public void UsingSystemReflectionAssembly_ThrowsException(string selector)
7979
Action action = () => queryable.Select(selector);
8080

8181
// Assert
82-
action.Should().Throw<ParseException>().WithMessage("Methods on type 'Object' are not accessible");
82+
action.Should().Throw<ParseException>().WithMessage("Method 'GetType' on type 'Object' is not accessible.");
8383
}
8484

8585
[Theory]

0 commit comments

Comments
 (0)