Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src-blazor/BlazorAppServer/BlazorAppServer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="Oqtane.Shared" Version="3.1.4" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" />
<!--<PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.19" />-->
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#if NETSTANDARD1_3
#if NETSTANDARD1_3 || UAP10_0
using System.Linq;

namespace System.Reflection;
Expand Down
11 changes: 3 additions & 8 deletions src/System.Linq.Dynamic.Core/DynamicClassFactory.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#if !(UAP10_0)
#if !UAP10_0
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq.Dynamic.Core.Parser;
using System.Linq.Dynamic.Core.Validation;
using System.Reflection;
using System.Reflection.Emit;
Expand All @@ -27,12 +28,6 @@

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

#if UAP10_0 || NETSTANDARD
private static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public)!;
#else
private static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null)!;
#endif

private static readonly ConstructorInfo StringBuilderCtor = typeof(StringBuilder).GetConstructor(Type.EmptyTypes)!;
#if UAP10_0 || NETSTANDARD
private static readonly MethodInfo StringBuilderAppendString = typeof(StringBuilder).GetMethod(nameof(StringBuilder.Append), [typeof(string)])!;
Expand Down Expand Up @@ -310,7 +305,7 @@
ilgeneratorEquals.Emit(OpCodes.Ldfld, fieldBuilders[i]);
ilgeneratorEquals.Emit(OpCodes.Callvirt, equalityComparerTEquals);

// GetHashCode();

Check warning on line 308 in src/System.Linq.Dynamic.Core/DynamicClassFactory.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Remove this commented out code. (https://rules.sonarsource.com/csharp/RSPEC-125)
MethodInfo equalityComparerTGetHashCode = equalityComparerT.GetMethod(nameof(EqualityComparer.GetHashCode), BindingFlags.Instance | BindingFlags.Public, null, [fieldType], null)!;
ilgeneratorGetHashCode.Emit(OpCodes.Stloc_0);
ilgeneratorGetHashCode.Emit(OpCodes.Ldc_I4, -1521134295);
Expand Down Expand Up @@ -419,7 +414,7 @@
ilgeneratorToString.Emit(OpCodes.Callvirt, StringBuilderAppendString);
ilgeneratorToString.Emit(OpCodes.Pop);
ilgeneratorToString.Emit(OpCodes.Ldloc_0);
ilgeneratorToString.Emit(OpCodes.Callvirt, ObjectToString);
ilgeneratorToString.Emit(OpCodes.Callvirt, PredefinedMethodsHelper.ObjectToString);
ilgeneratorToString.Emit(OpCodes.Ret);

EmitEqualityOperators(typeBuilder, equals);
Expand Down
4 changes: 2 additions & 2 deletions src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1840,9 +1840,9 @@

case 1:
var method = (MethodInfo)methodBase!;
if (!PredefinedTypesHelper.IsPredefinedType(_parsingConfig, method.DeclaringType!))
if (!PredefinedTypesHelper.IsPredefinedType(_parsingConfig, method.DeclaringType!) && !PredefinedMethodsHelper.IsPredefinedMethod(_parsingConfig, method))
{
throw ParseError(errorPos, Res.MethodsAreInaccessible, TypeHelper.GetTypeName(method.DeclaringType!));
throw ParseError(errorPos, Res.MethodIsInaccessible, id, TypeHelper.GetTypeName(method.DeclaringType!));
}

MethodInfo methodToCall;
Expand Down Expand Up @@ -1920,8 +1920,8 @@
switch (member)
{
case PropertyInfo property:
var propertyIsStatic = property?.GetGetMethod().IsStatic ?? property?.GetSetMethod().IsStatic ?? false;

Check warning on line 1923 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Dereference of a possibly null reference.

Check warning on line 1923 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Dereference of a possibly null reference.

Check warning on line 1923 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Dereference of a possibly null reference.

Check warning on line 1923 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Dereference of a possibly null reference.
propertyOrFieldExpression = propertyIsStatic ? Expression.Property(null, property) : Expression.Property(expression, property);

Check warning on line 1924 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Possible null reference argument for parameter 'property' in 'MemberExpression Expression.Property(Expression? expression, PropertyInfo property)'.

Check warning on line 1924 in src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs

View workflow job for this annotation

GitHub Actions / Linux: Build and Tests

Possible null reference argument for parameter 'property' in 'MemberExpression Expression.Property(Expression? expression, PropertyInfo property)'.
return true;

case FieldInfo field:
Expand Down
30 changes: 30 additions & 0 deletions src/System.Linq.Dynamic.Core/Parser/PredefinedMethodsHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Collections.Generic;
using System.Linq.Dynamic.Core.Validation;
using System.Reflection;

namespace System.Linq.Dynamic.Core.Parser;

internal static class PredefinedMethodsHelper
{
internal static readonly MethodInfo ObjectToString = typeof(object).GetMethod(nameof(ToString), BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null)!;
internal static readonly MethodInfo ObjectInstanceEquals = typeof(object).GetMethod(nameof(Equals), BindingFlags.Instance | BindingFlags.Public, null, [typeof(object)], null)!;
internal static readonly MethodInfo ObjectStaticEquals = typeof(object).GetMethod(nameof(Equals), BindingFlags.Static | BindingFlags.Public, null, [typeof(object), typeof(object)], null)!;
internal static readonly MethodInfo ObjectStaticReferenceEquals = typeof(object).GetMethod(nameof(ReferenceEquals), BindingFlags.Static | BindingFlags.Public, null, [typeof(object), typeof(object)], null)!;


private static readonly HashSet<MemberInfo> ObjectToStringAndObjectEquals =
[
ObjectToString,
ObjectInstanceEquals,
ObjectStaticEquals,
ObjectStaticReferenceEquals
];

public static bool IsPredefinedMethod(ParsingConfig config, MemberInfo member)
{
Check.NotNull(config);
Check.NotNull(member);

return config.AllowEqualsAndToStringMethodsOnObject && ObjectToStringAndObjectEquals.Contains(member);
}
}
7 changes: 7 additions & 0 deletions src/System.Linq.Dynamic.Core/ParsingConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,11 @@ public IQueryableAnalyzer QueryableAnalyzer
/// Default value is <c>false</c>.
/// </summary>
public bool RestrictOrderByToPropertyOrField { get; set; }

/// <summary>
/// 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.
///
/// Default value is <c>false</c>.
/// </summary>
public bool AllowEqualsAndToStringMethodsOnObject { get; set; }
}
2 changes: 1 addition & 1 deletion src/System.Linq.Dynamic.Core/Res.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ internal static class Res
public const string InvalidStringLength = "String '{0}' should have at least {1} characters.";
public const string IsNullRequiresTwoArgs = "The 'isnull' function requires two arguments";
public const string MethodIsVoid = "Method '{0}' in type '{1}' does not return a value";
public const string MethodsAreInaccessible = "Methods on type '{0}' are not accessible";
public const string MethodIsInaccessible = "Method '{0}' on type '{1}' is not accessible.";
public const string MinusCannotBeAppliedToUnsignedInteger = "'-' cannot be applied to unsigned integers.";
public const string MissingAsClause = "Expression is missing an 'as' clause";
public const string NeitherTypeConvertsToOther = "Neither of the types '{0}' and '{1}' converts to the other";
Expand Down
10 changes: 7 additions & 3 deletions test/System.Linq.Dynamic.Core.Tests/DynamicClassTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -281,11 +281,15 @@ public void DynamicClassArray_Issue593_Fails()
isValid.Should().BeFalse(); // This should actually be true, but fails. For solution see Issue593_Solution1 and Issue593_Solution2.
}

