Exemple #1
0
        /// <summary>
        /// Implements a <see cref="ISpecializationCacheArgs"/> interface in order to
        /// make the given <paramref name="typeBuilder"/> compatible with a
        /// <see cref="SpecializationCache{TLoader, TArgs, TDelegate}"/> instance.
        /// </summary>
        /// <param name="typeBuilder">The target type builder to use.</param>
        /// <param name="fields">The source fields used for implementation.</param>
        private static void ImplementSpecializationCacheArgs(
            TypeBuilder typeBuilder,
            FieldInfo[] fields)
        {
            var specializedType = typeof(SpecializedValue <>);
            var getArgMethod    = typeBuilder.DefineMethod(
                nameof(ISpecializationCacheArgs.GetSpecializedArg),
                MethodAttributes.Public | MethodAttributes.Virtual,
                typeof(object),
                new Type[] { typeof(int) });

            var emitter = new ILEmitter(getArgMethod.GetILGenerator());

            // Declare labels and emit jump table
            var labels = new ILLabel[fields.Length];

            for (int i = 0, e = fields.Length; i < e; ++i)
            {
                labels[i] = emitter.DeclareLabel();
            }
            emitter.Emit(OpCodes.Ldarg_1);
            emitter.EmitSwitch(labels);
            for (int i = 0, e = fields.Length; i < e; ++i)
            {
                var field = fields[i];
                emitter.MarkLabel(labels[i]);
                emitter.Emit(OpCodes.Ldarg_0);
                emitter.Emit(OpCodes.Ldfld, field);

                // Wrap in a specialized instance
                var fieldReturnType     = specializedType.MakeGenericType(field.FieldType);
                var instanceConstructor = fieldReturnType.GetConstructor(
                    new Type[]
                {
                    field.FieldType
                });
                emitter.EmitNewObject(instanceConstructor);

                emitter.Emit(OpCodes.Box, fieldReturnType);
                emitter.Emit(OpCodes.Ret);
            }

            // Return dummy argument
            emitter.Emit(OpCodes.Ldnull);
            emitter.Emit(OpCodes.Ret);
            emitter.Finish();

            typeBuilder.AddInterfaceImplementation(typeof(ISpecializationCacheArgs));
        }
Exemple #2
0
        private static ILEmitter.Label MakeReturnLabel(ILEmitter il)
        {
            // We replace all `ret`s with a simple branch to force potential execution of post-original code

            // Create a helper label as well
            // We mark the label as not emitted so that potential postfix code can mark it
            var resultLabel = il.DeclareLabel();

            resultLabel.emitted     = false;
            resultLabel.instruction = Instruction.Create(OpCodes.Ret);

            foreach (var ins in il.IL.Body.Instructions.Where(ins => ins.MatchRet()))
            {
                ins.OpCode  = OpCodes.Br;
                ins.Operand = resultLabel.instruction;
                resultLabel.targets.Add(ins);
            }

            // Already append `ret` for other code to use as emitBefore point
            il.IL.Append(resultLabel.instruction);
            return(resultLabel);
        }
