From d9710a13e33107c4ebce1c8cfbbc17995addbd6e Mon Sep 17 00:00:00 2001 From: Daniel Svensson Date: Thu, 8 Feb 2024 14:09:56 +0100 Subject: [PATCH] Spanify DynamicQueryable (#485) This fixes the current analyser suggestions/warnings when building AspNetCore hosting about several places in query deserialisation where span's should be used instead of substring. Performance and allocations might be better, but the main driver is to remove build output when building the AspNetCore hosting project. Apart from that it also changes naming of private fields to better match the rest of the code base * Add System.Collections.Immutable nuget dependency to WCF Hosting and update other dependencies --- Changelog.md | 7 +- .../OpenRiaServices.Hosting.Wcf.nuspec | 8 +- ...RiaServices.Hosting.AspNetCore.Test.csproj | 3 + .../Framework/Linq/DynamicQueryable.cs | 493 ++++++++++-------- .../OpenRiaServices.Hosting.Wcf.csproj | 4 +- .../Test/Linq/QueryDeserializerTest.cs | 5 +- src/Test/WebsiteFullTrust/Web.config | 8 +- .../WebsiteFullTrust/WebsiteFullTrust.csproj | 3 + 8 files changed, 302 insertions(+), 229 deletions(-) diff --git a/Changelog.md b/Changelog.md index ea1af6a14..82ae85912 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,4 +1,9 @@ -# Unreleased +# Unreleased (5.5.0) + +### Server +* Reduced allocatgions when parsing queries on server (both WCF and AspNetCore hosting) in https://github.com/OpenRIAServices/OpenRiaServices/pull/485 +* Update nuget dependencies and add new dependencies to OpenRiaServices.Hosting.Wcf + * `System.Collections.Immutable` and `System.Memory` are new dependencies of Wcf hosting * .NET Framework builds now includes the smaller portable pdb's instead of of the old "full" windows style pdb's * NOTE: For .NET Framework apps ensure that *supportedRuntime* in *app.config* and corresponding setting in *web.config* does not specify an older runtime if you wan't line numbers in stack traces. diff --git a/NuGet/OpenRiaServices.Hosting.Wcf/OpenRiaServices.Hosting.Wcf.nuspec b/NuGet/OpenRiaServices.Hosting.Wcf/OpenRiaServices.Hosting.Wcf.nuspec index 4df76c2cb..4d77a4b39 100644 --- a/NuGet/OpenRiaServices.Hosting.Wcf/OpenRiaServices.Hosting.Wcf.nuspec +++ b/NuGet/OpenRiaServices.Hosting.Wcf/OpenRiaServices.Hosting.Wcf.nuspec @@ -16,13 +16,15 @@ Open RIA Services - Server-side assemblies and configuration For release notes see https://github.com/OpenRIAServices/OpenRiaServices/releases - 2019 Outercurve Foundation + 2024 .NET Foundation en-US WCF RIA Services RIAServices Server aspnet OpenRiaServices + - + + @@ -33,4 +35,4 @@ - \ No newline at end of file + diff --git a/src/OpenRiaServices.Hosting.AspNetCore/Test/OpenRiaServices.Hosting.AspNetCore.Test/OpenRiaServices.Hosting.AspNetCore.Test.csproj b/src/OpenRiaServices.Hosting.AspNetCore/Test/OpenRiaServices.Hosting.AspNetCore.Test/OpenRiaServices.Hosting.AspNetCore.Test.csproj index 79cfe7206..fba6004aa 100644 --- a/src/OpenRiaServices.Hosting.AspNetCore/Test/OpenRiaServices.Hosting.AspNetCore.Test/OpenRiaServices.Hosting.AspNetCore.Test.csproj +++ b/src/OpenRiaServices.Hosting.AspNetCore/Test/OpenRiaServices.Hosting.AspNetCore.Test/OpenRiaServices.Hosting.AspNetCore.Test.csproj @@ -15,4 +15,7 @@ + + + \ No newline at end of file diff --git a/src/OpenRiaServices.Hosting.Wcf/Framework/Linq/DynamicQueryable.cs b/src/OpenRiaServices.Hosting.Wcf/Framework/Linq/DynamicQueryable.cs index e8ac262f1..ab436cc60 100644 --- a/src/OpenRiaServices.Hosting.Wcf/Framework/Linq/DynamicQueryable.cs +++ b/src/OpenRiaServices.Hosting.Wcf/Framework/Linq/DynamicQueryable.cs @@ -1,11 +1,21 @@ -using System.Collections.Generic; +using System.Collections.Frozen; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq.Expressions; using System.Reflection; +using System; namespace System.Linq.Dynamic { +#if NETFRAMEWORK + static class StringExtensions + { + static public string Slice(this string str, int startIndex, int length) + => str.Substring(startIndex, length); + } +#endif + internal static class DynamicQueryable { public static IQueryable Where(this IQueryable source, string predicate, QueryResolver queryResolver) @@ -129,11 +139,11 @@ public ParseException(string message, int position) internal partial class ExpressionParser { - struct Token + struct Token(TokenId id, int pos, int length) { - public TokenId id; - public string text; - public int pos; + public readonly TokenId id = id; + public readonly int pos = pos; + public readonly int length = length; } enum TokenId @@ -287,7 +297,7 @@ interface IEnumerableSignatures void Average(decimal? selector); } - static readonly Type[] predefinedTypes = { + static readonly Type[] s_predefinedTypes = { typeof(Object), typeof(Boolean), typeof(Char), @@ -313,37 +323,43 @@ interface IEnumerableSignatures typeof(Uri) }; - static readonly Expression trueLiteral = Expression.Constant(true); - static readonly Expression falseLiteral = Expression.Constant(false); - static readonly Expression nullLiteral = Expression.Constant(null); + static readonly ConstantExpression TrueLiteral = Expression.Constant(true); + static readonly ConstantExpression FalseLiteral = Expression.Constant(false); + static readonly ConstantExpression NullLiteral = Expression.Constant(null); + + const string KeywordIt = "it"; + const string KeywordIif = "iif"; + + static readonly FrozenDictionary s_keywords = CreateKeywords(); + + readonly Dictionary _symbols = new Dictionary(StringComparer.OrdinalIgnoreCase); + readonly Dictionary> _literals = new(); + ParameterExpression _it; + readonly string _text; + int _textPos; + char _ch; + TokenId _tokenId; + int _tokenStart; + readonly QueryResolver _queryResolver; + + Token CurrentToken => new Token(_tokenId, _tokenStart, _textPos - _tokenStart); - const string keywordIt = "it"; - const string keywordIif = "iif"; + ReadOnlySpan GetText(Token token) => + _text.AsSpan(token.pos, token.length); - static readonly Dictionary keywords = CreateKeywords(); + string GetString(Token token) + => _text.Substring(token.pos, token.length); - Dictionary symbols; - Dictionary literals; - ParameterExpression it; - string text; - int textPos; - int textLen; - char ch; - Token token; - QueryResolver queryResolver; public ExpressionParser(ParameterExpression[] parameters, string expression, QueryResolver queryResolver) { if (expression == null) throw new ArgumentNullException(nameof(expression)); - this.queryResolver = queryResolver; - symbols = new Dictionary(StringComparer.OrdinalIgnoreCase); - literals = new Dictionary(); + this._queryResolver = queryResolver; if (parameters != null) ProcessParameters(parameters); - text = expression; - textLen = text.Length; + _text = expression; SetTextPos(0); NextToken(); } @@ -351,21 +367,21 @@ public ExpressionParser(ParameterExpression[] parameters, string expression, Que void ProcessParameters(ParameterExpression[] parameters) { foreach (ParameterExpression pe in parameters) - if (!String.IsNullOrEmpty(pe.Name)) + if (!string.IsNullOrEmpty(pe.Name)) AddSymbol(pe.Name, pe); - if (parameters.Length == 1 && String.IsNullOrEmpty(parameters[0].Name)) - it = parameters[0]; + if (parameters.Length == 1 && string.IsNullOrEmpty(parameters[0].Name)) + _it = parameters[0]; } void AddSymbol(string name, object value) { - if (!symbols.TryAdd(name, value)) + if (!_symbols.TryAdd(name, value)) throw ParseError(Resource.DuplicateIdentifier, name); } public Expression Parse(Type resultType) { - int exprPos = token.pos; + int exprPos = CurrentToken.pos; Expression expr = ParseExpression(); if (resultType != null) if ((expr = PromoteExpression(expr, resultType, true)) == null) @@ -374,7 +390,6 @@ public Expression Parse(Type resultType) return expr; } -#pragma warning disable 0219 public IEnumerable ParseOrdering() { List orderings = new List(); @@ -382,35 +397,38 @@ public IEnumerable ParseOrdering() { Expression expr = ParseExpression(); bool ascending = true; - if (TokenIdentifierIs("asc") || TokenIdentifierIs("ascending")) + if (_tokenId == TokenId.Identifier) { - NextToken(); - } - else if (TokenIdentifierIs("desc") || TokenIdentifierIs("descending")) - { - NextToken(); - ascending = false; + if (TokenIdentifierIs("asc") || TokenIdentifierIs("ascending")) + { + NextToken(); + } + else if (TokenIdentifierIs("desc") || TokenIdentifierIs("descending")) + { + NextToken(); + ascending = false; + } } + orderings.Add(new DynamicOrdering { Selector = expr, Ascending = ascending }); - if (token.id != TokenId.Comma) + if (CurrentToken.id != TokenId.Comma) break; NextToken(); } ValidateToken(TokenId.End, Resource.SyntaxError); return orderings; } -#pragma warning restore 0219 // ?: operator Expression ParseExpression() { - int errorPos = token.pos; + int errorPos = CurrentToken.pos; Expression expr = ParseLogicalOr(); - if (token.id == TokenId.Question) + if (CurrentToken.id == TokenId.Question) { NextToken(); Expression expr1 = ParseExpression(); @@ -426,12 +444,12 @@ Expression ParseExpression() Expression ParseLogicalOr() { Expression left = ParseLogicalAnd(); - while (token.id == TokenId.DoubleBar || TokenIdentifierIs("or")) + while (CurrentToken.id == TokenId.DoubleBar || TokenIdentifierIs("or")) { - Token op = token; + Token op = CurrentToken; NextToken(); Expression right = ParseLogicalAnd(); - CheckAndPromoteOperands(typeof(ILogicalSignatures), op.text, ref left, ref right, op.pos); + CheckAndPromoteOperands(typeof(ILogicalSignatures), op, ref left, ref right); left = Expression.OrElse(left, right); } return left; @@ -441,12 +459,12 @@ Expression ParseLogicalOr() Expression ParseLogicalAnd() { Expression left = ParseHasOperator(); - while (token.id == TokenId.DoubleAmphersand || TokenIdentifierIs("and")) + while (CurrentToken.id == TokenId.DoubleAmphersand || TokenIdentifierIs("and")) { - Token op = token; + Token op = CurrentToken; NextToken(); Expression right = ParseHasOperator(); - CheckAndPromoteOperands(typeof(ILogicalSignatures), op.text, ref left, ref right, op.pos); + CheckAndPromoteOperands(typeof(ILogicalSignatures), op, ref left, ref right); left = Expression.AndAlso(left, right); } return left; @@ -462,7 +480,7 @@ Expression ParseHasOperator() Expression left = ParseComparison(); while (TokenIdentifierIs("has")) { - Token op = token; + Token op = CurrentToken; NextToken(); Type enumType = left.Type; @@ -476,26 +494,26 @@ Expression ParseHasOperator() NextToken(); //Verify next is dot and then remove it - if (token.id != TokenId.Dot) - throw new ParseException("Expected a dot", token.pos); + if (CurrentToken.id != TokenId.Dot) + throw new ParseException("Expected a dot", CurrentToken.pos); NextToken(); // Read the enum field name, parse it and remove it - if (token.id != TokenId.Identifier) - throw new ParseException("Expected an Identifier", token.pos); - right = Expression.Constant(ParseEnum(token.text, enumType), enumType); + if (CurrentToken.id != TokenId.Identifier) + throw new ParseException("Expected an Identifier", CurrentToken.pos); + right = Expression.Constant(ParseEnum(GetText(CurrentToken), enumType), enumType); NextToken(); } else // Either numeric value or member access - { + { right = ParseComparison(); } left = ConvertEnumExpression(left, right); right = ConvertEnumExpression(right, left); - CheckAndPromoteOperands(typeof(IArithmeticSignatures), op.text, ref left, ref right, op.pos); - + CheckAndPromoteOperands(typeof(IArithmeticSignatures), op, ref left, ref right); + // Treat as (left & right) == right which is the same behaviour as calling Enum.HasFlag // but it will work with entity framework and probably most other query providers left = Expression.Equal(Expression.And(left, right), right); @@ -507,16 +525,17 @@ Expression ParseHasOperator() Expression ParseComparison() { Expression left = ParseAdditive(); - while (token.id == TokenId.Equal || token.id == TokenId.DoubleEqual || - token.id == TokenId.ExclamationEqual || token.id == TokenId.LessGreater || - token.id == TokenId.GreaterThan || token.id == TokenId.GreaterThanEqual || - token.id == TokenId.LessThan || token.id == TokenId.LessThanEqual) + + while (CurrentToken.id is TokenId.Equal or TokenId.DoubleEqual or + TokenId.ExclamationEqual or TokenId.LessGreater or + TokenId.GreaterThan or TokenId.GreaterThanEqual or + TokenId.LessThan or TokenId.LessThanEqual) { - Token op = token; + Token op = CurrentToken; NextToken(); Expression right = ParseAdditive(); - bool isEquality = op.id == TokenId.Equal || op.id == TokenId.DoubleEqual || - op.id == TokenId.ExclamationEqual || op.id == TokenId.LessGreater; + bool isEquality = op.id is TokenId.Equal or TokenId.DoubleEqual or + TokenId.ExclamationEqual or TokenId.LessGreater; if (isEquality && !left.Type.IsValueType && !right.Type.IsValueType) { if (left.Type != right.Type) @@ -531,7 +550,7 @@ Expression ParseComparison() } else { - throw IncompatibleOperandsError(op.text, left, right, op.pos); + throw IncompatibleOperandsError(GetString(op), left, right, op.pos); } } } @@ -542,12 +561,12 @@ Expression ParseComparison() right = ConvertEnumExpression(right, left); CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), - op.text, ref left, ref right, op.pos); + op, ref left, ref right); } else { CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), - op.text, ref left, ref right, op.pos); + op, ref left, ref right); } switch (op.id) { @@ -607,10 +626,10 @@ static Expression ConvertEnumExpression(Expression expr, Expression otherExpr) Expression ParseAdditive() { Expression left = ParseMultiplicative(); - while (token.id == TokenId.Plus || token.id == TokenId.Minus || - token.id == TokenId.Amphersand) + while (CurrentToken.id is TokenId.Plus or TokenId.Minus or + TokenId.Amphersand) { - Token op = token; + Token op = CurrentToken; NextToken(); Expression right = ParseMultiplicative(); switch (op.id) @@ -618,11 +637,11 @@ Expression ParseAdditive() case TokenId.Plus: if (left.Type == typeof(string) || right.Type == typeof(string)) goto case TokenId.Amphersand; - CheckAndPromoteOperands(typeof(IAddSignatures), op.text, ref left, ref right, op.pos); + CheckAndPromoteOperands(typeof(IAddSignatures), op, ref left, ref right); left = GenerateAdd(left, right); break; case TokenId.Minus: - CheckAndPromoteOperands(typeof(ISubtractSignatures), op.text, ref left, ref right, op.pos); + CheckAndPromoteOperands(typeof(ISubtractSignatures), op, ref left, ref right); left = GenerateSubtract(left, right); break; case TokenId.Amphersand: @@ -637,13 +656,13 @@ Expression ParseAdditive() Expression ParseMultiplicative() { Expression left = ParseUnary(); - while (token.id == TokenId.Asterisk || token.id == TokenId.Slash || - token.id == TokenId.Percent || TokenIdentifierIs("mod")) + while (CurrentToken.id == TokenId.Asterisk || CurrentToken.id == TokenId.Slash || + CurrentToken.id == TokenId.Percent || TokenIdentifierIs("mod")) { - Token op = token; + Token op = CurrentToken; NextToken(); Expression right = ParseUnary(); - CheckAndPromoteOperands(typeof(IArithmeticSignatures), op.text, ref left, ref right, op.pos); + CheckAndPromoteOperands(typeof(IArithmeticSignatures), op, ref left, ref right); switch (op.id) { case TokenId.Asterisk: @@ -664,27 +683,27 @@ Expression ParseMultiplicative() // -, !, not unary operators Expression ParseUnary() { - if (token.id == TokenId.Minus || token.id == TokenId.Exclamation || + if (CurrentToken.id == TokenId.Minus || CurrentToken.id == TokenId.Exclamation || TokenIdentifierIs("not")) { - Token op = token; + Token op = CurrentToken; NextToken(); - if (op.id == TokenId.Minus && (token.id == TokenId.IntegerLiteral || - token.id == TokenId.RealLiteral)) + if (op.id == TokenId.Minus && (CurrentToken.id == TokenId.IntegerLiteral || + CurrentToken.id == TokenId.RealLiteral)) { - token.text = "-" + token.text; - token.pos = op.pos; + // Reset token start to also include "-" sign + _tokenStart = op.pos; return ParsePrimary(); } Expression expr = ParseUnary(); if (op.id == TokenId.Minus) { - CheckAndPromoteOperand(typeof(INegationSignatures), op.text, ref expr, op.pos); + CheckAndPromoteOperand(typeof(INegationSignatures), op, ref expr); expr = Expression.Negate(expr); } else { - CheckAndPromoteOperand(typeof(INotSignatures), op.text, ref expr, op.pos); + CheckAndPromoteOperand(typeof(INotSignatures), op, ref expr); expr = Expression.Not(expr); } return expr; @@ -697,12 +716,12 @@ Expression ParsePrimary() Expression expr = ParsePrimaryStart(); while (true) { - if (token.id == TokenId.Dot) + if (CurrentToken.id == TokenId.Dot) { NextToken(); expr = ParseMemberAccess(null, expr); } - else if (token.id == TokenId.OpenBracket) + else if (CurrentToken.id == TokenId.OpenBracket) { expr = ParseElementAccess(expr); } @@ -716,7 +735,7 @@ Expression ParsePrimary() Expression ParsePrimaryStart() { - switch (token.id) + switch (CurrentToken.id) { case TokenId.Identifier: return ParseIdentifier(); @@ -736,9 +755,10 @@ Expression ParsePrimaryStart() Expression ParseStringLiteral() { ValidateToken(TokenId.StringLiteral); - char quote = token.text[0]; + var text = GetText(CurrentToken); + char quote = text[0]; // Unwrap string (remove surrounding quotes) and unwrap backslashes. - string s = token.text.Substring(1, token.text.Length - 2).Replace("\\\\", "\\"); + string s = text.Slice(1, text.Length - 2).ToString().Replace("\\\\", "\\"); if (quote == '\'') { // Unwrap quotes. @@ -760,74 +780,90 @@ Expression ParseStringLiteral() Expression ParseIntegerLiteral() { + Token token = CurrentToken; ValidateToken(TokenId.IntegerLiteral); - string text = token.text; +#if NET + ReadOnlySpan text = GetText(token); +#else + string text = GetString(token); +#endif if (text[0] != '-') { ulong value; - if (!UInt64.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out value)) - throw ParseError(Resource.InvalidIntegerLiteral, text); + if (!ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out value)) + throw ParseError(Resource.InvalidIntegerLiteral, text.ToString()); NextToken(); - if (value <= (ulong)Int32.MaxValue) - return CreateLiteral((int)value, text); - if (value <= (ulong)UInt32.MaxValue) - return CreateLiteral((uint)value, text); - if (value <= (ulong)Int64.MaxValue) - return CreateLiteral((long)value, text); - return CreateLiteral(value, text); + if (value <= (ulong)int.MaxValue) + return CreateLiteral((int)value, token); + if (value <= (ulong)uint.MaxValue) + return CreateLiteral((uint)value, token); + if (value <= (ulong)long.MaxValue) + return CreateLiteral((long)value, token); + return CreateLiteral(value, token); } else { long value; - if (!Int64.TryParse(text, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out value)) - throw ParseError(Resource.InvalidIntegerLiteral, text); + if (!long.TryParse(text, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out value)) + throw ParseError(Resource.InvalidIntegerLiteral, text.ToString()); NextToken(); - if (value >= Int32.MinValue && value <= Int32.MaxValue) - return CreateLiteral((int)value, text); - return CreateLiteral(value, text); + if (value >= int.MinValue && value <= int.MaxValue) + return CreateLiteral((int)value, token); + return CreateLiteral(value, token); } } Expression ParseRealLiteral() { ValidateToken(TokenId.RealLiteral); - string text = token.text; + Token token = CurrentToken; +#if NET + ReadOnlySpan text = GetText(token); +#else + string text = GetString(token); +#endif object value = null; char last = text[text.Length - 1]; - if (last == 'F' || last == 'f') + if (last is 'F' or 'f') { float f; - if (Single.TryParse(text.Substring(0, text.Length - 1), NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out f)) + if (float.TryParse(text.Slice(0, text.Length - 1), NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out f)) value = f; } - else if (last == 'M' || last == 'm') + else if (last is 'M' or 'm') { decimal m; - if (Decimal.TryParse(text.Substring(0, text.Length - 1), NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out m)) + if (decimal.TryParse(text.Slice(0, text.Length - 1), NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out m)) value = m; } - else if (last == 'D' || last == 'd') + else if (last is 'D' or 'd') { double d; - if (Double.TryParse(text.Substring(0, text.Length - 1), NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out d)) + if (double.TryParse(text.Slice(0, text.Length - 1), NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out d)) value = d; } else { double d; - if (Double.TryParse(text, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out d)) + if (double.TryParse(text, NumberStyles.Number | NumberStyles.AllowExponent, CultureInfo.InvariantCulture, out d)) value = d; } if (value == null) - throw ParseError(Resource.InvalidRealLiteral, text); + throw ParseError(Resource.InvalidRealLiteral, text.ToString()); NextToken(); - return CreateLiteral(value, text); + return CreateLiteral(value, token); } Expression CreateLiteral(object value, string valueAsString) { ConstantExpression expr = Expression.Constant(value); - literals.Add(expr, valueAsString); + _literals.Add(expr, valueAsString.AsMemory()); + return expr; + } + Expression CreateLiteral(object value, Token token) + { + ConstantExpression expr = Expression.Constant(value); + _literals.Add(expr, _text.AsMemory(token.pos, token.length)); return expr; } @@ -845,18 +881,20 @@ Expression ParseIdentifier() { ValidateToken(TokenId.Identifier); object value; - if (keywords.TryGetValue(token.text, out value)) + string text = GetString(CurrentToken); + + if (s_keywords.TryGetValue(text, out value)) { - if (value is Type) - return ParseTypeAccess((Type)value); - if (value == (object)keywordIt) + if (value is Type type) + return ParseTypeAccess(type); + if (object.ReferenceEquals(value, KeywordIt)) return ParseIt(); - if (value == (object)keywordIif) + if (object.ReferenceEquals(value, KeywordIif)) return ParseIif(); NextToken(); return (Expression)value; } - if (symbols.TryGetValue(token.text, out value)) + if (_symbols.TryGetValue(text, out value)) { Expression expr = value as Expression; if (expr == null) @@ -866,22 +904,22 @@ Expression ParseIdentifier() NextToken(); return expr; } - if (it != null) - return ParseMemberAccess(null, it); - throw ParseError(Resource.UnknownIdentifier, token.text); + if (_it != null) + return ParseMemberAccess(null, _it); + throw ParseError(Resource.UnknownIdentifier, text); } Expression ParseIt() { - if (it == null) + if (_it == null) throw ParseError(Resource.NoItInScope); NextToken(); - return it; + return _it; } Expression ParseIif() { - int errorPos = token.pos; + int errorPos = CurrentToken.pos; NextToken(); Expression[] args = ParseArgumentList(); if (args.Length != 3) @@ -895,8 +933,8 @@ Expression GenerateConditional(Expression test, Expression expr1, Expression exp throw ParseError(errorPos, Resource.FirstExprMustBeBool); if (expr1.Type != expr2.Type) { - Expression expr1as2 = expr2 != nullLiteral ? PromoteExpression(expr1, expr2.Type, true) : null; - Expression expr2as1 = expr1 != nullLiteral ? PromoteExpression(expr2, expr1.Type, true) : null; + Expression expr1as2 = expr2 != NullLiteral ? PromoteExpression(expr1, expr2.Type, true) : null; + Expression expr2as1 = expr1 != NullLiteral ? PromoteExpression(expr2, expr1.Type, true) : null; if (expr1as2 != null && expr2as1 == null) { expr1 = expr1as2; @@ -907,8 +945,8 @@ Expression GenerateConditional(Expression test, Expression expr1, Expression exp } else { - string type1 = expr1 != nullLiteral ? expr1.Type.Name : "null"; - string type2 = expr2 != nullLiteral ? expr2.Type.Name : "null"; + string type1 = expr1 != NullLiteral ? expr1.Type.Name : "null"; + string type2 = expr2 != NullLiteral ? expr2.Type.Name : "null"; if (expr1as2 != null && expr2as1 != null) throw ParseError(errorPos, Resource.BothTypesConvertToOther, type1, type2); throw ParseError(errorPos, Resource.NeitherTypeConvertsToOther, type1, type2); @@ -919,16 +957,16 @@ Expression GenerateConditional(Expression test, Expression expr1, Expression exp Expression ParseTypeAccess(Type type) { - int errorPos = token.pos; + int errorPos = CurrentToken.pos; NextToken(); - if (token.id == TokenId.Question) + if (CurrentToken.id == TokenId.Question) { if (!type.IsValueType || IsNullableType(type)) throw ParseError(errorPos, Resource.TypeHasNoNullableForm, GetTypeName(type)); type = typeof(Nullable<>).MakeGenericType(type); NextToken(); } - if (token.id == TokenId.OpenParen) + if (CurrentToken.id == TokenId.OpenParen) { Expression[] args = ParseArgumentList(); MethodBase method; @@ -977,10 +1015,10 @@ Expression ParseMemberAccess(Type type, Expression instance) { if (instance != null) type = instance.Type; - int errorPos = token.pos; + int errorPos = CurrentToken.pos; string id = GetIdentifier(); NextToken(); - if (token.id == TokenId.OpenParen) + if (CurrentToken.id == TokenId.OpenParen) { if (instance != null && type != typeof(string)) { @@ -1018,9 +1056,9 @@ Expression ParseMemberAccess(Type type, Expression instance) MemberInfo member = FindPropertyOrField(type, id, instance == null); if (member == null) { - if (this.queryResolver != null) + if (this._queryResolver != null) { - MemberExpression mex = queryResolver.ResolveMember(type, id, instance); + MemberExpression mex = _queryResolver.ResolveMember(type, id, instance); if (mex != null) { return mex; @@ -1057,16 +1095,16 @@ static Type FindGenericType(Type generic, Type type) Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos) { - ParameterExpression outerIt = it; + ParameterExpression outerIt = _it; ParameterExpression innerIt = Expression.Parameter(elementType, ""); - it = innerIt; + _it = innerIt; Expression[] args = ParseArgumentList(); - it = outerIt; + _it = outerIt; MethodBase signature; if (FindMethod(typeof(IEnumerableSignatures), methodName, false, args, out signature) != 1) throw ParseError(errorPos, Resource.NoApplicableAggregate, methodName); Type[] typeArgs; - if (signature.Name == "Min" || signature.Name == "Max") + if (signature.Name is "Min" or "Max") { typeArgs = new Type[] { elementType, args[0].Type }; } @@ -1089,7 +1127,7 @@ Expression[] ParseArgumentList() { ValidateToken(TokenId.OpenParen, Resource.OpenParenExpected); NextToken(); - Expression[] args = token.id != TokenId.CloseParen ? ParseArguments() : Array.Empty(); + Expression[] args = CurrentToken.id != TokenId.CloseParen ? ParseArguments() : Array.Empty(); ValidateToken(TokenId.CloseParen, Resource.CloseParenOrCommaExpected); NextToken(); return args; @@ -1101,7 +1139,7 @@ Expression[] ParseArguments() while (true) { argList.Add(ParseExpression()); - if (token.id != TokenId.Comma) + if (CurrentToken.id != TokenId.Comma) break; NextToken(); } @@ -1110,7 +1148,7 @@ Expression[] ParseArguments() Expression ParseElementAccess(Expression expr) { - int errorPos = token.pos; + int errorPos = CurrentToken.pos; ValidateToken(TokenId.OpenBracket, Resource.OpenParenExpected); NextToken(); Expression[] args = ParseArguments(); @@ -1145,7 +1183,7 @@ Expression ParseElementAccess(Expression expr) static bool IsPredefinedType(Type type) { type = GetNonNullableType(type); - foreach (Type t in predefinedTypes) + foreach (Type t in s_predefinedTypes) if (t == type) return true; return false; @@ -1217,22 +1255,22 @@ static bool IsEnumType(Type type) return GetNonNullableType(type).IsEnum; } - void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr, int errorPos) + void CheckAndPromoteOperand(Type signatures, Token op, ref Expression expr) { Expression[] args = new Expression[] { expr }; MethodBase method; if (FindMethod(signatures, "F", false, args, out method) != 1) - throw ParseError(errorPos, Resource.IncompatibleOperand, - opName, GetTypeName(args[0].Type)); + throw ParseError(op.pos, Resource.IncompatibleOperand, + GetString(op), GetTypeName(args[0].Type)); expr = args[0]; } - void CheckAndPromoteOperands(Type signatures, string opName, ref Expression left, ref Expression right, int errorPos) + void CheckAndPromoteOperands(Type signatures, Token op, ref Expression left, ref Expression right) { Expression[] args = new Expression[] { left, right }; MethodBase method; if (FindMethod(signatures, "F", false, args, out method) != 1) - throw IncompatibleOperandsError(opName, left, right, errorPos); + throw IncompatibleOperandsError(GetString(op), left, right, op.pos); left = args[0]; right = args[1]; } @@ -1386,18 +1424,17 @@ Expression PromoteExpression(Expression expr, Type type, bool exact) if (expr is ConstantExpression) { ConstantExpression ce = (ConstantExpression)expr; - if (ce == nullLiteral) + if (ce == NullLiteral) { if (!type.IsValueType || IsNullableType(type)) return Expression.Constant(null, type); } else { - string text; - if (literals.TryGetValue(ce, out text)) + if (_literals.TryGetValue(ce, out ReadOnlyMemory text)) { Type target = GetNonNullableType(type); - Object value = null; + object value = null; switch (Type.GetTypeCode(ce.Type)) { case TypeCode.Int32: @@ -1407,7 +1444,7 @@ Expression PromoteExpression(Expression expr, Type type, bool exact) if (target.IsEnum) { // promoting from a number to an enum - value = Enum.Parse(target, text); + value = Enum.ToObject(target, ce.Value); } else if (target == typeof(char)) { @@ -1424,15 +1461,17 @@ Expression PromoteExpression(Expression expr, Type type, bool exact) } else { - value = ParseNumber(text, target); + value = ParseNumber(text.Span, target); } break; case TypeCode.Double: + // Wanted decimal but it got parsed as double, needs to reparse to avoid loosing precision if (target == typeof(decimal)) - value = ParseNumber(text, target); + value = ParseNumber(text.Span, target); break; case TypeCode.String: - value = ParseEnum(text, target); + // We parsed as text but wanted it as something else, probaly an enum + value = ParseEnum(text.Span, target); break; } if (value != null) @@ -1449,8 +1488,14 @@ Expression PromoteExpression(Expression expr, Type type, bool exact) return null; } - static object ParseNumber(string text, Type type) +#if NETFRAMEWORK + static object ParseNumber(ReadOnlySpan span, Type type) + { + string text = span.ToString(); +#else + static object ParseNumber(ReadOnlySpan text, Type type) { +#endif switch (Type.GetTypeCode(GetNonNullableType(type))) { case TypeCode.SByte: @@ -1512,15 +1557,20 @@ static object ParseNumber(string text, Type type) return null; } - static object ParseEnum(string name, Type type) + static object ParseEnum(ReadOnlySpan name, Type type) { if (type.IsEnum) { +#if NET + if (Enum.TryParse(type, name, ignoreCase: true, out var result)) + return result; +#else MemberInfo[] memberInfos = type.FindMembers(MemberTypes.Field, BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static, - Type.FilterNameIgnoreCase, name); + Type.FilterNameIgnoreCase, name.ToString()); if (memberInfos.Length != 0) return ((FieldInfo)memberInfos[0]).GetValue(null); +#endif } return null; } @@ -1789,28 +1839,28 @@ static Expression GenerateStaticMethodCall(string methodName, Expression left, E void SetTextPos(int pos) { - textPos = pos; - ch = textPos < textLen ? text[textPos] : '\0'; + _textPos = pos; + _ch = _textPos < _text.Length ? _text[_textPos] : '\0'; } void NextChar() { - if (textPos < textLen) - textPos++; - ch = textPos < textLen ? text[textPos] : '\0'; + if (_textPos < _text.Length) + _textPos++; + _ch = _textPos < _text.Length ? _text[_textPos] : '\0'; } void NextToken() { - while (Char.IsWhiteSpace(ch)) + while (char.IsWhiteSpace(_ch)) NextChar(); TokenId t; - int tokenPos = textPos; - switch (ch) + _tokenStart = _textPos; + switch (_ch) { case '!': NextChar(); - if (ch == '=') + if (_ch == '=') { NextChar(); t = TokenId.ExclamationEqual; @@ -1826,7 +1876,7 @@ void NextToken() break; case '&': NextChar(); - if (ch == '&') + if (_ch == '&') { NextChar(); t = TokenId.DoubleAmphersand; @@ -1874,12 +1924,12 @@ void NextToken() break; case '<': NextChar(); - if (ch == '=') + if (_ch == '=') { NextChar(); t = TokenId.LessThanEqual; } - else if (ch == '>') + else if (_ch == '>') { NextChar(); t = TokenId.LessGreater; @@ -1891,7 +1941,7 @@ void NextToken() break; case '=': NextChar(); - if (ch == '=') + if (_ch == '=') { NextChar(); t = TokenId.DoubleEqual; @@ -1903,7 +1953,7 @@ void NextToken() break; case '>': NextChar(); - if (ch == '=') + if (_ch == '=') { NextChar(); t = TokenId.GreaterThanEqual; @@ -1927,7 +1977,7 @@ void NextToken() break; case '|': NextChar(); - if (ch == '|') + if (_ch == '|') { NextChar(); t = TokenId.DoubleBar; @@ -1939,13 +1989,13 @@ void NextToken() break; case '"': case '\'': - char quote = ch; + char quote = _ch; do { NextChar(); - while (textPos < textLen && ch != quote) + while (_textPos < _text.Length && _ch != quote) { - if (ch == '\\') + if (_ch == '\\') { NextChar(); } @@ -1953,30 +2003,30 @@ void NextToken() NextChar(); } - if (textPos == textLen) - throw ParseError(textPos, Resource.UnterminatedStringLiteral); + if (_textPos == _text.Length) + throw ParseError(_textPos, Resource.UnterminatedStringLiteral); NextChar(); - } while (ch == quote); + } while (_ch == quote); t = TokenId.StringLiteral; break; default: - if (IsIdentifierStart(ch) || ch == '@' || ch == '_') + if (IsIdentifierStart(_ch) || _ch == '@' || _ch == '_') { do { NextChar(); - } while (IsIdentifierPart(ch) || ch == '_'); + } while (IsIdentifierPart(_ch) || _ch == '_'); t = TokenId.Identifier; break; } - if (Char.IsDigit(ch)) + if (char.IsDigit(_ch)) { t = TokenId.IntegerLiteral; do { NextChar(); - } while (Char.IsDigit(ch)); - if (ch == '.') + } while (char.IsDigit(_ch)); + if (_ch == '.') { t = TokenId.RealLiteral; NextChar(); @@ -1984,37 +2034,36 @@ void NextToken() do { NextChar(); - } while (Char.IsDigit(ch)); + } while (char.IsDigit(_ch)); } - if (ch == 'E' || ch == 'e') + if (_ch is 'E' or 'e') { t = TokenId.RealLiteral; NextChar(); - if (ch == '+' || ch == '-') + if (_ch is '+' or '-') NextChar(); ValidateDigit(); do { NextChar(); - } while (Char.IsDigit(ch)); + } while (char.IsDigit(_ch)); } - if (ch == 'F' || ch == 'f' || ch == 'M' || ch == 'm' || ch == 'D' || ch == 'd') + if (_ch is 'F' or 'f' or 'M' or 'm' or 'D' or 'd') { t = TokenId.RealLiteral; NextChar(); } break; } - if (textPos == textLen) + if (_textPos == _text.Length) { t = TokenId.End; break; } - throw ParseError(textPos, Resource.InvalidCharacter, ch); + throw ParseError(_textPos, Resource.InvalidCharacter, _ch); } - token.id = t; - token.text = text.Substring(tokenPos, textPos - tokenPos); - token.pos = tokenPos; + + _tokenId = t; } static bool IsIdentifierStart(char ch) @@ -2026,7 +2075,7 @@ static bool IsIdentifierStart(char ch) 1 << (int)UnicodeCategory.ModifierLetter | 1 << (int)UnicodeCategory.OtherLetter | 1 << (int)UnicodeCategory.LetterNumber; - return (1 << (int)Char.GetUnicodeCategory(ch) & mask) != 0; + return (1 << (int)char.GetUnicodeCategory(ch) & mask) != 0; } static bool IsIdentifierPart(char ch) @@ -2043,44 +2092,47 @@ static bool IsIdentifierPart(char ch) 1 << (int)UnicodeCategory.NonSpacingMark | 1 << (int)UnicodeCategory.SpacingCombiningMark | 1 << (int)UnicodeCategory.Format; - return (1 << (int)Char.GetUnicodeCategory(ch) & mask) != 0; + return (1 << (int)char.GetUnicodeCategory(ch) & mask) != 0; } bool TokenIdentifierIs(string id) { - return token.id == TokenId.Identifier && String.Equals(id, token.text, StringComparison.OrdinalIgnoreCase); + Token token = this.CurrentToken; + return _tokenId == TokenId.Identifier + && token.length == id.Length + && GetText(token).Equals(id.AsSpan(), StringComparison.OrdinalIgnoreCase); } string GetIdentifier() { ValidateToken(TokenId.Identifier, Resource.IdentifierExpected); - string id = token.text; + var id = GetText(CurrentToken); if (id.Length > 1 && id[0] == '@') - id = id.Substring(1); - return id; + id = id.Slice(1); + return id.ToString(); } void ValidateDigit() { - if (!Char.IsDigit(ch)) - throw ParseError(textPos, Resource.DigitExpected); + if (!char.IsDigit(_ch)) + throw ParseError(_textPos, Resource.DigitExpected); } void ValidateToken(TokenId t, string errorMessage) { - if (token.id != t) + if (_tokenId != t) throw ParseError(errorMessage); } void ValidateToken(TokenId t) { - if (token.id != t) + if (_tokenId != t) throw ParseError(Resource.SyntaxError); } Exception ParseError(string format, params object[] args) { - return ParseError(token.pos, format, args); + return ParseError(CurrentToken.pos, format, args); } static Exception ParseError(int pos, string format, params object[] args) @@ -2088,17 +2140,20 @@ static Exception ParseError(int pos, string format, params object[] args) return new ParseException(string.Format(CultureInfo.CurrentCulture, format, args), pos); } - static Dictionary CreateKeywords() + static FrozenDictionary CreateKeywords() { - Dictionary d = new Dictionary(StringComparer.OrdinalIgnoreCase); - d.Add("true", trueLiteral); - d.Add("false", falseLiteral); - d.Add("null", nullLiteral); - d.Add(keywordIt, keywordIt); - d.Add(keywordIif, keywordIif); - foreach (Type type in predefinedTypes) + Dictionary d = new(capacity: 5 + s_predefinedTypes.Length, StringComparer.OrdinalIgnoreCase) + { + { "true", TrueLiteral }, + { "false", FalseLiteral }, + { "null", NullLiteral }, + { KeywordIt, KeywordIt }, + { KeywordIif, KeywordIif } + }; + foreach (Type type in s_predefinedTypes) d.Add(type.Name, type); - return d; + + return d.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase); } } } diff --git a/src/OpenRiaServices.Hosting.Wcf/Framework/OpenRiaServices.Hosting.Wcf.csproj b/src/OpenRiaServices.Hosting.Wcf/Framework/OpenRiaServices.Hosting.Wcf.csproj index e66531131..fa9f57ea6 100644 --- a/src/OpenRiaServices.Hosting.Wcf/Framework/OpenRiaServices.Hosting.Wcf.csproj +++ b/src/OpenRiaServices.Hosting.Wcf/Framework/OpenRiaServices.Hosting.Wcf.csproj @@ -18,7 +18,9 @@ - + + + diff --git a/src/OpenRiaServices.Hosting.Wcf/Test/Linq/QueryDeserializerTest.cs b/src/OpenRiaServices.Hosting.Wcf/Test/Linq/QueryDeserializerTest.cs index eaac90390..89718e0e3 100644 --- a/src/OpenRiaServices.Hosting.Wcf/Test/Linq/QueryDeserializerTest.cs +++ b/src/OpenRiaServices.Hosting.Wcf/Test/Linq/QueryDeserializerTest.cs @@ -2,7 +2,6 @@ using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Runtime.Serialization; -using OpenRiaServices.Client.Test; using OpenRiaServices.Server; using OpenRiaServices.Hosting; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -45,7 +44,7 @@ public void TestConstructorAccessChecked() new ServiceQueryPart("where", "it.Double.ToString() == String('a', 1000)") }; - ExceptionHelper.ExpectException(delegate + Assert.ThrowsException(() => { QueryDeserializer.Deserialize(DomainServiceDescription.GetDescription(typeof(QueryDeserializerDomainService)), queryable, queryParts); }, string.Format(CultureInfo.CurrentCulture, System.Linq.Dynamic.Resource.MethodsAreInaccessible + " (at index 24)", typeof(String).Name)); @@ -69,7 +68,7 @@ private static void AccessMember(Type entityType, string memberToAccess) new ServiceQueryPart("where", String.Format("it.{0} == \"Whatever\"", memberToAccess)) }; - ExceptionHelper.ExpectInvalidOperationException(delegate + Assert.ThrowsException(() => { QueryDeserializer.Deserialize(DomainServiceDescription.GetDescription(typeof(QueryDeserializerDomainService)), queryable, queryParts); }, string.Format(CultureInfo.CurrentCulture, System.Linq.Dynamic.Resource.UnknownPropertyOrField, memberToAccess, entityType.Name)); diff --git a/src/Test/WebsiteFullTrust/Web.config b/src/Test/WebsiteFullTrust/Web.config index 0205fe8ee..3ce7c9e17 100644 --- a/src/Test/WebsiteFullTrust/Web.config +++ b/src/Test/WebsiteFullTrust/Web.config @@ -78,16 +78,20 @@ - + - + + + + + diff --git a/src/Test/WebsiteFullTrust/WebsiteFullTrust.csproj b/src/Test/WebsiteFullTrust/WebsiteFullTrust.csproj index ec5b293a9..8cd153988 100644 --- a/src/Test/WebsiteFullTrust/WebsiteFullTrust.csproj +++ b/src/Test/WebsiteFullTrust/WebsiteFullTrust.csproj @@ -149,6 +149,9 @@ 8.0.0 + + 8.0.0 + 4.5.4