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