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

Lines changed: 0 additions & 2 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 3 additions & 8 deletions
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

Lines changed: 2 additions & 2 deletions
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;
Lines changed: 30 additions & 0 deletions
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

Lines changed: 7 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 7 additions & 3 deletions
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

Lines changed: 12 additions & 2 deletions
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

Lines changed: 4 additions & 2 deletions
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);

0 commit comments

Comments
 (0)