Пример #1
0
        private void InstrumentInstructions(MethodDefinition methodDefinition, IEnumerable <SequencePoint> sequencePoints, string[] fileLines,
                                            InstrumentedAssembly instrumentedAssembly, string sourceRelativePath, MethodReference hitInstructionReference,
                                            Dictionary <int, Instruction> instructions, ILProcessor ilProcessor, VariableDefinition methodContextVariable)
        {
            foreach (var sequencePoint in sequencePoints)
            {
                var code = sequencePoint.ExtractCode(fileLines);
                if (code == null || code == "{" || code == "}")
                {
                    continue;
                }

                var instruction = instructions[sequencePoint.Offset];

                // if the previous instruction is a Prefix instruction then this instruction MUST go with it.
                // we cannot put an instruction between the two.
                if (instruction.Previous != null && instruction.Previous.OpCode.OpCodeType == OpCodeType.Prefix)
                {
                    continue;
                }

                var instructionId = ++id;

                instrumentedAssembly.AddInstruction(sourceRelativePath, new InstrumentedInstruction
                {
                    Id             = instructionId,
                    StartLine      = sequencePoint.StartLine,
                    EndLine        = sequencePoint.EndLine,
                    StartColumn    = sequencePoint.StartColumn,
                    EndColumn      = sequencePoint.EndColumn,
                    Class          = methodDefinition.DeclaringType.FullName,
                    Method         = methodDefinition.Name,
                    MethodFullName = methodDefinition.FullName,
                    Instruction    = instruction.ToString()
                });

                InstrumentInstruction(instructionId, instruction, hitInstructionReference, ilProcessor,
                                      methodContextVariable);
            }
        }
Пример #2
0
        private InstrumentedAssembly InstrumentAssemblyIfNecessary(string assemblyFile)
        {
            var assemblyDirectory = Path.GetDirectoryName(assemblyFile);

            var resolver = new DefaultAssemblyResolver();

            resolver.AddSearchDirectory(assemblyDirectory);

            using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyFile, new ReaderParameters {
                ReadSymbols = true, AssemblyResolver = resolver
            }))
            {
                if (!HasSourceFiles(assemblyDefinition))
                {
                    return(null);
                }

                if (assemblyDefinition.CustomAttributes.Any(a => a.AttributeType.Name == "InstrumentedAttribute"))
                {
                    throw new Exception($"Assembly \"{assemblyFile}\" is already instrumented");
                }

                Console.WriteLine($"Instrumenting assembly \"{assemblyDefinition.Name.Name}\"");

                var instrumentedAssembly = new InstrumentedAssembly(assemblyDefinition.Name.Name);

                var instrumentedAttributeReference = assemblyDefinition.MainModule.ImportReference(instrumentedAttributeConstructor);
                assemblyDefinition.CustomAttributes.Add(new CustomAttribute(instrumentedAttributeReference));

                var enterMethodInfo          = hitServiceType.GetMethod("EnterMethod");
                var exitMethodInfo           = methodContextType.GetMethod("Exit");
                var hitInstructionMethodInfo = methodContextType.GetMethod("HitInstruction");

                var methodContextClassReference = assemblyDefinition.MainModule.ImportReference(methodContextType);
                var enterMethodReference        = assemblyDefinition.MainModule.ImportReference(enterMethodInfo);
                var exitMethodReference         = assemblyDefinition.MainModule.ImportReference(exitMethodInfo);

                var hitInstructionReference = assemblyDefinition.MainModule.ImportReference(hitInstructionMethodInfo);

                var methods = assemblyDefinition.GetAllMethods();

                var documentsGroups = methods
                                      .SelectMany(m => m.DebugInformation.SequencePoints, (method, s) => new
                {
                    Method        = method,
                    SequencePoint = s,
                    s.Document
                })
                                      .GroupBy(j => j.Document)
                                      .ToArray();

                foreach (var documentGroup in documentsGroups)
                {
                    if (!sourceFiles.Contains(documentGroup.Key.Url))
                    {
                        continue;
                    }

                    var sourceRelativePath = GetSourceRelativePath(documentGroup.Key.Url);

                    if (documentGroup.Key.FileHasChanged())
                    {
                        Console.WriteLine($"Ignoring modified file \"{documentGroup.Key.Url}\"");
                        continue;
                    }

                    var fileLines = File.ReadAllLines(documentGroup.Key.Url);

                    var methodGroups = documentGroup
                                       .GroupBy(j => j.Method, j => j.SequencePoint)
                                       .ToArray();

                    foreach (var methodGroup in methodGroups)
                    {
                        var methodDefinition = methodGroup.Key;

                        var ilProcessor = methodDefinition.Body.GetILProcessor();

                        ilProcessor.Body.SimplifyMacros();

                        var instructions = methodDefinition.Body.Instructions.ToDictionary(i => i.Offset);

                        var methodContextVariable = new VariableDefinition(methodContextClassReference);
                        methodDefinition.Body.Variables.Add(methodContextVariable);
                        var pathParamLoadInstruction     = ilProcessor.Create(OpCodes.Ldstr, hitsFile);
                        var enterMethodInstruction       = ilProcessor.Create(OpCodes.Call, enterMethodReference);
                        var storeMethodResultInstruction = ilProcessor.Create(OpCodes.Stloc, methodContextVariable);
                        ilProcessor.InsertBefore(instructions[0], storeMethodResultInstruction);
                        ilProcessor.InsertBefore(storeMethodResultInstruction, enterMethodInstruction);
                        ilProcessor.InsertBefore(enterMethodInstruction, pathParamLoadInstruction);
                        UpdateInstructionReferences(methodDefinition, instructions[0], pathParamLoadInstruction);

                        foreach (var instruction in instructions.Values)
                        {
                            if (instruction.OpCode == OpCodes.Ret)
                            {
                                var loadMethodContextInstruction = ilProcessor.Create(OpCodes.Ldloc, methodContextVariable);
                                var exitMethodInstruction        = ilProcessor.Create(OpCodes.Callvirt, exitMethodReference);
                                ilProcessor.InsertBefore(instruction, exitMethodInstruction);
                                ilProcessor.InsertBefore(exitMethodInstruction, loadMethodContextInstruction);
                                UpdateInstructionReferences(methodDefinition, instruction, loadMethodContextInstruction);
                            }
                        }

                        foreach (var sequencePoint in methodGroup)
                        {
                            var code = sequencePoint.ExtractCode(fileLines);
                            if (code == null || code == "{" || code == "}")
                            {
                                continue;
                            }

                            var instruction = instructions[sequencePoint.Offset];

                            // if the previous instruction is a Prefix instruction then this instruction MUST go with it.
                            // we cannot put an instruction between the two.
                            if (instruction.Previous != null && instruction.Previous.OpCode.OpCodeType == OpCodeType.Prefix)
                            {
                                continue;
                            }

                            var instructionId = ++id;

                            instrumentedAssembly.AddInstruction(sourceRelativePath, new InstrumentedInstruction
                            {
                                Id             = instructionId,
                                StartLine      = sequencePoint.StartLine,
                                EndLine        = sequencePoint.EndLine,
                                StartColumn    = sequencePoint.StartColumn,
                                EndColumn      = sequencePoint.EndColumn,
                                Class          = methodDefinition.DeclaringType.FullName,
                                Method         = methodDefinition.Name,
                                MethodFullName = methodDefinition.FullName,
                                Instruction    = instruction.ToString()
                            });

                            InstrumentInstruction(instructionId, instruction, hitInstructionReference, methodDefinition, ilProcessor, methodContextVariable);
                        }

                        ilProcessor.Body.OptimizeMacros();
                    }
                }

                var miniCoverTempPath = GetMiniCoverTempPath();

                var instrumentedAssemblyFile = Path.Combine(miniCoverTempPath, $"{Guid.NewGuid()}.dll");
                var instrumentedPdbFile      = GetPdbFile(instrumentedAssemblyFile);

                assemblyDefinition.Write(instrumentedAssemblyFile, new WriterParameters {
                    WriteSymbols = true
                });

                instrumentedAssembly.TempAssemblyFile = instrumentedAssemblyFile;
                instrumentedAssembly.TempPdbFile      = instrumentedPdbFile;

                return(instrumentedAssembly);
            }
        }
