1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Collections.Generic; 6 using System.Collections.ObjectModel; 7 using System.Diagnostics; 8 using System.Linq.Expressions; 9 using System.Reflection; 10 using System.Runtime.CompilerServices; 11 using System.Threading; 12 13 namespace System.Dynamic.Utils 14 { 15 internal static class ExpressionUtils 16 { 17 /// <summary> 18 /// See overload with <see cref="IArgumentProvider"/> for more information. 19 /// </summary> ReturnReadOnly(IParameterProvider provider, ref object collection)20 public static ReadOnlyCollection<ParameterExpression> ReturnReadOnly(IParameterProvider provider, ref object collection) 21 { 22 ParameterExpression tObj = collection as ParameterExpression; 23 if (tObj != null) 24 { 25 // otherwise make sure only one read-only collection ever gets exposed 26 Interlocked.CompareExchange( 27 ref collection, 28 new ReadOnlyCollection<ParameterExpression>(new ListParameterProvider(provider, tObj)), 29 tObj 30 ); 31 } 32 33 // and return what is not guaranteed to be a read-only collection 34 return (ReadOnlyCollection<ParameterExpression>)collection; 35 } 36 ReturnReadOnly(ref IReadOnlyList<T> collection)37 public static ReadOnlyCollection<T> ReturnReadOnly<T>(ref IReadOnlyList<T> collection) 38 { 39 IReadOnlyList<T> value = collection; 40 41 // if it's already read-only just return it. 42 ReadOnlyCollection<T> res = value as ReadOnlyCollection<T>; 43 if (res != null) 44 { 45 return res; 46 } 47 48 // otherwise make sure only read-only collection every gets exposed 49 Interlocked.CompareExchange(ref collection, value.ToReadOnly(), value); 50 51 // and return it 52 return (ReadOnlyCollection<T>)collection; 53 } 54 55 /// <summary> 56 /// Helper used for ensuring we only return 1 instance of a ReadOnlyCollection of T. 57 /// 58 /// This is similar to the ReturnReadOnly of T. This version supports nodes which hold 59 /// onto multiple Expressions where one is typed to object. That object field holds either 60 /// an expression or a ReadOnlyCollection of Expressions. When it holds a ReadOnlyCollection 61 /// the IList which backs it is a ListArgumentProvider which uses the Expression which 62 /// implements IArgumentProvider to get 2nd and additional values. The ListArgumentProvider 63 /// continues to hold onto the 1st expression. 64 /// 65 /// This enables users to get the ReadOnlyCollection w/o it consuming more memory than if 66 /// it was just an array. Meanwhile The DLR internally avoids accessing which would force 67 /// the read-only collection to be created resulting in a typical memory savings. 68 /// </summary> ReturnReadOnly(IArgumentProvider provider, ref object collection)69 public static ReadOnlyCollection<Expression> ReturnReadOnly(IArgumentProvider provider, ref object collection) 70 { 71 Expression tObj = collection as Expression; 72 if (tObj != null) 73 { 74 // otherwise make sure only one read-only collection ever gets exposed 75 Interlocked.CompareExchange( 76 ref collection, 77 new ReadOnlyCollection<Expression>(new ListArgumentProvider(provider, tObj)), 78 tObj 79 ); 80 } 81 82 // and return what is not guaranteed to be a read-only collection 83 return (ReadOnlyCollection<Expression>)collection; 84 } 85 86 /// <summary> 87 /// Helper which is used for specialized subtypes which use ReturnReadOnly(ref object, ...). 88 /// This is the reverse version of ReturnReadOnly which takes an IArgumentProvider. 89 /// 90 /// This is used to return the 1st argument. The 1st argument is typed as object and either 91 /// contains a ReadOnlyCollection or the Expression. We check for the Expression and if it's 92 /// present we return that, otherwise we return the 1st element of the ReadOnlyCollection. 93 /// </summary> 94 public static T ReturnObject<T>(object collectionOrT) where T : class 95 { 96 T t = collectionOrT as T; 97 if (t != null) 98 { 99 return t; 100 } 101 102 return ((ReadOnlyCollection<T>)collectionOrT)[0]; 103 } 104 ValidateArgumentTypes(MethodBase method, ExpressionType nodeKind, ref ReadOnlyCollection<Expression> arguments, string methodParamName)105 public static void ValidateArgumentTypes(MethodBase method, ExpressionType nodeKind, ref ReadOnlyCollection<Expression> arguments, string methodParamName) 106 { 107 Debug.Assert(nodeKind == ExpressionType.Invoke || nodeKind == ExpressionType.Call || nodeKind == ExpressionType.Dynamic || nodeKind == ExpressionType.New); 108 109 ParameterInfo[] pis = GetParametersForValidation(method, nodeKind); 110 111 ValidateArgumentCount(method, nodeKind, arguments.Count, pis); 112 113 Expression[] newArgs = null; 114 for (int i = 0, n = pis.Length; i < n; i++) 115 { 116 Expression arg = arguments[i]; 117 ParameterInfo pi = pis[i]; 118 arg = ValidateOneArgument(method, nodeKind, arg, pi, methodParamName, nameof(arguments), i); 119 120 if (newArgs == null && arg != arguments[i]) 121 { 122 newArgs = new Expression[arguments.Count]; 123 for (int j = 0; j < i; j++) 124 { 125 newArgs[j] = arguments[j]; 126 } 127 } 128 if (newArgs != null) 129 { 130 newArgs[i] = arg; 131 } 132 } 133 if (newArgs != null) 134 { 135 arguments = new TrueReadOnlyCollection<Expression>(newArgs); 136 } 137 } 138 ValidateArgumentCount(MethodBase method, ExpressionType nodeKind, int count, ParameterInfo[] pis)139 public static void ValidateArgumentCount(MethodBase method, ExpressionType nodeKind, int count, ParameterInfo[] pis) 140 { 141 if (pis.Length != count) 142 { 143 // Throw the right error for the node we were given 144 switch (nodeKind) 145 { 146 case ExpressionType.New: 147 throw Error.IncorrectNumberOfConstructorArguments(); 148 case ExpressionType.Invoke: 149 throw Error.IncorrectNumberOfLambdaArguments(); 150 case ExpressionType.Dynamic: 151 case ExpressionType.Call: 152 throw Error.IncorrectNumberOfMethodCallArguments(method, nameof(method)); 153 default: 154 throw ContractUtils.Unreachable; 155 } 156 } 157 } 158 ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, string methodParamName, string argumentParamName, int index = -1)159 public static Expression ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, string methodParamName, string argumentParamName, int index = -1) 160 { 161 RequiresCanRead(arguments, argumentParamName, index); 162 Type pType = pi.ParameterType; 163 if (pType.IsByRef) 164 { 165 pType = pType.GetElementType(); 166 } 167 168 TypeUtils.ValidateType(pType, methodParamName, allowByRef: true, allowPointer: true); 169 if (!TypeUtils.AreReferenceAssignable(pType, arguments.Type)) 170 { 171 if (!TryQuote(pType, ref arguments)) 172 { 173 // Throw the right error for the node we were given 174 switch (nodeKind) 175 { 176 case ExpressionType.New: 177 throw Error.ExpressionTypeDoesNotMatchConstructorParameter(arguments.Type, pType, argumentParamName, index); 178 case ExpressionType.Invoke: 179 throw Error.ExpressionTypeDoesNotMatchParameter(arguments.Type, pType, argumentParamName, index); 180 case ExpressionType.Dynamic: 181 case ExpressionType.Call: 182 throw Error.ExpressionTypeDoesNotMatchMethodParameter(arguments.Type, pType, method, argumentParamName, index); 183 default: 184 throw ContractUtils.Unreachable; 185 } 186 } 187 } 188 return arguments; 189 } 190 RequiresCanRead(Expression expression, string paramName)191 public static void RequiresCanRead(Expression expression, string paramName) 192 { 193 RequiresCanRead(expression, paramName, -1); 194 } 195 RequiresCanRead(Expression expression, string paramName, int idx)196 public static void RequiresCanRead(Expression expression, string paramName, int idx) 197 { 198 ContractUtils.RequiresNotNull(expression, paramName, idx); 199 200 // validate that we can read the node 201 switch (expression.NodeType) 202 { 203 case ExpressionType.Index: 204 IndexExpression index = (IndexExpression)expression; 205 if (index.Indexer != null && !index.Indexer.CanRead) 206 { 207 throw Error.ExpressionMustBeReadable(paramName, idx); 208 } 209 break; 210 case ExpressionType.MemberAccess: 211 MemberExpression member = (MemberExpression)expression; 212 PropertyInfo prop = member.Member as PropertyInfo; 213 if (prop != null) 214 { 215 if (!prop.CanRead) 216 { 217 throw Error.ExpressionMustBeReadable(paramName, idx); 218 } 219 } 220 break; 221 } 222 } 223 224 // Attempts to auto-quote the expression tree. Returns true if it succeeded, false otherwise. TryQuote(Type parameterType, ref Expression argument)225 public static bool TryQuote(Type parameterType, ref Expression argument) 226 { 227 // We used to allow quoting of any expression, but the behavior of 228 // quote (produce a new tree closed over parameter values), only 229 // works consistently for lambdas 230 Type quoteable = typeof(LambdaExpression); 231 232 if (TypeUtils.IsSameOrSubclass(quoteable, parameterType) && parameterType.IsInstanceOfType(argument)) 233 { 234 argument = Expression.Quote(argument); 235 return true; 236 } 237 238 return false; 239 } 240 GetParametersForValidation(MethodBase method, ExpressionType nodeKind)241 internal static ParameterInfo[] GetParametersForValidation(MethodBase method, ExpressionType nodeKind) 242 { 243 ParameterInfo[] pis = method.GetParametersCached(); 244 245 if (nodeKind == ExpressionType.Dynamic) 246 { 247 pis = pis.RemoveFirst(); // ignore CallSite argument 248 } 249 return pis; 250 } 251 252 internal static bool SameElements<T>(ICollection<T> replacement, IReadOnlyList<T> current) where T : class 253 { 254 Debug.Assert(current != null); 255 if (replacement == current) // Relatively common case, so particularly useful to take the short-circuit. 256 { 257 return true; 258 } 259 260 if (replacement == null) // Treat null as empty. 261 { 262 return current.Count == 0; 263 } 264 265 return SameElementsInCollection(replacement, current); 266 } 267 268 internal static bool SameElements<T>(ref IEnumerable<T> replacement, IReadOnlyList<T> current) where T : class 269 { 270 Debug.Assert(current != null); 271 if (replacement == current) // Relatively common case, so particularly useful to take the short-circuit. 272 { 273 return true; 274 } 275 276 if (replacement == null) // Treat null as empty. 277 { 278 return current.Count == 0; 279 } 280 281 // Ensure arguments is safe to enumerate twice. 282 // If we have to build a collection, build a TrueReadOnlyCollection<T> 283 // so it won't be built a second time if used. 284 ICollection<T> replacementCol = replacement as ICollection<T>; 285 if (replacementCol == null) 286 { 287 replacement = replacementCol = replacement.ToReadOnly(); 288 } 289 290 return SameElementsInCollection(replacementCol, current); 291 } 292 293 private static bool SameElementsInCollection<T>(ICollection<T> replacement, IReadOnlyList<T> current) where T : class 294 { 295 int count = current.Count; 296 if (replacement.Count != count) 297 { 298 return false; 299 } 300 301 if (count != 0) 302 { 303 int index = 0; 304 foreach (T replacementObject in replacement) 305 { 306 if (replacementObject != current[index]) 307 { 308 return false; 309 } 310 311 index++; 312 } 313 } 314 315 return true; 316 } 317 ValidateArgumentCount(this LambdaExpression lambda)318 public static void ValidateArgumentCount(this LambdaExpression lambda) 319 { 320 if (((IParameterProvider)lambda).ParameterCount >= ushort.MaxValue) 321 { 322 throw Error.InvalidProgram(); 323 } 324 } 325 } 326 } 327