// [SkipIfGitHubActions]
[Fact(Skip = "867")]
[SkipIfGitHubActions]
public void DynamicClassArray_Issue593_Solution1()
{
// Arrange
var config = new ParsingConfig
{
AllowEqualsAndToStringMethodsOnObject = true
};

var field = new
{
Name = "firstName",
Expand All @@ -308,7 +312,7 @@ public void DynamicClassArray_Issue593_Solution1()
var query = dynamicClasses.AsQueryable();

// Act
var isValid = query.Any("firstName.ToString() eq \"firstValue\"");
var isValid = query.Any(config, "firstName.ToString() eq \"firstValue\"");

// Assert
isValid.Should().BeTrue();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1058,13 +1058,23 @@ public void DynamicExpressionParser_ParseLambda_StringLiteral_QuotationMark()
Assert.Equal(expectedRightValue, rightValue);
}

[Fact(Skip = "867")]
[Fact]
public void DynamicExpressionParser_ParseLambda_TupleToStringMethodCall_ReturnsStringLambdaExpression()
{
// Arrange
var config = new ParsingConfig
{
AllowEqualsAndToStringMethodsOnObject = true
};

// Act
var expression = DynamicExpressionParser.ParseLambda(
config,
typeof(Tuple<int>),
typeof(string),
"it.ToString()");

// Assert
Assert.Equal(typeof(string), expression.ReturnType);
}

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

// Assert
action.Should().Throw<ParseException>().WithMessage("Methods on type 'CustomClassWithMethod' are not accessible");
action.Should().Throw<ParseException>().WithMessage("Method 'GetAge' on type 'CustomClassWithMethod' is not accessible.");
}

