PageRenderTime 29ms CodeModel.GetById 11ms app.highlight 13ms RepoModel.GetById 2ms app.codeStats 0ms

/src/LinFu.Reflection.Emit/CilWorkerExtensions.cs

http://github.com/philiplaureano/LinFu
C# | 363 lines | 203 code | 47 blank | 113 comment | 14 complexity | a0a94936879ce93cc79a130c99709ba4 MD5 | raw file
  1using System;
  2using System.Collections.Generic;
  3using System.Diagnostics;
  4using System.Reflection;
  5using Mono.Cecil;
  6using Mono.Cecil.Cil;
  7
  8namespace LinFu.Reflection.Emit
  9{
 10    /// <summary>
 11    ///     A class that extends the <see cref="ILProcessor" /> class
 12    ///     with helper methods that make it easier to save
 13    ///     information about the method currently being implemented.
 14    /// </summary>
 15    public static class ILProcessorExtensions
 16    {
 17        private static readonly Dictionary<string, OpCode> stindMap = new Dictionary<string, OpCode>();
 18
 19        static ILProcessorExtensions()
 20        {
 21            stindMap["Bool&"] = OpCodes.Stind_I1;
 22            stindMap["Int8&"] = OpCodes.Stind_I1;
 23            stindMap["Uint8&"] = OpCodes.Stind_I1;
 24
 25            stindMap["Int16&"] = OpCodes.Stind_I2;
 26            stindMap["Uint16&"] = OpCodes.Stind_I2;
 27
 28            stindMap["Uint32&"] = OpCodes.Stind_I4;
 29            stindMap["Int32&"] = OpCodes.Stind_I4;
 30
 31            stindMap["IntPtr"] = OpCodes.Stind_I4;
 32            stindMap["Uint64&"] = OpCodes.Stind_I8;
 33            stindMap["Int64&"] = OpCodes.Stind_I8;
 34            stindMap["Float32&"] = OpCodes.Stind_R4;
 35            stindMap["Float64&"] = OpCodes.Stind_R8;
 36        }
 37
 38        /// <summary>
 39        ///     Emits a Console.WriteLine call to using the current ILProcessor that will only be called if the contents
 40        ///     of the target variable are null at runtime.
 41        /// </summary>
 42        /// <param name="IL">The target ILProcessor.</param>
 43        /// <param name="text">The text that will be written to the console.</param>
 44        /// <param name="targetVariable">The target variable that will be checked for null at runtime.</param>
 45        public static void EmitWriteLineIfNull(this ILProcessor IL, string text, VariableDefinition targetVariable)
 46        {
 47            var skipWrite = IL.Create(OpCodes.Nop);
 48            IL.Emit(OpCodes.Ldloc, targetVariable);
 49            IL.Emit(OpCodes.Brtrue, skipWrite);
 50            IL.EmitWriteLine(text);
 51            IL.Append(skipWrite);
 52        }
 53
 54        /// <summary>
 55        ///     Emits a Console.WriteLine call using the current ILProcessor.
 56        /// </summary>
 57        /// <param name="IL">The target ILProcessor.</param>
 58        /// <param name="text">The text that will be written to the console.</param>
 59        public static void EmitWriteLine(this ILProcessor IL, string text)
 60        {
 61            var body = IL.GetBody();
 62            var method = body.Method;
 63            var declaringType = method.DeclaringType;
 64            var module = declaringType.Module;
 65
 66            var writeLineMethod = typeof(Console).GetMethod("WriteLine",
 67                BindingFlags.Public | BindingFlags.Static, null,
 68                new[] {typeof(string)}, null);
 69            IL.Emit(OpCodes.Ldstr, text);
 70            IL.Emit(OpCodes.Call, module.Import(writeLineMethod));
 71        }
 72
 73        /// <summary>
 74        ///     Pushes the current <paramref name="method" /> onto the stack.
 75        /// </summary>
 76        /// <param name="IL">The <see cref="ILProcessor" /> that will be used to create the instructions.</param>
 77        /// <param name="method">The method that represents the <see cref="MethodInfo" /> that will be pushed onto the stack.</param>
 78        /// <param name="module">The module that contains the host method.</param>
 79        public static void PushMethod(this ILProcessor IL, MethodReference method, ModuleDefinition module)
 80        {
 81            var getMethodFromHandle = module.ImportMethod<MethodBase>("GetMethodFromHandle",
 82                typeof(RuntimeMethodHandle),
 83                typeof(RuntimeTypeHandle));
 84
 85            var declaringType = method.DeclaringType;
 86
 87            // Instantiate the generic type before determining
 88            // the current method
 89            if (declaringType.GenericParameters.Count > 0)
 90            {
 91                var genericType = new GenericInstanceType(declaringType);
 92                foreach (GenericParameter parameter in declaringType.GenericParameters)
 93                    genericType.GenericArguments.Add(parameter);
 94
 95                declaringType = genericType;
 96            }
 97
 98
 99            IL.Emit(OpCodes.Ldtoken, method);
100            IL.Emit(OpCodes.Ldtoken, declaringType);
101            IL.Emit(OpCodes.Call, getMethodFromHandle);
102        }
103
104        /// <summary>
105        ///     Gets the declaring type for the target method.
106        /// </summary>
107        /// <param name="method">The target method.</param>
108        /// <returns>The declaring type.</returns>
109        public static TypeReference GetDeclaringType(this MethodReference method)
110        {
111            var declaringType = method.DeclaringType;
112            return GetDeclaringType(declaringType);
113        }
114
115        /// <summary>
116        ///     Pushes a <paramref name="Type" /> instance onto the stack.
117        /// </summary>
118        /// <param name="IL">The <see cref="ILProcessor" /> that will be used to create the instructions.</param>
119        /// <param name="type">The type that represents the <see cref="Type" /> that will be pushed onto the stack.</param>
120        /// <param name="module">The module that contains the host method.</param>
121        public static void PushType(this ILProcessor IL, TypeReference type, ModuleDefinition module)
122        {
123            var getTypeFromHandle = module.ImportMethod<Type>("GetTypeFromHandle",
124                typeof(RuntimeTypeHandle));
125
126            // Instantiate the generic type before pushing it onto the stack
127            var declaringType = GetDeclaringType(type);
128
129            IL.Emit(OpCodes.Ldtoken, declaringType);
130            IL.Emit(OpCodes.Call, getTypeFromHandle);
131        }
132
133        /// <summary>
134        ///     Pushes the current <paramref name="field" /> onto the stack.
135        /// </summary>
136        /// <param name="IL">The <see cref="ILProcessor" /> that will be used to create the instructions.</param>
137        /// <param name="field">The field that represents the <see cref="FieldInfo" /> that will be pushed onto the stack.</param>
138        /// <param name="module">The module that contains the target field.</param>
139        public static void PushField(this ILProcessor IL, FieldReference field, ModuleDefinition module)
140        {
141            var getFieldFromHandle = module.ImportMethod<FieldInfo>("GetFieldFromHandle",
142                typeof(RuntimeFieldHandle),
143                typeof(RuntimeTypeHandle));
144
145            var declaringType = GetDeclaringType(field.DeclaringType);
146
147            IL.Emit(OpCodes.Ldtoken, field);
148            IL.Emit(OpCodes.Ldtoken, declaringType);
149            IL.Emit(OpCodes.Call, getFieldFromHandle);
150        }
151
152        /// <summary>
153        ///     Pushes the arguments of a method onto the stack.
154        /// </summary>
155        /// <param name="IL">The <see cref="ILProcessor" /> that will be used to create the instructions.</param>
156        /// <param name="module">The module that contains the host method.</param>
157        /// <param name="method">The target method.</param>
158        /// <param name="arguments">The <see cref="VariableDefinition">local variable</see> that will hold the array of arguments.</param>
159        public static void PushArguments(this ILProcessor IL, IMethodSignature method, ModuleDefinition module,
160            VariableDefinition arguments)
161        {
162            var objectType = module.ImportType(typeof(object));
163            var parameterCount = method.Parameters.Count;
164            IL.Emit(OpCodes.Ldc_I4, parameterCount);
165            IL.Emit(OpCodes.Newarr, objectType);
166            IL.Emit(OpCodes.Stloc, arguments);
167
168            if (parameterCount == 0)
169                return;
170
171            var index = 0;
172            foreach (ParameterDefinition param in method.Parameters) IL.PushParameter(index++, arguments, param);
173        }
174
175
176        /// <summary>
177        ///     Pushes the stack trace of the currently executing method onto the stack.
178        /// </summary>
179        /// <param name="IL">The <see cref="ILProcessor" /> that will be used to create the instructions.</param>
180        /// <param name="module">The module that contains the host method.</param>
181        public static void PushStackTrace(this ILProcessor IL, ModuleDefinition module)
182        {
183            var stackTraceConstructor =
184                typeof(StackTrace).GetConstructor(new[] {typeof(int), typeof(bool)});
185            var stackTraceCtor = module.Import(stackTraceConstructor);
186
187            var addDebugSymbols = OpCodes.Ldc_I4_0;
188            IL.Emit(OpCodes.Ldc_I4_1);
189            IL.Emit(addDebugSymbols);
190            IL.Emit(OpCodes.Newobj, stackTraceCtor);
191        }
192
193        /// <summary>
194        ///     Saves the generic type arguments that were used to construct the method.
195        /// </summary>
196        /// <param name="IL">The <see cref="ILProcessor" /> that will be used to create the instructions.</param>
197        /// <param name="method">
198        ///     The target method whose generic type arguments (if any) will be saved into the
199        ///     <paramref name="typeArguments">local variable</paramref>.
200        /// </param>
201        /// <param name="module">The module that contains the host method.</param>
202        /// <param name="typeArguments">The local variable that will store the resulting array of <see cref="Type" /> objects.</param>
203        public static void PushGenericArguments(this ILProcessor IL, IGenericParameterProvider method,
204            ModuleDefinition module, VariableDefinition typeArguments)
205        {
206            var getTypeFromHandle = module.ImportMethod<Type>("GetTypeFromHandle",
207                BindingFlags.Public | BindingFlags.Static);
208            var genericParameterCount = method.GenericParameters.Count;
209
210            var genericParameters = method.GenericParameters;
211            for (var index = 0; index < genericParameterCount; index++)
212            {
213                var current = genericParameters[index];
214
215                IL.Emit(OpCodes.Ldloc, typeArguments);
216                IL.Emit(OpCodes.Ldc_I4, index);
217                IL.Emit(OpCodes.Ldtoken, current);
218                IL.Emit(OpCodes.Call, getTypeFromHandle);
219                IL.Emit(OpCodes.Stelem_Ref);
220            }
221        }
222
223        /// <summary>
224        ///     Saves the current method signature of a method into an array
225        ///     of <see cref="System.Type" /> objects. This can be used to determine the
226        ///     signature of methods with generic type parameters or methods that have
227        ///     parameters that are generic parameters specified by the type itself.
228        /// </summary>
229        /// <param name="IL">The <see cref="ILProcessor" /> that will be used to create the instructions.</param>
230        /// <param name="method">The target method whose generic type arguments (if any) will be saved into the local variable .</param>
231        /// <param name="module">The module that contains the host method.</param>
232        /// <param name="parameterTypes">The local variable that will store the current method signature.</param>
233        public static void SaveParameterTypes(this ILProcessor IL, MethodReference method, ModuleDefinition module,
234            VariableDefinition parameterTypes)
235        {
236            var getTypeFromHandle = module.ImportMethod<Type>("GetTypeFromHandle",
237                BindingFlags.Public | BindingFlags.Static);
238            var parameterCount = method.Parameters.Count;
239            for (var index = 0; index < parameterCount; index++)
240            {
241                var current = method.Parameters[index];
242                IL.Emit(OpCodes.Ldloc, parameterTypes);
243                IL.Emit(OpCodes.Ldc_I4, index);
244                IL.Emit(OpCodes.Ldtoken, current.ParameterType);
245                IL.Emit(OpCodes.Call, getTypeFromHandle);
246                IL.Emit(OpCodes.Stelem_Ref);
247            }
248        }
249
250        /// <summary>
251        ///     Converts the return value of a method into the <paramref name="returnType">target type</paramref>.
252        ///     If the target type is void, then the value will simply be popped from the stack.
253        /// </summary>
254        /// <param name="IL">The <see cref="ILProcessor" /> that will be used to create the instructions.</param>
255        /// <param name="module">The module that contains the host method.</param>
256        /// <param name="returnType">The method return type itself.</param>
257        public static void PackageReturnValue(this ILProcessor IL, ModuleDefinition module, TypeReference returnType)
258        {
259            var voidType = module.ImportType(typeof(void));
260            if (returnType == voidType)
261            {
262                IL.Emit(OpCodes.Pop);
263                return;
264            }
265
266            IL.Emit(OpCodes.Unbox_Any, returnType);
267        }
268
269        /// <summary>
270        ///     Emits the proper Stind (store indirect) IL instruction for the <paramref name="currentType" />.
271        /// </summary>
272        /// <param name="IL">The target <see cref="ILProcessor" /> that will emit the IL.</param>
273        /// <param name="currentType">The type of data being stored.</param>
274        public static void Stind(this ILProcessor IL, TypeReference currentType)
275        {
276            var typeName = currentType.Name;
277            var opCode = OpCodes.Nop;
278            if (!currentType.IsValueType && !typeName.EndsWith("&"))
279                opCode = OpCodes.Stind_Ref;
280
281            opCode = !stindMap.ContainsKey(typeName) ? OpCodes.Stind_Ref : stindMap[typeName];
282
283            IL.Emit(opCode);
284        }
285
286        /// <summary>
287        ///     Stores the <paramref name="param">current parameter value</paramref>
288        ///     into the array of method <paramref name="arguments" />.
289        /// </summary>
290        /// <param name="IL">The <see cref="ILProcessor" /> that will be used to create the instructions.</param>
291        /// <param name="arguments">The local variable that will store the method arguments.</param>
292        /// <param name="index">The array index that indicates where the parameter value will be stored in the array of arguments.</param>
293        /// <param name="param">The current argument value being stored.</param>
294        private static void PushParameter(this ILProcessor IL, int index, VariableDefinition arguments,
295            ParameterDefinition param)
296        {
297            var parameterType = param.ParameterType;
298            IL.Emit(OpCodes.Ldloc, arguments);
299            IL.Emit(OpCodes.Ldc_I4, index);
300
301            // Zero out the [out] parameters
302            if (param.IsOut || param.IsByRef())
303            {
304                IL.Emit(OpCodes.Ldnull);
305                IL.Emit(OpCodes.Stelem_Ref);
306                return;
307            }
308
309            IL.Emit(OpCodes.Ldarg, param);
310
311            if (parameterType.IsValueType || parameterType is GenericParameter)
312                IL.Emit(OpCodes.Box, param.ParameterType);
313
314            IL.Emit(OpCodes.Stelem_Ref);
315        }
316
317        /// <summary>
318        ///     Obtains the method definition that contains the current <see cref="ILProcessor" />.
319        /// </summary>
320        /// <param name="IL">The <see cref="ILProcessor" /> responsible for the method body.</param>
321        /// <returns>A method definition.</returns>
322        public static MethodDefinition GetMethod(this ILProcessor IL)
323        {
324            var body = IL.GetBody();
325            var targetMethod = body.Method;
326
327            return targetMethod;
328        }
329
330        /// <summary>
331        ///     Obtains the module that contains the current <see cref="ILProcessor" />.
332        /// </summary>
333        /// <param name="IL">The <see cref="ILProcessor" /> responsible for the method body.</param>
334        /// <returns>The host module.</returns>
335        public static ModuleDefinition GetModule(this ILProcessor IL)
336        {
337            var method = IL.GetMethod();
338            var declaringType = method.DeclaringType;
339            return declaringType.Module;
340        }
341
342        /// <summary>
343        ///     Obtains the declaring type for a given type reference.
344        /// </summary>
345        /// <param name="declaringType">The declaring ty pe.</param>
346        /// <returns>The actual declaring type.</returns>
347        private static TypeReference GetDeclaringType(TypeReference declaringType)
348        {
349            // Instantiate the generic type before determining
350            // the current method
351            if (declaringType.GenericParameters.Count > 0)
352            {
353                var genericType = new GenericInstanceType(declaringType);
354                foreach (GenericParameter parameter in declaringType.GenericParameters)
355                    genericType.GenericArguments.Add(parameter);
356
357                declaringType = genericType;
358            }
359
360            return declaringType;
361        }
362    }
363}