/// <summary> /// Create the Ctor /// </summary> private static void CreateDefaultCtor(ReachableContext reachableContext, TypeDefinition type) { var typeSystem = type.Module.TypeSystem; var ctor = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, typeSystem.Void); ctor.DeclaringType = type; var body = new MethodBody(ctor); body.InitLocals = true; ctor.Body = body; // Prepare code var seq = new ILSequence(); seq.Emit(OpCodes.Nop); seq.Emit(OpCodes.Ret); // Append ret sequence seq.AppendTo(body); // Update offsets body.ComputeOffsets(); // Add ctor type.Methods.Add(ctor); ctor.SetReachable(reachableContext); }
public void Disassemble(MethodBody body) { // start writing IL code var method = body.Method; var codesize = body.ComputeCodeSize(); var maxstacksize = body.ComputeMaxStackSize(); body.ComputeOffsets(); _o.WriteLine("// Code size {0} (0x{0:x})", codesize); _o.WriteLine(".maxstack {0}", maxstacksize); if (method.DeclaringType.Module.Assembly != null && method.DeclaringType.Module.Assembly.EntryPoint == method) _o.WriteLine(".entrypoint"); if (method.Body.HasVariables) { _o.Write(".locals "); if (body.InitLocals) _o.Write("init "); _o.WriteLine("("); _o.Indent(); foreach (var v in body.Variables) { _o.Write("[" + v.Index + "] "); v.VariableType.WriteTo(_o); if (!string.IsNullOrEmpty(v.Name)) { _o.Write(' '); _o.Write(DisassemblerHelpers.Escape(v.Name)); } if (v.Index + 1 < body.Variables.Count) _o.Write(','); _o.WriteLine(); } _o.Unindent(); _o.WriteLine(")"); } _o.WriteLine(); if (body.Instructions.Count > 0) { var inst = body.Instructions[0]; var branchTargets = GetBranchTargets(body.Instructions); WriteStructureBody(new IlStructure(body, codesize), branchTargets, ref inst); } else { // we ignore method without instructions (but it can have exception handlers) _o.WriteLine(); } }
/// <summary> /// Ensure there is a class ctor. /// </summary> private static MethodDefinition EnsureClassCtor(ReachableContext reachableContext, TypeDefinition type) { var ctor = type.GetClassCtor(); if (ctor != null) return ctor; // Already exists // Create class ctor var typeSystem = type.Module.TypeSystem; ctor = new MethodDefinition(".cctor", MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.Static | MethodAttributes.SpecialName, typeSystem.Void); ctor.DeclaringType = type; var body = new MethodBody(ctor); body.InitLocals = true; ctor.Body = body; // Prepare code var seq = new ILSequence(); seq.Emit(OpCodes.Nop); seq.Emit(OpCodes.Ret); // Append ret sequence seq.AppendTo(body); // Update offsets body.ComputeOffsets(); // Add ctor type.Methods.Add(ctor); ctor.SetReachable(reachableContext); return ctor; }
/// <summary> /// Add a method that has the same signature as basemethod and calls method. /// </summary> private MethodDefinition AddBridge(MethodDefinition method, MethodDefinition baseMethod) { var bridge = new MethodDefinition(baseMethod.Name, baseMethod.Attributes, baseMethod.ReturnType) { HasThis = true }; var cloner = new TypeCloner(true, method.Module.TypeSystem); bridge.ReturnType = cloner.Get(baseMethod.ReturnType, bridge); bridge.IsAbstract = false; // Clone parameters foreach (var p in baseMethod.Parameters) { bridge.Parameters.Add(new ParameterDefinition(p.Name, p.Attributes, cloner.Get(p.ParameterType, bridge))); } // Create body var body = new MethodBody(bridge); bridge.Body = body; // Create code var seq = new ILSequence(); // this seq.Emit(OpCodes.Ldarg_0); // parameters for (var i = 0; i < bridge.Parameters.Count; i++) { var p = bridge.Parameters[i]; seq.Emit(OpCodes.Ldarg, p); if (baseMethod.Parameters[i].ParameterType.ContainsGenericParameter) { seq.Emit(OpCodes.Unbox, method.Parameters[i].ParameterType); } } // Call actual method seq.Emit(OpCodes.Call, method); // Return seq.Emit(OpCodes.Ret); // Add code to body seq.AppendTo(body); body.ComputeOffsets(); // add overrides, so that we can find the method later bridge.Overrides.Add(baseMethod); // Add to class method.DeclaringType.Methods.Add(bridge); bridge.SetReachable(reachableContext); return bridge; }
/// <summary> /// Create a CopyFrom method. /// </summary> private static MethodDefinition CreateCopyFromMethod(ReachableContext reachableContext, TypeDefinition type) { var typeSystem = type.Module.TypeSystem; var method = new MethodDefinition(NameConstants.Struct.CopyFromMethodName, MethodAttributes.Public, type); var sourceParam = new ParameterDefinition(type); method.Parameters.Add(sourceParam); method.DeclaringType = type; var body = new MethodBody(method); body.InitLocals = true; method.Body = body; // Prepare code var seq = new ILSequence(); seq.Emit(OpCodes.Nop); // Call base CopyFrom var baseType = (type.BaseType != null) ? type.BaseType.GetElementType().Resolve() : null; if ((baseType != null) && baseType.IsValueType && (baseType.FullName != "System.ValueType")) { var baseMethod = new MethodReference(NameConstants.Struct.CopyFromMethodName, baseType, baseType) { HasThis = true }; baseMethod.Parameters.Add(new ParameterDefinition(baseType)); seq.Emit(OpCodes.Ldarg, sourceParam); seq.Emit(OpCodes.Call, baseMethod); } // Copy all fields foreach (var field in type.Fields.Where(x => !x.IsStatic)) { TypeDefinition fieldTypeDef; var isStructField = StructFields.IsStructField(field, out fieldTypeDef); // Prepare for stfld seq.Emit(OpCodes.Ldarg, body.ThisParameter); // Load from source seq.Emit(OpCodes.Ldarg, sourceParam); seq.Emit(OpCodes.Ldfld, field); // If struct, create clone if (isStructField) { var cloneMethod = new MethodReference(NameConstants.Struct.CloneMethodName, fieldTypeDef, fieldTypeDef) { HasThis = true }; seq.Emit(OpCodes.Call, cloneMethod); } // Save in this seq.Emit(OpCodes.Stfld, field); } // Return this seq.Emit(OpCodes.Ldarg, body.ThisParameter); seq.Emit(OpCodes.Ret); // Append ret sequence seq.AppendTo(body); // Update offsets body.ComputeOffsets(); // Add method type.Methods.Add(method); method.SetReachable(reachableContext); return method; }
/// <summary> /// Create a Clone method. /// </summary> private static void CreateCloneMethod(ReachableContext reachableContext, TypeDefinition type, MethodDefinition copyFromMethod) { var method = new MethodDefinition(NameConstants.Struct.CloneMethodName, MethodAttributes.Public, type); method.DeclaringType = type; var body = new MethodBody(method); body.InitLocals = true; method.Body = body; // Prepare code var seq = new ILSequence(); seq.Emit(OpCodes.Nop); // Create new instance var defaultCtor = CreateDefaultCtorRef(type); seq.Emit(OpCodes.Newobj, defaultCtor); // Call clone.CopyFrom seq.Emit(OpCodes.Dup); seq.Emit(OpCodes.Ldarg, body.ThisParameter); seq.Emit(OpCodes.Call, copyFromMethod); // Return clone seq.Emit(OpCodes.Ret); // Append ret sequence seq.AppendTo(body); // Update offsets body.ComputeOffsets(); // Add method type.Methods.Add(method); method.SetReachable(reachableContext); }
/// <summary> /// Inline the call to the given ctor /// </summary> private static void InlineNewObjCall(Instruction instruction, MethodBody body, MethodDefinition ctor) { // Prepare var prefixSeq = new ILSequence(); ctor.Body.SimplifyMacros(); // Create "this" variable var thisVariable = new VariableDefinition(ctor.DeclaringType); body.Variables.Add(thisVariable); body.InitLocals = true; // Store argument in variables var paramVariables = new List<VariableDefinition>(); foreach (var parameter in ctor.Parameters.Reverse()) { // Create variable var paramVariable = new VariableDefinition(parameter.ParameterType); body.Variables.Add(paramVariable); paramVariables.Insert(0, paramVariable); // Pop prefixSeq.Emit(OpCodes.Stloc, paramVariable); } // Clone variables first var source = ctor.Body; var variables = new List<VariableDefinition>(); foreach (var sv in source.Variables) { var clone = new VariableDefinition(sv.VariableType); variables.Add(clone); body.Variables.Add(clone); } // Now clone instructions var seq = new ILSequence(); foreach (var instr in source.Instructions) { var ni = new Instruction(instr.OpCode, instr.Operand); seq.Append(ni); ni.Offset = instr.Offset; // Convert variable opcodes switch (instr.OpCode.OperandType) { case OperandType.InlineVar: case OperandType.ShortInlineVar: { var index = source.Variables.IndexOf((VariableDefinition) instr.Operand); ni.Operand = variables[index]; } break; } // Convert parameter opcodes switch (instr.OpCode.Code) { case Mono.Cecil.Cil.Code.Ldarg: { var index = ctor.Parameters.IndexOf((ParameterDefinition) instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloc; } break; case Mono.Cecil.Cil.Code.Ldarga: { var index = ctor.Parameters.IndexOf((ParameterDefinition) instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloca; } break; case Mono.Cecil.Cil.Code.Starg: { var index = ctor.Parameters.IndexOf((ParameterDefinition) instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Stloc; } break; } } // Update branch targets for (var i = 0; i < seq.Length; i++) { var instr = seq[i]; var oldi = source.Instructions[i]; if (instr.OpCode.OperandType == OperandType.InlineSwitch) { var olds = (Instruction[]) oldi.Operand; var targets = new Instruction[olds.Length]; for (int j = 0; j < targets.Length; j++) { targets[j] = GetClone(seq, source.Instructions, olds[j]); } instr.Operand = targets; } else if (instr.OpCode.OperandType == OperandType.ShortInlineBrTarget || instr.OpCode.OperandType == OperandType.InlineBrTarget) { instr.Operand = GetClone(seq, source.Instructions, (Instruction) oldi.Operand); } } // Clone exception handlers if (source.HasExceptionHandlers) { CloneInstructions(seq, source.Instructions, body.ExceptionHandlers, source.ExceptionHandlers); } // Find call to "this" ctor var callToCtors = seq.Where(x => IsCallToThisCtor(x, ctor)).ToList(); if (callToCtors.Count == 0) throw new CompilerException(string.Format("No call to another this ctor found in {0}", ctor)); if (callToCtors.Count > 1) throw new CompilerException(string.Format("Multiple calls to another this ctor found in {0}", ctor)); var callToCtor = callToCtors[0]; // Change "ld this" to nop var args = callToCtor.GetCallArguments(seq, true); args[0].ChangeToNop(); // Replace ldarg.0 // Replace call to this ctor with newobj var callSeq = new ILSequence(); callSeq.Emit(OpCodes.Newobj, (MethodReference) callToCtor.Operand); callSeq.Emit(OpCodes.Stloc, thisVariable); // Save new object callToCtor.ChangeToNop(); callSeq.InsertToBefore(callToCtor, seq); // Replace ret instructions var end = seq.Emit(OpCodes.Ldloc, thisVariable); var retInstructions = seq.Where(x => x.OpCode.Code == Mono.Cecil.Cil.Code.Ret).ToList(); foreach (var ins in retInstructions) { ins.OpCode = OpCodes.Br; ins.Operand = end; } // Insert cloned instructions prefixSeq.InsertTo(0, seq); seq.InsertToAfter(instruction, body); // Change replaced instruction to nop instruction.ChangeToNop(); // Update offsets body.ComputeOffsets(); }
/// <summary> /// Inline the call to the given method /// </summary> private static void InlineCall(Instruction instruction, MethodBody body, MethodDefinition targetMethod) { // Prepare var prefixSeq = new ILSequence(); // Create "this" variable VariableDefinition thisVariable = null; if (targetMethod.HasThis) { thisVariable = new VariableDefinition(targetMethod.DeclaringType); body.Variables.Add(thisVariable); body.InitLocals = true; } // Store argument in variables var paramVariables = new List<VariableDefinition>(); foreach (var parameter in targetMethod.Parameters.Reverse()) { // Create variable var paramVariable = new VariableDefinition(parameter.ParameterType); body.Variables.Add(paramVariable); paramVariables.Insert(0, paramVariable); // Pop prefixSeq.Emit(OpCodes.Stloc, paramVariable); } // Store this argument (if any) if (thisVariable != null) { // Pop prefixSeq.Emit(OpCodes.Stloc, thisVariable); } // Clone variables first var source = targetMethod.Body; var variables = new List<VariableDefinition>(); foreach (var sv in source.Variables) { var clone = new VariableDefinition(sv.VariableType); variables.Add(clone); body.Variables.Add(clone); } // Now clone instructions var seq = new ILSequence(); foreach (var instr in source.Instructions) { var ni = new Instruction(instr.OpCode, instr.Operand); seq.Append(ni); ni.Offset = instr.Offset; // Convert variable opcodes switch (instr.OpCode.OperandType) { case OperandType.InlineVar: case OperandType.ShortInlineVar: { var index = source.Variables.IndexOf((VariableDefinition)instr.Operand); ni.Operand = variables[index]; } break; } // Convert parameter opcodes switch (instr.OpCode.Code) { case Code.Ldarg: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloc; } break; case Code.Ldarga: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloca; } break; case Code.Starg: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Stloc; } break; } } // Update branch targets for (var i = 0; i < seq.Length; i++) { var instr = seq[i]; var oldi = source.Instructions[i]; if (instr.OpCode.OperandType == OperandType.InlineSwitch) { var olds = (Instruction[])oldi.Operand; var targets = new Instruction[olds.Length]; for (int j = 0; j < targets.Length; j++) { targets[j] = GetClone(seq, source.Instructions, olds[j]); } instr.Operand = targets; } else if (instr.OpCode.OperandType == OperandType.ShortInlineBrTarget || instr.OpCode.OperandType == OperandType.InlineBrTarget) { instr.Operand = GetClone(seq, source.Instructions, (Instruction)oldi.Operand); } } // Clone exception handlers if (source.HasExceptionHandlers) { body.ComputeOffsets(); CloneInstructions(seq, source.Instructions, body.ExceptionHandlers, source.ExceptionHandlers); } // Replace ret instructions var end = seq.Emit(OpCodes.Nop); var retInstructions = seq.Where(x => x.OpCode.Code == Code.Ret).ToList(); foreach (var ins in retInstructions) { ins.OpCode = OpCodes.Br; ins.Operand = end; } // cast return type of a generic method. TODO: better might be to // correctly resolve calls to generic instances as well (above) if (targetMethod.ReturnType.IsGenericParameter) { var methodRef = (MethodReference)instruction.Operand; TypeReference returnType = null; var gp = (GenericParameter)methodRef.ReturnType; if (gp.Type == GenericParameterType.Type) { var gi = methodRef.DeclaringType as IGenericInstance; if (gi != null && gi.HasGenericArguments) returnType = gi.GenericArguments[gp.Position]; } else if (gp.Type == GenericParameterType.Method) { var gi = methodRef as IGenericInstance; if (gi != null && gi.HasGenericArguments) returnType = gi.GenericArguments[gp.Position]; } if (returnType != null) { if (!returnType.IsPrimitive) { seq.Emit(OpCodes.Castclass, returnType); } // todo: handle primitive types. unbox them? are structs correctly handled? enums? } } // Insert cloned instructions prefixSeq.InsertTo(0, seq); seq.InsertToAfter(instruction, body); // Change replaced instruction to nop instruction.ChangeToNop(); // Update offsets body.ComputeOffsets(); }
/// <summary> /// Convert all synchronized methods. /// </summary> private static void Convert(MethodBody body) { var typeSystem = body.Method.Module.TypeSystem; var monitorType = typeSystem.LookupType("System.Threading", "Monitor"); var enterMethod = new MethodReference("Enter", typeSystem.Void, monitorType); enterMethod.Parameters.Add(new ParameterDefinition(typeSystem.Object)); var exitMethod = new MethodReference("Exit", typeSystem.Void, monitorType); exitMethod.Parameters.Add(new ParameterDefinition(typeSystem.Object)); var firstInstr = body.Instructions[0]; // Expand macro's body.SimplifyMacros(); // Prepare new return var retSeq = new ILSequence(); retSeq.Emit(OpCodes.Nop); retSeq.Emit(OpCodes.Ret); // Monitor.Enter(this) var initSeq = new ILSequence(); initSeq.Emit(OpCodes.Ldarg_0); // ld this initSeq.Emit(OpCodes.Call, enterMethod); initSeq.InsertTo(0, body); // Leave sequence var leaveSeq = new ILSequence(); leaveSeq.Emit(OpCodes.Nop); leaveSeq.Emit(OpCodes.Leave, retSeq.First); leaveSeq.AppendTo(body); // Finally: Monitor.Exit(this) var finallySeq = new ILSequence(); finallySeq.Emit(OpCodes.Ldarg_0); // ld this finallySeq.Emit(OpCodes.Call, exitMethod); finallySeq.Emit(OpCodes.Endfinally); finallySeq.AppendTo(body); // Replace Ret instructions foreach (var instr in body.Instructions.Where(x => x.OpCode.Code == Mono.Cecil.Cil.Code.Ret)) { if (instr.Next == leaveSeq.First) { instr.ChangeToNop(); } else { instr.OpCode = OpCodes.Br; instr.Operand = leaveSeq.First; } } // Append ret sequence retSeq.AppendTo(body); // Update offsets body.ComputeOffsets(); // Add try/finally block var handler = new ExceptionHandler(ExceptionHandlerType.Finally); handler.TryStart = firstInstr; handler.TryEnd = finallySeq.First; // leaveSeq.Last; handler.HandlerStart = finallySeq.First; handler.HandlerEnd = retSeq.First; // finallySeq.Last; body.ExceptionHandlers.Insert(0, handler); }
/// <summary> /// Inline the call to the given method /// </summary> private static void InlineCall(Instruction instruction, MethodBody body, MethodDefinition targetMethod) { // Prepare var prefixSeq = new ILSequence(); // Create "this" variable VariableDefinition thisVariable = null; if (targetMethod.HasThis) { thisVariable = new VariableDefinition(targetMethod.DeclaringType); body.Variables.Add(thisVariable); body.InitLocals = true; } // Store argument in variables var paramVariables = new List<VariableDefinition>(); foreach (var parameter in targetMethod.Parameters.Reverse()) { // Create variable var paramVariable = new VariableDefinition(parameter.ParameterType); body.Variables.Add(paramVariable); paramVariables.Insert(0, paramVariable); // Pop prefixSeq.Emit(OpCodes.Stloc, paramVariable); } // Store this argument (if any) if (thisVariable != null) { // Pop prefixSeq.Emit(OpCodes.Stloc, thisVariable); } // Clone variables first var source = targetMethod.Body; var variables = new List<VariableDefinition>(); foreach (var sv in source.Variables) { var clone = new VariableDefinition(sv.VariableType); variables.Add(clone); body.Variables.Add(clone); } // Now clone instructions var seq = new ILSequence(); foreach (var instr in source.Instructions) { var ni = new Instruction(instr.OpCode, instr.Operand); seq.Append(ni); ni.Offset = instr.Offset; // Convert variable opcodes switch (instr.OpCode.OperandType) { case OperandType.InlineVar: case OperandType.ShortInlineVar: { var index = source.Variables.IndexOf((VariableDefinition)instr.Operand); ni.Operand = variables[index]; } break; } // Convert parameter opcodes switch (instr.OpCode.Code) { case Code.Ldarg: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloc; } break; case Code.Ldarga: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Ldloca; } break; case Code.Starg: { var index = targetMethod.Parameters.IndexOf((ParameterDefinition)instr.Operand); ni.Operand = (index >= 0) ? paramVariables[index] : thisVariable; ni.OpCode = OpCodes.Stloc; } break; } } // Update branch targets for (var i = 0; i < seq.Length; i++) { var instr = seq[i]; var oldi = source.Instructions[i]; if (instr.OpCode.OperandType == OperandType.InlineSwitch) { var olds = (Instruction[])oldi.Operand; var targets = new Instruction[olds.Length]; for (int j = 0; j < targets.Length; j++) { targets[j] = GetClone(seq, source.Instructions, olds[j]); } instr.Operand = targets; } else if (instr.OpCode.OperandType == OperandType.ShortInlineBrTarget || instr.OpCode.OperandType == OperandType.InlineBrTarget) { instr.Operand = GetClone(seq, source.Instructions, (Instruction)oldi.Operand); } } // Clone exception handlers if (source.HasExceptionHandlers) { body.ComputeOffsets(); CloneInstructions(seq, source.Instructions, body.ExceptionHandlers, source.ExceptionHandlers); } // Replace ret instructions var end = seq.Emit(OpCodes.Nop); var retInstructions = seq.Where(x => x.OpCode.Code == Code.Ret).ToList(); foreach (var ins in retInstructions) { ins.OpCode = OpCodes.Br; ins.Operand = end; } // Insert cloned instructions prefixSeq.InsertTo(0, seq); seq.InsertToAfter(instruction, body); // Change replaced instruction to nop instruction.ChangeToNop(); // Update offsets body.ComputeOffsets(); }