// [Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,8 @@ public void Parse_NullableShouldReturnNullable(string expression, object resultT
[Theory]
[InlineData("it.MainCompany.Name != null", "(company.MainCompany.Name != null)")]
[InlineData("@MainCompany.Companies.Count() > 0", "(company.MainCompany.Companies.Count() > 0)")]
// [InlineData("Company.Equals(null, null)", "Equals(null, null)")] issue 867
[InlineData("Company.Equals(null, null)", "Equals(null, null)")]
[InlineData("Equals(null)", "company.Equals(null)")]
[InlineData("MainCompany.Name", "company.MainCompany.Name")]
[InlineData("Name", "company.Name")]
[InlineData("company.Name", "company.Name")]
Expand All @@ -357,7 +358,8 @@ public void Parse_When_PrioritizePropertyOrFieldOverTheType_IsTrue(string expres
var config = new ParsingConfig
{
IsCaseSensitive = true,
CustomTypeProvider = _dynamicTypeProviderMock.Object
CustomTypeProvider = _dynamicTypeProviderMock.Object,
AllowEqualsAndToStringMethodsOnObject = true
};
ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "company") };
var sut = new ExpressionParser(parameters, expression, null, config);
Expand Down
91 changes: 84 additions & 7 deletions test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,93 @@ namespace System.Linq.Dynamic.Core.Tests.Parser;

public class MethodFinderTest
{
[Fact(Skip = "867")]
public void MethodsOfDynamicLinqAndSystemLinqShouldBeEqual()
[Fact]
public void Method_ToString_OnDynamicLinq_And_SystemLinq_ShouldBeEqual()
{
// Arrange
var config = new ParsingConfig
{
AllowEqualsAndToStringMethodsOnObject = true
};

Expression<Func<int?, string?>> expr = x => x.ToString();

var selector = "ToString()";
var prm = Parameter(typeof(int?));
var parser = new ExpressionParser([prm], selector, [], ParsingConfig.Default);
var expr1 = parser.Parse(null);

Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expr1).Method.DeclaringType);
var parser = new ExpressionParser([prm], selector, [], config);

// Act
var expression = parser.Parse(null);

// Assert
Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expression).Method.DeclaringType);
}

[Fact]
public void Method_Equals1_OnDynamicLinq_And_SystemLinq_ShouldBeEqual()
{
// Arrange
var config = new ParsingConfig
{
AllowEqualsAndToStringMethodsOnObject = true
};

Expression<Func<int?, bool>> expr = x => x.Equals("a");

var selector = "Equals(\"a\")";
var prm = Parameter(typeof(int?));
var parser = new ExpressionParser([prm], selector, [], config);

// Act
var expression = parser.Parse(null);

// Assert
Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expression).Method.DeclaringType);
}

[Fact]
public void Method_Equals2_OnDynamicLinq_And_SystemLinq_ShouldBeEqual()
{
// Arrange
var config = new ParsingConfig
{
AllowEqualsAndToStringMethodsOnObject = true
};

// ReSharper disable once RedundantNameQualifier
Expression<Func<int?, bool>> expr = x => object.Equals("a", "b");

var selector = "object.Equals(\"a\", \"b\")";
var prm = Parameter(typeof(int?));
var parser = new ExpressionParser([prm], selector, [], config);

// Act
var expression = parser.Parse(null);

// Assert
Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expression).Method.DeclaringType);
}

[Fact]
public void Method_ReferenceEquals_OnDynamicLinq_And_SystemLinq_ShouldBeEqual()
{
// Arrange
var config = new ParsingConfig
{
AllowEqualsAndToStringMethodsOnObject = true
};

// ReSharper disable once RedundantNameQualifier
Expression<Func<int?, bool>> expr = x => object.ReferenceEquals("a", "b");

var selector = "object.ReferenceEquals(\"a\", \"b\")";
var prm = Parameter(typeof(int?));
var parser = new ExpressionParser([prm], selector, [], config);

// Act
var expression = parser.Parse(null);

// Assert
Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expression).Method.DeclaringType);
}
}
14 changes: 7 additions & 7 deletions test/System.Linq.Dynamic.Core.Tests/SecurityTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void MethodsShouldOnlyBeCallableOnPredefinedTypes_Test1()
Action action = () => baseQuery.OrderBy(predicate);

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

[Fact]
Expand All @@ -48,19 +48,19 @@ public void MethodsShouldOnlyBeCallableOnPredefinedTypes_Test2()
);

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

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

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

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

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

[Theory]
Expand Down
Loading