private void RewriteType(TypeDefinition type, ObserverReferences observer) { foreach (MethodDefinition method in type.Methods) { RewriteMethod(method, observer); } }
/// <summary> /// Makes the <see cref="Observer"/> available to the given method as a variable and then /// applies all of the individual rewriters to the method. /// </summary> private void RewriteMethod(MethodDefinition methodDefinition, ObserverReferences observer) { if (methodDefinition.DeclaringType == observer.InstanceField.DeclaringType) { return; // don't inject on our injected type. } if (!methodDefinition.HasBody || methodDefinition.Body.Instructions.Count == 0) { return; // don't inject on method without a Body } // Inject observer instance to method. ILProcessor il = methodDefinition.Body.GetILProcessor(); var observerVariable = new VariableDefinition(observer.InstanceField.FieldType); il.Body.Variables.Add(observerVariable); Instruction start = methodDefinition.Body.Instructions[0]; il.InsertBefore(start, il.Create(OpCodes.Ldsfld, observer.InstanceField)); il.InsertBefore(start, il.CreateStlocBest(observerVariable)); var context = new ObserverRewriterContext(observer, observerVariable); foreach (IObserverMethodRewriter rewriter in methodRewriters) { rewriter.Rewrite(methodDefinition, il, context); } }
/// <summary> /// Adds a call to SpendGas from the RuntimeObserver before the given instruction. /// </summary> private static void AddSpendGasMethodBeforeInstruction(ILProcessor il, ObserverReferences observer, VariableDefinition variable, CodeSegment codeSegment) { Instruction first = codeSegment.Instructions.First(); Instruction newFirst = il.CreateLdlocBest(variable); long segmentCost = (long)codeSegment.CalculateGasCost().Value; il.Body.SimplifyMacros(); il.InsertBefore(first, newFirst); // load observer il.InsertBefore(first, il.Create(OpCodes.Ldc_I8, (long)segmentCost)); // load gas amount il.InsertBefore(first, il.Create(OpCodes.Call, observer.SpendGasMethod)); // trigger method il.Body.OptimizeMacros(); }
public ModuleDefinition Rewrite(ModuleDefinition module) { Guid id = Guid.NewGuid(); FieldDefinition observerInstanceField = GetObserverInstance(module, id); var observer = new ObserverReferences(observerInstanceField, module); foreach (TypeDefinition type in module.Types) { RewriteType(type, observer); } ObserverInstances.Set(id, this.observerToInject); return(module); }
/// <summary> /// Adds a call to SpendGas from the RuntimeObserver before the given instruction. /// </summary> private static void AddSpendGasMethodBeforeInstruction(MethodDefinition methodDefinition, ObserverReferences observer, VariableDefinition variable, Instruction instruction, Gas opcodeCount) { ILProcessor il = methodDefinition.Body.GetILProcessor(); il.Body.SimplifyMacros(); il.InsertBefore(instruction, il.CreateLdlocBest(variable)); // load observer il.InsertBefore(instruction, il.Create(OpCodes.Ldc_I8, (long)opcodeCount.Value)); // load gas amount il.InsertBefore(instruction, il.Create(OpCodes.Call, observer.SpendGasMethod)); // trigger method il.Body.OptimizeMacros(); }
private void RewriteMethod(MethodDefinition methodDefinition, ObserverReferences observer) { if (methodDefinition.DeclaringType == observer.InstanceField.DeclaringType) { return; // don't inject on our injected type. } if (!methodDefinition.HasBody || methodDefinition.Body.Instructions.Count == 0) { return; // don't inject on method without a Body } List <Instruction> branches = methodDefinition.Body.Instructions.Where(x => BranchingOps.Contains(x.OpCode)).ToList(); List <Instruction> branchTos = branches.Select(x => (Instruction)x.Operand).ToList(); Instruction currentSegmentStart = methodDefinition.Body.Instructions.FirstOrDefault(); Gas gasTally = Gas.None; Dictionary <Instruction, Gas> gasToSpendForSegment = new Dictionary <Instruction, Gas>(); // Inject observer instance to method. ILProcessor il = methodDefinition.Body.GetILProcessor(); var observerVariable = new VariableDefinition(observer.InstanceField.FieldType); il.Body.Variables.Add(observerVariable); Instruction start = methodDefinition.Body.Instructions[0]; il.InsertBefore(start, il.Create(OpCodes.Ldsfld, observer.InstanceField)); il.InsertBefore(start, il.CreateStlocBest(observerVariable)); // Start at 2 because of the instructions we just added. int position = 2; while (position < methodDefinition.Body.Instructions.Count) { Instruction instruction = methodDefinition.Body.Instructions[position]; Gas instructionCost = GasPriceList.InstructionOperationCost(instruction); // is the end of a segment. Include the current instruction in the count. if (branches.Contains(instruction)) { gasTally = (Gas)(gasTally + instructionCost); gasToSpendForSegment.Add(currentSegmentStart, gasTally); gasTally = Gas.None; position++; if (position == methodDefinition.Body.Instructions.Count) { break; } currentSegmentStart = methodDefinition.Body.Instructions[position]; } // is the start of a new segment. Don't include the current instruction in count. else if (branchTos.Contains(instruction) && instruction != currentSegmentStart) { gasToSpendForSegment.Add(currentSegmentStart, gasTally); gasTally = Gas.None; currentSegmentStart = instruction; position++; } // is a call to another method else if (CallingOps.Contains(instruction.OpCode)) { var methodToCall = (MethodReference)instruction.Operand; // If it's a method inside this contract then the gas will be injected no worries. if (methodToCall.DeclaringType == methodDefinition.DeclaringType) { position++; gasTally = (Gas)(gasTally + instructionCost); } // If it's a method outside this contract then we will need to get some average in future. else { Gas methodCallCost = GasPriceList.MethodCallCost(methodToCall); position++; gasTally = (Gas)(gasTally + instructionCost + methodCallCost); } } // any other instruction. just increase counter. else { position++; gasTally = (Gas)(gasTally + instructionCost); } } if (!gasToSpendForSegment.ContainsKey(currentSegmentStart)) { gasToSpendForSegment.Add(currentSegmentStart, gasTally); } foreach (Instruction instruction in gasToSpendForSegment.Keys) { Instruction injectAfterInstruction = instruction; // If it's a constructor we need to skip the first 3 instructions. // These will always be invoking the base constructor // ldarg.0 // ldarg.0 // call SmartContract::ctor if (methodDefinition.IsConstructor) { injectAfterInstruction = instruction.Next.Next.Next; } AddSpendGasMethodBeforeInstruction(methodDefinition, observer, observerVariable, injectAfterInstruction, gasToSpendForSegment[instruction]); } foreach (Instruction instruction in branches) { var oldReference = (Instruction)instruction.Operand; Instruction newReference = oldReference.Previous.Previous.Previous; // 3 were inserted Instruction newInstruction = il.Create(instruction.OpCode, newReference); il.Replace(instruction, newInstruction); } }
public ObserverRewriterContext(ObserverReferences observer, VariableDefinition observerVariable) { this.Observer = observer; this.ObserverVariable = observerVariable; }