Exemple #3
0
        private static void WriteFinalizers(ILEmitter il, MethodBase original, ILEmitter.Label returnLabel,
                                            Dictionary <string, VariableDefinition> variables,
                                            List <MethodInfo> finalizers)
        {
            // Finalizer layout:
            // Create __exception variable to store exception info and a skip flag
            // Wrap the whole method into a try/catch
            // Call finalizers at the end of method (simulate `finally`)
            // If __exception got set, throw it
            // Begin catch block
            // Store exception into __exception
            // If skip flag is set, skip finalizers
            // Call finalizers
            // If __exception is still set, rethrow (if new exception set, otherwise throw the new exception)
            // End catch block

            if (finalizers.Count == 0)
            {
                return;
            }

            Logger.Log(Logger.LogChannel.Info, () => "Writing finalizers");

            il.emitBefore = il.IL.Body.Instructions[il.IL.Body.Instructions.Count - 1];

            // Mark the original method return label here if it hasn't been yet
            il.MarkLabel(returnLabel);

            if (!variables.TryGetValue(RESULT_VAR, out var returnValueVar))
            {
                var retVal = AccessTools.GetReturnedType(original);
                returnValueVar = variables[RESULT_VAR] = retVal == typeof(void) ? null : il.DeclareVariable(retVal);
            }

            // Create variables to hold custom exception
            var skipFinalizersVar = il.DeclareVariable(typeof(bool));

            variables[EXCEPTION_VAR] = il.DeclareVariable(typeof(Exception));

            // Start main exception block
            var mainBlock = il.BeginExceptionBlock(il.DeclareLabelFor(il.IL.Body.Instructions[0]));

            bool WriteFinalizerCalls(bool suppressExceptions)
            {
                var canRethrow = true;

                foreach (var finalizer in finalizers)
                {
                    var start = il.DeclareLabel();
                    il.MarkLabel(start);

                    EmitCallParameter(il, original, finalizer, variables, false);
                    il.Emit(OpCodes.Call, finalizer);

                    if (finalizer.ReturnType != typeof(void))
                    {
                        il.Emit(OpCodes.Stloc, variables[EXCEPTION_VAR]);
                        canRethrow = false;
                    }

                    if (suppressExceptions)
                    {
                        var exBlock = il.BeginExceptionBlock(start);

                        il.BeginHandler(exBlock, ExceptionHandlerType.Catch, typeof(object));
                        il.Emit(OpCodes.Pop);
                        il.EndExceptionBlock(exBlock);
                    }
                }

                return(canRethrow);
            }

            // First, store potential result into a variable and empty the stack
            if (returnValueVar != null)
            {
                il.Emit(OpCodes.Stloc, returnValueVar);
            }

            // Write finalizers inside the `try`
            WriteFinalizerCalls(false);

            // Mark finalizers as skipped so they won't rerun
            il.Emit(OpCodes.Ldc_I4_1);
            il.Emit(OpCodes.Stloc, skipFinalizersVar);

            // If __exception is not null, throw
            var skipLabel = il.DeclareLabel();

            il.Emit(OpCodes.Ldloc, variables[EXCEPTION_VAR]);
            il.Emit(OpCodes.Brfalse, skipLabel);
            il.Emit(OpCodes.Ldloc, variables[EXCEPTION_VAR]);
            il.Emit(OpCodes.Throw);
            il.MarkLabel(skipLabel);

            // Begin a generic `catch(Exception o)` here and capture exception into __exception
            il.BeginHandler(mainBlock, ExceptionHandlerType.Catch, typeof(Exception));
            il.Emit(OpCodes.Stloc, variables[EXCEPTION_VAR]);

            // Call finalizers or skip them if needed
            il.Emit(OpCodes.Ldloc, skipFinalizersVar);
            var postFinalizersLabel = il.DeclareLabel();

            il.Emit(OpCodes.Brtrue, postFinalizersLabel);

            var rethrowPossible = WriteFinalizerCalls(true);

            il.MarkLabel(postFinalizersLabel);

            // Possibly rethrow if __exception is still not null (i.e. suppressed)
            skipLabel = il.DeclareLabel();
            il.Emit(OpCodes.Ldloc, variables[EXCEPTION_VAR]);
            il.Emit(OpCodes.Brfalse, skipLabel);
            if (rethrowPossible)
            {
                il.Emit(OpCodes.Rethrow);
            }
            else
            {
                il.Emit(OpCodes.Ldloc, variables[EXCEPTION_VAR]);
                il.Emit(OpCodes.Throw);
            }

            il.MarkLabel(skipLabel);
            il.EndExceptionBlock(mainBlock);

            if (returnValueVar != null)
            {
                il.Emit(OpCodes.Ldloc, returnValueVar);
            }
        }
Exemple #4
0
        private static void WritePrefixes(ILEmitter il, MethodBase original, ILEmitter.Label returnLabel,
                                          Dictionary <string, VariableDefinition> variables, List <MethodInfo> prefixes)
        {
            // Prefix layout:
            // Make return value (if needed) into a variable
            // Call prefixes
            // If method returns a value, add additional logic to allow skipping original method

            if (prefixes.Count == 0)
            {
                return;
            }

            Logger.Log(Logger.LogChannel.Info, () => "Writing prefixes");

            // Start emitting at the start
            il.emitBefore = il.IL.Body.Instructions[0];

            if (!variables.TryGetValue(RESULT_VAR, out var returnValueVar))
            {
                var retVal = AccessTools.GetReturnedType(original);
                returnValueVar = variables[RESULT_VAR] = retVal == typeof(void) ? null : il.DeclareVariable(retVal);
            }

            // Flag to check if the orignal method should be run (or was run)
            // Only present if method has a return value and there are prefixes that modify control flow
            var runOriginal = prefixes.Any(p => p.ReturnType == typeof(bool))
                                ? il.DeclareVariable(typeof(bool))
                                : null;

            // Init runOriginal to true
            if (runOriginal != null)
            {
                il.Emit(OpCodes.Ldc_I4_1);
                il.Emit(OpCodes.Stloc, runOriginal);
            }

            // If runOriginal flag exists, we need to add more logic to the method end
            var postProcessTarget = returnValueVar != null?il.DeclareLabel() : returnLabel;

            foreach (var prefix in prefixes)
            {
                EmitCallParameter(il, original, prefix, variables, false);
                il.Emit(OpCodes.Call, prefix);

                if (!AccessTools.IsVoid(prefix.ReturnType))
                {
                    if (prefix.ReturnType != typeof(bool))
                    {
                        throw new InvalidHarmonyPatchArgumentException(
                                  $"Prefix patch {prefix.GetID()} has return type {prefix.ReturnType}, but only `bool` or `void` are permitted", original, prefix);
                    }

                    if (runOriginal != null)
                    {
                        // AND the current runOriginal to return value of the method (if any)
                        il.Emit(OpCodes.Ldloc, runOriginal);
                        il.Emit(OpCodes.And);
                        il.Emit(OpCodes.Stloc, runOriginal);
                    }
                }
            }

            if (runOriginal == null)
            {
                return;
            }

            // If runOriginal is false, branch automatically to the end
            il.Emit(OpCodes.Ldloc, runOriginal);
            il.Emit(OpCodes.Brfalse, postProcessTarget);

            if (returnValueVar == null)
            {
                return;
            }

            // Finally, load return value onto stack at the end
            il.emitBefore = il.IL.Body.Instructions[il.IL.Body.Instructions.Count - 1];
            il.MarkLabel(postProcessTarget);
            il.Emit(OpCodes.Ldloc, returnValueVar);
        }