Пример #3
0
        private void InstrumentInstructions(
            InstrumentationContext context,
            MethodDefinition methodDefinition,
            InstrumentedAssembly instrumentedAssembly,
            Dictionary <int, Instruction> instructionsByOffset,
            ILProcessor ilProcessor,
            VariableDefinition methodContextVariable,
            InstrumentedMethod instrumentedMethod)
        {
            var hitInstructionReference = methodDefinition.Module.GetOrImportReference(hitInstructionMethodInfo);

            foreach (var sequencePoint in methodDefinition.DebugInformation.SequencePoints)
            {
                var document = sequencePoint.Document;

                if (document.FileHasChanged())
                {
                    _logger.LogInformation("Ignoring modified file {file}", document.Url);
                    continue;
                }

                if (sequencePoint.IsHidden)
                {
                    continue;
                }

                var documentUrl = sequencePoint.Document.Url;

                var documentLines = _fileReader.ReadAllLines(new FileInfo(documentUrl));

                var code = documentLines.ExtractCode(
                    sequencePoint.StartLine,
                    sequencePoint.EndLine,
                    sequencePoint.StartColumn,
                    sequencePoint.EndColumn);

                if (code == null || code == "{" || code == "}")
                {
                    continue;
                }

                var instruction = instructionsByOffset[sequencePoint.Offset];

                // if the previous instruction is a Prefix instruction then this instruction MUST go with it.
                // we cannot put an instruction between the two.
                if (instruction.Previous != null && instruction.Previous.OpCode.OpCodeType == OpCodeType.Prefix)
                {
                    continue;
                }

                if (!ilProcessor.Body.Instructions.Contains(instruction))
                {
                    var methodFullName = $"{methodDefinition.DeclaringType.FullName}.{methodDefinition.Name}";
                    _logger.LogWarning("Skipping instruction because it was removed from method {method}", methodFullName);
                    continue;
                }

                var sourceRelativePath = GetSourceRelativePath(context, documentUrl);

                var instructionId = ++context.InstructionId;

                instrumentedAssembly.AddInstruction(sourceRelativePath, new InstrumentedInstruction
                {
                    Id          = instructionId,
                    StartLine   = sequencePoint.StartLine,
                    EndLine     = sequencePoint.EndLine,
                    StartColumn = sequencePoint.StartColumn,
                    EndColumn   = sequencePoint.EndColumn,
                    Instruction = instruction.ToString(),
                    Method      = instrumentedMethod,
                    Code        = code
                });

                ilProcessor.InsertBefore(instruction, new[]
                {
                    ilProcessor.Create(OpCodes.Ldloc, methodContextVariable),
                    ilProcessor.Create(OpCodes.Ldc_I4, instructionId),
                    ilProcessor.Create(OpCodes.Callvirt, hitInstructionReference)
                }, true);
            }
        }