public void Rewrite(MethodDefinition methodDefinition, ILProcessor il, ObserverRewriterContext context)
        {
            // Start from 2 - we added the 2 load variable instructions in ObserverRewriter
            for (int i = 2; i < methodDefinition.Body.Instructions.Count; i++)
            {
                // Keep current number of instructions to detect changes.
                int instructionCount = methodDefinition.Body.Instructions.Count;

                Instruction instruction = methodDefinition.Body.Instructions[i];

                if (instruction.OpCode.Code == Code.Newarr)
                {
                    CheckArrayCreationSize(instruction, il, context);
                }

                if (instruction.Operand is MethodReference called)
                {
                    PossiblyRewriteCalledMethod(called, instruction, il, context);
                }

                // If we rewrote, need to increase our iterator by the number of instructions we inserted.
                int instructionsAdded = methodDefinition.Body.Instructions.Count - instructionCount;
                i += instructionsAdded;
            }
        }
 /// <summary>
 /// Insert instructions to check on the size of an array before it is created.
 /// Assumes the item on top of the stack at the moment is an int for the length of the array we're creating.
 /// </summary>
 private void CheckArrayCreationSize(Instruction instruction, ILProcessor il, ObserverRewriterContext context)
 {
     il.Body.SimplifyMacros();
     il.InsertBefore(instruction, il.CreateLdlocBest(context.ObserverVariable));
     il.InsertBefore(instruction, il.Create(OpCodes.Call, context.Observer.FlowThroughMemoryInt32Method));
     il.Body.OptimizeMacros();
 }
Example #3
0
        /// <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);
            }
        }
Example #4
0
        /// <inheritdoc />
        public void Rewrite(MethodDefinition methodDefinition, ILProcessor il, ObserverRewriterContext context)
        {
            List <Instruction> branches  = GetBranchingOps(methodDefinition).ToList();
            List <Instruction> branchTos = branches.Select(x => (Instruction)x.Operand).ToList();

            // Start from 2 because we setup Observer in first 2 instructions
            int position = 2;

            List <CodeSegment> segments = new List <CodeSegment>();

            var codeSegment = new CodeSegment(methodDefinition);

            while (position < methodDefinition.Body.Instructions.Count)
            {
                Instruction instruction = methodDefinition.Body.Instructions[position];

                // End of a segment. Add this as the last instruction and move onwards with a new segment
                if (branches.Contains(instruction))
                {
                    codeSegment.Instructions.Add(instruction);
                    segments.Add(codeSegment);
                    codeSegment = new CodeSegment(methodDefinition);
                }
                // Start of a new segment. End last segment and start new one with this as the first instruction
                else if (branchTos.Contains(instruction))
                {
                    if (codeSegment.Instructions.Any())
                    {
                        segments.Add(codeSegment);
                    }
                    codeSegment = new CodeSegment(methodDefinition);
                    codeSegment.Instructions.Add(instruction);
                }
                // Just an in-between instruction. Add to current segment.
                else
                {
                    codeSegment.Instructions.Add(instruction);
                }

                position++;
            }

            // Got to end of the method. Add the last one if necessary
            if (!segments.Contains(codeSegment) && codeSegment.Instructions.Any())
            {
                segments.Add(codeSegment);
            }

            foreach (CodeSegment segment in segments)
            {
                AddSpendGasMethodBeforeInstruction(il, context.Observer, context.ObserverVariable, segment);
            }

            // All of the branches now need to point to the place 3 instructions earlier!
            foreach (Instruction branch in branches)
            {
                Instruction currentlyPointingTo = (Instruction)branch.Operand;
                branch.Operand = currentlyPointingTo.Previous.Previous.Previous;
            }
        }
        /// <summary>
        /// If is the specific string constructor that takes in a 'count' parameter, we need to check its size,
        /// </summary>
        private void CheckStringConstructor(Instruction instruction, ILProcessor il, ObserverRewriterContext context)
        {
            MethodDefinition method = ((MethodReference)instruction.Operand).Resolve();

            // Ensure is the constructor with a count param (not all string constructors have a count param)
            if (method.Parameters.Any(x => x.Name == "count"))
            {
                CheckArrayCreationSize(instruction, il, context);
            }
        }
 /// <summary>
 /// Insert instructions to check on the size of an array that has just been pushed onto the top of the stack.
 /// </summary>
 private void CheckArrayReturnSize(Instruction instruction, ILProcessor il, ObserverRewriterContext context)
 {
     // TODO: We could do away with the pop on the end and not return anything from the observer method but we would need to cast to long correctly.
     il.InsertAfter(instruction,
                    il.Create(OpCodes.Dup),
                    il.Create(OpCodes.Ldlen),
                    il.CreateLdlocBest(context.ObserverVariable),
                    il.Create(OpCodes.Call, context.Observer.FlowThroughMemoryInt32Method),
                    il.Create(OpCodes.Pop)
                    );
 }
        /// <inheritdoc />
        public void Rewrite(MethodDefinition methodDefinition, ILProcessor il, ObserverRewriterContext context)
        {
            List <Instruction> branches  = methodDefinition.Body.Instructions.Where(x => BranchingOps.Contains(x.OpCode)).ToList();
            List <Instruction> branchTos = branches.Select(x => (Instruction)x.Operand).ToList();

            Gas gasTally = Gas.None;

            Dictionary <Instruction, Gas> gasToSpendForSegment = new Dictionary <Instruction, Gas>();

            // To account  for variable load at start.
            int         position            = 2;
            Instruction currentSegmentStart = methodDefinition.Body.Instructions[position];

            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, context.Observer, context.ObserverVariable, injectAfterInstruction, gasToSpendForSegment[instruction]);
            }
        }
        /// <summary>
        /// Checks if it is one of a few method calls we need to check on.
        /// If so, does the necessary IL rewrite.
        /// </summary>
        private void PossiblyRewriteCalledMethod(MethodReference called, Instruction instruction, ILProcessor il, ObserverRewriterContext context)
        {
            if (called.DeclaringType.FullName == typeof(Array).FullName && called.Name == nameof(Array.Resize))
            {
                CheckArrayCreationSize(instruction, il, context);
                return;
            }

            if (called.DeclaringType.FullName == typeof(string).FullName)
            {
                if (called.Name == nameof(string.ToCharArray))
                {
                    CheckArrayReturnSize(instruction, il, context);
                }
                else if (called.Name == nameof(string.Split))
                {
                    CheckArrayReturnSize(instruction, il, context);
                }
                else if (called.Name == nameof(string.Concat))
                {
                    CheckArrayReturnSize(instruction, il, context);
                }
                else if (called.Name == nameof(string.Join))
                {
                    CheckArrayReturnSize(instruction, il, context);
                }
                else if (called.Name == ".ctor")
                {
                    CheckStringConstructor(instruction, il, context);
                }
            }
        }