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.Diagnostics;
6 using System.Dynamic.Utils;
7 using System.Reflection;
8 using System.Reflection.Emit;
9 
10 namespace System.Linq.Expressions.Compiler
11 {
12     internal partial class LambdaCompiler
13     {
EmitAddress(Expression node, Type type)14         private void EmitAddress(Expression node, Type type)
15         {
16             EmitAddress(node, type, CompilationFlags.EmitExpressionStart);
17         }
18 
19         // We don't want "ref" parameters to modify values of expressions
20         // except where it would in IL: locals, args, fields, and array elements
21         // (Unbox is an exception, it's intended to emit a ref to the original
22         // boxed value)
EmitAddress(Expression node, Type type, CompilationFlags flags)23         private void EmitAddress(Expression node, Type type, CompilationFlags flags)
24         {
25             Debug.Assert(node != null);
26             bool emitStart = (flags & CompilationFlags.EmitExpressionStartMask) == CompilationFlags.EmitExpressionStart;
27             CompilationFlags startEmitted = emitStart ? EmitExpressionStart(node) : CompilationFlags.EmitNoExpressionStart;
28 
29             switch (node.NodeType)
30             {
31                 default:
32                     EmitExpressionAddress(node, type);
33                     break;
34 
35                 case ExpressionType.ArrayIndex:
36                     AddressOf((BinaryExpression)node, type);
37                     break;
38 
39                 case ExpressionType.Parameter:
40                     AddressOf((ParameterExpression)node, type);
41                     break;
42 
43                 case ExpressionType.MemberAccess:
44                     AddressOf((MemberExpression)node, type);
45                     break;
46 
47                 case ExpressionType.Unbox:
48                     AddressOf((UnaryExpression)node, type);
49                     break;
50 
51                 case ExpressionType.Call:
52                     AddressOf((MethodCallExpression)node, type);
53                     break;
54 
55                 case ExpressionType.Index:
56                     AddressOf((IndexExpression)node, type);
57                     break;
58             }
59 
60             if (emitStart)
61             {
62                 EmitExpressionEnd(startEmitted);
63             }
64         }
65 
66 
AddressOf(BinaryExpression node, Type type)67         private void AddressOf(BinaryExpression node, Type type)
68         {
69             Debug.Assert(node.NodeType == ExpressionType.ArrayIndex && node.Method == null);
70 
71             if (TypeUtils.AreEquivalent(type, node.Type))
72             {
73                 EmitExpression(node.Left);
74                 EmitExpression(node.Right);
75                 Debug.Assert(node.Right.Type == typeof(int));
76                 _ilg.Emit(OpCodes.Ldelema, node.Type);
77             }
78             else
79             {
80                 EmitExpressionAddress(node, type);
81             }
82         }
83 
AddressOf(ParameterExpression node, Type type)84         private void AddressOf(ParameterExpression node, Type type)
85         {
86             if (TypeUtils.AreEquivalent(type, node.Type))
87             {
88                 if (node.IsByRef)
89                 {
90                     _scope.EmitGet(node);
91                 }
92                 else
93                 {
94                     _scope.EmitAddressOf(node);
95                 }
96             }
97             else
98             {
99                 // NB: Stack spilling can generate ref locals.
100 
101                 if (node.Type.IsByRef && node.Type.GetElementType() == type)
102                 {
103                     EmitExpression(node);
104                 }
105                 else
106                 {
107                     EmitExpressionAddress(node, type);
108                 }
109             }
110         }
111 
112 
AddressOf(MemberExpression node, Type type)113         private void AddressOf(MemberExpression node, Type type)
114         {
115             if (TypeUtils.AreEquivalent(type, node.Type))
116             {
117                 // emit "this", if any
118                 Type objectType = null;
119                 if (node.Expression != null)
120                 {
121                     EmitInstance(node.Expression, out objectType);
122                 }
123                 EmitMemberAddress(node.Member, objectType);
124             }
125             else
126             {
127                 EmitExpressionAddress(node, type);
128             }
129         }
130 
131         // assumes the instance is already on the stack
EmitMemberAddress(MemberInfo member, Type objectType)132         private void EmitMemberAddress(MemberInfo member, Type objectType)
133         {
134             FieldInfo field = member as FieldInfo;
135             if ((object)field != null)
136             {
137                 // Verifiable code may not take the address of an init-only field.
138                 // If we are asked to do so then get the value out of the field, stuff it
139                 // into a local of the same type, and then take the address of the local.
140                 // Typically this is what we want to do anyway; if we are saying
141                 // Foo.bar.ToString() for a static value-typed field bar then we don't need
142                 // the address of field bar to do the call.  The address of a local which
143                 // has the same value as bar is sufficient.
144 
145                 // The C# compiler will not compile a lambda expression tree
146                 // which writes to the address of an init-only field. But one could
147                 // probably use the expression tree API to build such an expression.
148                 // (When compiled, such an expression would fail silently.)  It might
149                 // be worth it to add checking to the expression tree API to ensure
150                 // that it is illegal to attempt to write to an init-only field,
151                 // the same way that it is illegal to write to a read-only property.
152                 // The same goes for literal fields.
153                 if (!field.IsLiteral && !field.IsInitOnly)
154                 {
155                     _ilg.EmitFieldAddress(field);
156                     return;
157                 }
158             }
159 
160             EmitMemberGet(member, objectType);
161             LocalBuilder temp = GetLocal(GetMemberType(member));
162             _ilg.Emit(OpCodes.Stloc, temp);
163             _ilg.Emit(OpCodes.Ldloca, temp);
164         }
165 
166 
AddressOf(MethodCallExpression node, Type type)167         private void AddressOf(MethodCallExpression node, Type type)
168         {
169             // An array index of a multi-dimensional array is represented by a call to Array.Get,
170             // rather than having its own array-access node. This means that when we are trying to
171             // get the address of a member of a multi-dimensional array, we'll be trying to
172             // get the address of a Get method, and it will fail to do so. Instead, detect
173             // this situation and replace it with a call to the Address method.
174             if (!node.Method.IsStatic &&
175                 node.Object.Type.IsArray &&
176                 node.Method == node.Object.Type.GetMethod("Get", BindingFlags.Public | BindingFlags.Instance))
177             {
178                 MethodInfo mi = node.Object.Type.GetMethod("Address", BindingFlags.Public | BindingFlags.Instance);
179 
180                 EmitMethodCall(node.Object, mi, node);
181             }
182             else
183             {
184                 EmitExpressionAddress(node, type);
185             }
186         }
187 
AddressOf(IndexExpression node, Type type)188         private void AddressOf(IndexExpression node, Type type)
189         {
190             if (!TypeUtils.AreEquivalent(type, node.Type) || node.Indexer != null)
191             {
192                 EmitExpressionAddress(node, type);
193                 return;
194             }
195 
196             if (node.ArgumentCount == 1)
197             {
198                 EmitExpression(node.Object);
199                 EmitExpression(node.GetArgument(0));
200                 _ilg.Emit(OpCodes.Ldelema, node.Type);
201             }
202             else
203             {
204                 MethodInfo address = node.Object.Type.GetMethod("Address", BindingFlags.Public | BindingFlags.Instance);
205                 EmitMethodCall(node.Object, address, node);
206             }
207         }
208 
AddressOf(UnaryExpression node, Type type)209         private void AddressOf(UnaryExpression node, Type type)
210         {
211             Debug.Assert(node.NodeType == ExpressionType.Unbox);
212             Debug.Assert(type.IsValueType);
213 
214             // Unbox leaves a pointer to the boxed value on the stack
215             EmitExpression(node.Operand);
216             _ilg.Emit(OpCodes.Unbox, type);
217         }
218 
EmitExpressionAddress(Expression node, Type type)219         private void EmitExpressionAddress(Expression node, Type type)
220         {
221             Debug.Assert(TypeUtils.AreReferenceAssignable(type, node.Type));
222 
223             EmitExpression(node, CompilationFlags.EmitAsNoTail | CompilationFlags.EmitNoExpressionStart);
224             LocalBuilder tmp = GetLocal(type);
225             _ilg.Emit(OpCodes.Stloc, tmp);
226             _ilg.Emit(OpCodes.Ldloca, tmp);
227         }
228 
229 
230         // Emits the address of the expression, returning the write back if necessary
231         //
232         // For properties, we want to write back into the property if it's
233         // passed byref.
EmitAddressWriteBack(Expression node, Type type)234         private WriteBack EmitAddressWriteBack(Expression node, Type type)
235         {
236             CompilationFlags startEmitted = EmitExpressionStart(node);
237 
238             WriteBack result = null;
239             if (TypeUtils.AreEquivalent(type, node.Type))
240             {
241                 switch (node.NodeType)
242                 {
243                     case ExpressionType.MemberAccess:
244                         result = AddressOfWriteBack((MemberExpression)node);
245                         break;
246                     case ExpressionType.Index:
247                         result = AddressOfWriteBack((IndexExpression)node);
248                         break;
249                 }
250             }
251             if (result == null)
252             {
253                 EmitAddress(node, type, CompilationFlags.EmitAsNoTail | CompilationFlags.EmitNoExpressionStart);
254             }
255 
256             EmitExpressionEnd(startEmitted);
257 
258             return result;
259         }
260 
AddressOfWriteBack(MemberExpression node)261         private WriteBack AddressOfWriteBack(MemberExpression node)
262         {
263             var property = node.Member as PropertyInfo;
264             if ((object)property == null || !property.CanWrite)
265             {
266                 return null;
267             }
268 
269             return AddressOfWriteBackCore(node); // avoids closure allocation
270         }
271 
AddressOfWriteBackCore(MemberExpression node)272         private WriteBack AddressOfWriteBackCore(MemberExpression node)
273         {
274             // emit instance, if any
275             LocalBuilder instanceLocal = null;
276             Type instanceType = null;
277             if (node.Expression != null)
278             {
279                 EmitInstance(node.Expression, out instanceType);
280 
281                 // store in local
282                 _ilg.Emit(OpCodes.Dup);
283                 _ilg.Emit(OpCodes.Stloc, instanceLocal = GetInstanceLocal(instanceType));
284             }
285 
286             PropertyInfo pi = (PropertyInfo)node.Member;
287 
288             // emit the get
289             EmitCall(instanceType, pi.GetGetMethod(nonPublic: true));
290 
291             // emit the address of the value
292             LocalBuilder valueLocal = GetLocal(node.Type);
293             _ilg.Emit(OpCodes.Stloc, valueLocal);
294             _ilg.Emit(OpCodes.Ldloca, valueLocal);
295 
296             // Set the property after the method call
297             // don't re-evaluate anything
298             return @this =>
299             {
300                 if (instanceLocal != null)
301                 {
302                     @this._ilg.Emit(OpCodes.Ldloc, instanceLocal);
303                     @this.FreeLocal(instanceLocal);
304                 }
305                 @this._ilg.Emit(OpCodes.Ldloc, valueLocal);
306                 @this.FreeLocal(valueLocal);
307                 @this.EmitCall(instanceLocal?.LocalType, pi.GetSetMethod(nonPublic: true));
308             };
309         }
310 
AddressOfWriteBack(IndexExpression node)311         private WriteBack AddressOfWriteBack(IndexExpression node)
312         {
313             if (node.Indexer == null || !node.Indexer.CanWrite)
314             {
315                 return null;
316             }
317 
318             return AddressOfWriteBackCore(node); // avoids closure allocation
319         }
320 
AddressOfWriteBackCore(IndexExpression node)321         private WriteBack AddressOfWriteBackCore(IndexExpression node)
322         {
323             // emit instance, if any
324             LocalBuilder instanceLocal = null;
325             Type instanceType = null;
326             if (node.Object != null)
327             {
328                 EmitInstance(node.Object, out instanceType);
329 
330                 // store in local
331                 _ilg.Emit(OpCodes.Dup);
332                 _ilg.Emit(OpCodes.Stloc, instanceLocal = GetInstanceLocal(instanceType));
333             }
334 
335             // Emit indexes. We don't allow byref args, so no need to worry
336             // about write-backs or EmitAddress
337             int n = node.ArgumentCount;
338             var args = new LocalBuilder[n];
339             for (var i = 0; i < n; i++)
340             {
341                 Expression arg = node.GetArgument(i);
342                 EmitExpression(arg);
343 
344                 LocalBuilder argLocal = GetLocal(arg.Type);
345                 _ilg.Emit(OpCodes.Dup);
346                 _ilg.Emit(OpCodes.Stloc, argLocal);
347                 args[i] = argLocal;
348             }
349 
350             // emit the get
351             EmitGetIndexCall(node, instanceType);
352 
353             // emit the address of the value
354             LocalBuilder valueLocal = GetLocal(node.Type);
355             _ilg.Emit(OpCodes.Stloc, valueLocal);
356             _ilg.Emit(OpCodes.Ldloca, valueLocal);
357 
358             // Set the property after the method call
359             // don't re-evaluate anything
360             return @this =>
361             {
362                 if (instanceLocal != null)
363                 {
364                     @this._ilg.Emit(OpCodes.Ldloc, instanceLocal);
365                     @this.FreeLocal(instanceLocal);
366                 }
367                 foreach (LocalBuilder arg in args)
368                 {
369                     @this._ilg.Emit(OpCodes.Ldloc, arg);
370                     @this.FreeLocal(arg);
371                 }
372                 @this._ilg.Emit(OpCodes.Ldloc, valueLocal);
373                 @this.FreeLocal(valueLocal);
374 
375                 @this.EmitSetIndexCall(node, instanceLocal?.LocalType);
376             };
377         }
378 
GetInstanceLocal(Type type)379         private LocalBuilder GetInstanceLocal(Type type)
380         {
381             Type instanceLocalType = type.IsValueType ? type.MakeByRefType() : type;
382             return GetLocal(instanceLocalType);
383         }
384     }
385 }
386