public void InstrumentType( InstrumentationContext context, TypeDefinition typeDefinition, InstrumentedAssembly instrumentedAssembly) { foreach (var methodDefinition in typeDefinition.Methods) { if (!methodDefinition.HasBody || !methodDefinition.DebugInformation.HasSequencePoints) { continue; } var methodDocuments = methodDefinition.GetAllDocuments(); var isSource = methodDocuments.Any(d => context.IsSource(d.Url)); var isTest = methodDocuments.Any(d => context.IsTest(d.Url)); if (!isSource && !isTest) { continue; } _methodInstrumenter.InstrumentMethod( context, isSource, methodDefinition, instrumentedAssembly); } foreach (var nestedType in typeDefinition.NestedTypes) { InstrumentType(context, nestedType, instrumentedAssembly); } }
public void InstrumentType( InstrumentationContext context, TypeDefinition typeDefinition, InstrumentedAssembly instrumentedAssembly) { var typeDocuments = typeDefinition.GetAllDocuments(); if (!typeDocuments.Any(d => context.IsSource(d) || context.IsTest(d))) { return; } var methods = typeDefinition.GetAllMethods(); foreach (var methodDefinition in methods) { var methodDocuments = methodDefinition.GetAllDocuments(); var isSource = methodDocuments.Any(d => context.IsSource(d)); var isTest = methodDocuments.Any(d => context.IsTest(d)); if (!isSource && !isTest) { continue; } _methodInstrumenter.InstrumentMethod( context, isSource, methodDefinition, instrumentedAssembly); } }
private static XElement CreateClassesElement(InstrumentedAssembly assembly, HitsInfo hitsInfo) { return(new XElement( XName.Get("classes"), assembly.SourceFiles .Select(kv => CreateClassElement(kv.Key, kv.Value, hitsInfo)) )); }
public static InstrumentedAssembly Instrument(this MethodDefinition methodDefinition) { var documentsUrls = methodDefinition.GetAllDocuments().Select(d => d.Url).Distinct().ToArray(); var instrumentationContext = CreateInstrumentationContext(documentsUrls); var instrumentedAssembly = new InstrumentedAssembly(methodDefinition.Module.Assembly.Name.Name); var methodInstrumenter = CreateMethodInstrumenter(); methodInstrumenter.InstrumentMethod(instrumentationContext, true, methodDefinition, instrumentedAssembly); return(instrumentedAssembly); }
public static InstrumentedAssembly Instrument(this TypeDefinition typeDefinition) { var documents = typeDefinition.GetAllDocuments(); var instrumentationContext = CreateInstrumentationContext(documents); var instrumentedAssembly = new InstrumentedAssembly(typeDefinition.Module.Assembly.Name.Name); var methodInstrumenter = CreateMethodInstrumenter(); var typeInstrumenter = new TypeInstrumenter(methodInstrumenter); typeInstrumenter.InstrumentType(instrumentationContext, typeDefinition, instrumentedAssembly); return(instrumentedAssembly); }
private static CloverCounter CountPackageMetrics(InstrumentedAssembly assembly, HitsInfo hits) { return(assembly.SourceFiles .Select(t => CountFileMetrics(t, hits)) .Aggregate(new CloverCounter(), (counter, next) => { counter.Add(next); counter.Files += 1; return counter; })); }
private static IEnumerable <XElement> CreateFilesElement(InstrumentedAssembly assembly, HitsInfo hits) { return(assembly.SourceFiles.Select(file => new XElement( XName.Get("file"), new XAttribute(XName.Get("name"), Path.GetFileName(file.Path)), new XAttribute(XName.Get("path"), file.Path), CreateMetricsElement(CountFileMetrics(file, hits)), CreateClassesElement(file.Sequences, hits), CreateLinesElement(file.Sequences, hits) ))); }
public static InstrumentedAssembly Instrument(this TypeDefinition typeDefinition) { var documentsUrls = typeDefinition.GetAllDocuments(true).Select(d => d.Url).Distinct().ToArray(); var instrumentationContext = CreateInstrumentationContext(documentsUrls); var instrumentedAssembly = new InstrumentedAssembly(typeDefinition.Module.Assembly.Name.Name); var methodInstrumenter = CreateMethodInstrumenter(); var typeInstrumenter = new TypeInstrumenter(methodInstrumenter); typeInstrumenter.InstrumentType(instrumentationContext, typeDefinition, instrumentedAssembly); return(instrumentedAssembly); }
private void InstrumentMethod(ModuleDefinition moduleDefinition, MethodDefinition methodDefinition, IEnumerable <SequencePoint> sequencePoints, TypeReference methodContextClassReference, MethodReference enterMethodReference, MethodReference exitMethodReference, string[] fileLines, InstrumentedAssembly instrumentedAssembly, string sourceRelativePath, MethodReference hitInstructionReference, string hitsFile) { var ilProcessor = methodDefinition.Body.GetILProcessor(); ilProcessor.Body.InitLocals = true; 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); var firstInstruction = instructions[0]; var loadMethodContextInstruction = ilProcessor.Create(OpCodes.Ldloc, methodContextVariable); var exitMethodInstruction = ilProcessor.Create(OpCodes.Callvirt, exitMethodReference); ilProcessor.EncapsulateMethodBodyWithTryFinallyBlock(firstInstruction, (processor, instruction) => { ilProcessor.InsertBefore(instruction, exitMethodInstruction); ilProcessor.InsertBefore(exitMethodInstruction, loadMethodContextInstruction); }); var currentFirstInstruction = ilProcessor.Body.Instructions.First(); ilProcessor.InsertBefore(currentFirstInstruction, storeMethodResultInstruction); ilProcessor.InsertBefore(storeMethodResultInstruction, enterMethodInstruction); ilProcessor.InsertBefore(enterMethodInstruction, pathParamLoadInstruction); ilProcessor.ReplaceInstructionReferences(currentFirstInstruction, pathParamLoadInstruction); //InstrumentInstructions(methodDefinition, sequencePoints, fileLines, instrumentedAssembly, sourceRelativePath, hitInstructionReference, instructions, ilProcessor, methodContextVariable); foreach (var instruction in ilProcessor.Body.Instructions.ToArray()) { if (instruction.OpCode == OpCodes.Tail) { var noOpInstruction = ilProcessor.Create(OpCodes.Nop); ilProcessor.Replace(instruction, noOpInstruction); ilProcessor.ReplaceInstructionReferences(instruction, noOpInstruction); } } ilProcessor.Body.OptimizeMacros(); }
public void InstrumentMethod( MethodDefinition methodDefinition, IEnumerable <SequencePoint> sequencePoints, TypeReference methodContextClassReference, MethodReference enterMethodReference, MethodReference exitMethodReference, string[] fileLines, InstrumentedAssembly instrumentedAssembly, string sourceRelativePath, MethodReference hitInstructionReference) { var ilProcessor = methodDefinition.Body.GetILProcessor(); ilProcessor.Body.InitLocals = true; ilProcessor.Body.SimplifyMacros(); var methodContextVariable = new VariableDefinition(methodContextClassReference); ilProcessor.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.RemoveTailInstructions(); var instructions = ilProcessor.Body.Instructions.ToDictionary(i => i.Offset); ilProcessor.ForEachReturn((processor, instruction) => { var loadMethodContextInstruction = ilProcessor.Create(OpCodes.Ldloc, methodContextVariable); var exitMethodInstruction = ilProcessor.Create(OpCodes.Callvirt, exitMethodReference); ilProcessor.InsertBefore(instruction, exitMethodInstruction); ilProcessor.InsertBefore(exitMethodInstruction, loadMethodContextInstruction); ilProcessor.ReplaceInstructionReferences(instruction, loadMethodContextInstruction); }); var currentFirstInstruction = ilProcessor.Body.Instructions.First(); ilProcessor.InsertBefore(currentFirstInstruction, storeMethodResultInstruction); ilProcessor.InsertBefore(storeMethodResultInstruction, enterMethodInstruction); ilProcessor.InsertBefore(enterMethodInstruction, pathParamLoadInstruction); ilProcessor.ReplaceInstructionReferences(currentFirstInstruction, pathParamLoadInstruction); InstrumentInstructions(methodDefinition, sequencePoints, fileLines, instrumentedAssembly, sourceRelativePath, hitInstructionReference, instructions, ilProcessor, methodContextVariable); ilProcessor.Body.OptimizeMacros(); }
private static XElement CreatePackageElement(InstrumentedAssembly assembly, HitsInfo hitsInfo) { var allLines = assembly.SourceFiles .SelectMany(kvFile => kvFile.Value.Sequences) .SelectMany(i => i.GetLines()) .Distinct() .Count(); var coveredLines = assembly.SourceFiles .SelectMany(kvFile => kvFile.Value.Sequences) .Where(h => hitsInfo.WasHit(h.HitId)) .SelectMany(i => i.GetLines()) .Distinct() .Count(); var allBranches = assembly.SourceFiles .SelectMany(kvFile => kvFile.Value.Sequences) .SelectMany(i => i.Conditions) .SelectMany(c => c.Branches) .Count(); var coveredBranches = assembly.SourceFiles .SelectMany(kvFile => kvFile.Value.Sequences) .SelectMany(i => i.Conditions) .SelectMany(c => c.Branches) .Where(b => hitsInfo.WasHit(b.HitId)) .Count(); var lineRate = allLines == 0 ? 1d : (double)coveredLines / (double)allLines; var branchRate = allBranches == 0 ? 1d : (double)coveredBranches / (double)allBranches; return(new XElement( XName.Get("package"), new XAttribute(XName.Get("name"), assembly.Name), new XAttribute(XName.Get("line-rate"), lineRate), new XAttribute(XName.Get("branch-rate"), branchRate), new XAttribute(XName.Get("complexity"), 0), CreateClassesElement(assembly, hitsInfo) )); }
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); } }
private void InstrumentInstructions( IInstrumentationContext context, MethodDefinition methodDefinition, InstrumentedAssembly instrumentedAssembly, IList <(SequencePoint sequencePoint,
public void InstrumentMethod( IInstrumentationContext context, bool instrumentInstructions, MethodDefinition methodDefinition, InstrumentedAssembly instrumentedAssembly) { var originalMethod = methodDefinition.ResolveOriginalMethod(); var instrumentedMethod = instrumentedAssembly.GetOrAddMethod( originalMethod.DeclaringType.FullName, originalMethod.Name, originalMethod.FullName ); var methodContextClassReference = methodDefinition.Module.GetOrImportReference(methodScopeType); var enterMethodReference = methodDefinition.Module.GetOrImportReference(enterMethodInfo); var disposeMethodReference = methodDefinition.Module.GetOrImportReference(disposeMethodInfo); var sequencePointsInstructions = methodDefinition.MapSequencePointsToInstructions().ToArray(); var ilProcessor = methodDefinition.Body.GetILProcessor(); ilProcessor.Body.InitLocals = true; ilProcessor.Body.SimplifyMacros(); var methodContextVariable = new VariableDefinition(methodContextClassReference); ilProcessor.Body.Variables.Add(methodContextVariable); ilProcessor.RemoveTailInstructions(); var endFinally = ilProcessor.EncapsulateWithTryFinally(); ilProcessor.InsertBefore(endFinally, new[] { ilProcessor.Create(OpCodes.Ldloc, methodContextVariable), ilProcessor.Create(OpCodes.Callvirt, disposeMethodReference) }, true); ilProcessor.InsertBefore(ilProcessor.Body.Instructions[0], new[] { ilProcessor.Create(OpCodes.Ldstr, context.HitsPath), ilProcessor.Create(OpCodes.Ldstr, originalMethod.DeclaringType.Module.Assembly.Name.Name), ilProcessor.Create(OpCodes.Ldstr, originalMethod.DeclaringType.FullName), ilProcessor.Create(OpCodes.Ldstr, originalMethod.Name), ilProcessor.Create(OpCodes.Call, enterMethodReference), ilProcessor.Create(OpCodes.Stloc, methodContextVariable) }, true); if (instrumentInstructions && !methodDefinition.IsExcludedFromCodeCoverage()) { InstrumentInstructions( context, methodDefinition, instrumentedAssembly, sequencePointsInstructions, ilProcessor, methodContextVariable, instrumentedMethod); } ilProcessor.Body.OptimizeMacros(); }
private InstrumentedAssembly InstrumentAssemblyDefinition( InstrumentationContext context, AssemblyDefinition assemblyDefinition) { if (assemblyDefinition.CustomAttributes.Any(a => a.AttributeType.Name == "InstrumentedAttribute")) { _logger.LogInformation("Already instrumented"); return(null); } var assemblyDocuments = assemblyDefinition.GetAllDocuments(); if (!assemblyDocuments.Any(d => context.IsSource(d.Url) || context.IsTest(d.Url))) { _logger.LogInformation("No link to source files or test files"); return(null); } var changedDocuments = assemblyDocuments.Where(d => d.FileHasChanged()).ToArray(); if (changedDocuments.Any()) { if (_logger.IsEnabled(LogLevel.Debug)) { var changedFiles = changedDocuments.Select(d => d.Url).Distinct().ToArray(); _logger.LogDebug("Source files has changed: {changedFiles}", new object[] { changedFiles }); } else { _logger.LogInformation("Source files has changed"); } return(null); } _logger.LogInformation("Instrumenting"); var instrumentedAssembly = new InstrumentedAssembly(assemblyDefinition.Name.Name); var instrumentedAttributeReference = assemblyDefinition.MainModule.ImportReference(instrumentedAttributeConstructor); assemblyDefinition.CustomAttributes.Add(new CustomAttribute(instrumentedAttributeReference)); foreach (var type in assemblyDefinition.MainModule.GetTypes()) { _typeInstrumenter.InstrumentType( context, type, instrumentedAssembly); } var miniCoverTempPath = GetMiniCoverTempPath(); var instrumentedAssemblyFile = new FileInfo(Path.Combine(miniCoverTempPath, $"{Guid.NewGuid()}.dll")); var instrumentedPdbFile = FileUtils.GetPdbFile(instrumentedAssemblyFile); assemblyDefinition.Write(instrumentedAssemblyFile.FullName, new WriterParameters { WriteSymbols = true }); instrumentedAssembly.TempAssemblyFile = instrumentedAssemblyFile.FullName; instrumentedAssembly.TempPdbFile = instrumentedPdbFile.FullName; return(instrumentedAssembly); }
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); } }
public InstrumentedAssembly InstrumentAssembly( InstrumentationContext context, FileInfo assemblyFile) { var assemblyDirectory = assemblyFile.Directory; using (_logger.BeginScope("Checking assembly file {assembly}", assemblyFile.FullName, LogLevel.Information)) { if (assemblyFile.Name == "MiniCover.HitServices.dll") { _logger.LogInformation("Skipping HitServices"); return(null); } if (_loadedAssemblyFiles.Contains(assemblyFile.FullName)) { _logger.LogInformation("Can't instrument loaded assembly"); return(null); } var resolver = new CustomAssemblyResolver(assemblyDirectory, _assemblyResolverLogger); _logger.LogTrace("Assembly resolver search directories: {directories}", new object[] { resolver.GetSearchDirectories() }); using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyFile.FullName, new ReaderParameters { ReadSymbols = true, AssemblyResolver = resolver })) { if (assemblyDefinition.CustomAttributes.Any(a => a.AttributeType.Name == "InstrumentedAttribute")) { _logger.LogInformation("Already instrumented"); return(null); } var assemblyDocuments = assemblyDefinition.GetAllDocuments(); if (!assemblyDocuments.Any(d => context.IsSource(d) || context.IsTest(d))) { _logger.LogInformation("No link to source files or test files"); return(null); } _logger.LogInformation("Instrumenting"); var instrumentedAssembly = new InstrumentedAssembly(assemblyDefinition.Name.Name); var instrumentedAttributeReference = assemblyDefinition.MainModule.ImportReference(instrumentedAttributeConstructor); assemblyDefinition.CustomAttributes.Add(new CustomAttribute(instrumentedAttributeReference)); foreach (var type in assemblyDefinition.MainModule.GetTypes()) { _typeInstrumenter.InstrumentType( context, type, instrumentedAssembly); } var miniCoverTempPath = GetMiniCoverTempPath(); var instrumentedAssemblyFile = new FileInfo(Path.Combine(miniCoverTempPath, $"{Guid.NewGuid()}.dll")); var instrumentedPdbFile = FileUtils.GetPdbFile(instrumentedAssemblyFile); assemblyDefinition.Write(instrumentedAssemblyFile.FullName, new WriterParameters { WriteSymbols = true }); instrumentedAssembly.TempAssemblyFile = instrumentedAssemblyFile.FullName; instrumentedAssembly.TempPdbFile = instrumentedPdbFile.FullName; return(instrumentedAssembly); } } }
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; InstrumentMethod(methodDefinition, methodGroup, methodContextClassReference, enterMethodReference, exitMethodReference, fileLines, instrumentedAssembly, sourceRelativePath, hitInstructionReference); } } 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); } }
private InstrumentedAssembly InstrumentAssemblyDefinition( IInstrumentationContext context, AssemblyDefinition assemblyDefinition) { if (assemblyDefinition.CustomAttributes.Any(a => a.AttributeType.Name == "InstrumentedAttribute")) { _logger.LogInformation("Already instrumented"); return(null); } var assemblyDocuments = assemblyDefinition.GetAllDocuments(); var changedDocuments = assemblyDocuments.Where(d => d.FileHasChanged()).ToArray(); if (changedDocuments.Any()) { if (_logger.IsEnabled(LogLevel.Debug)) { var changedFiles = changedDocuments.Select(d => d.Url).Distinct().ToArray(); _logger.LogDebug("Source files has changed: {changedFiles}", new object[] { changedFiles }); } else { _logger.LogInformation("Source files has changed"); } return(null); } var instrumentedAssembly = new InstrumentedAssembly(assemblyDefinition.Name.Name); var instrumentedAttributeReference = assemblyDefinition.MainModule.ImportReference(instrumentedAttributeConstructor); assemblyDefinition.CustomAttributes.Add(new CustomAttribute(instrumentedAttributeReference)); foreach (var typeDefinition in assemblyDefinition.MainModule.GetTypes()) { if (typeDefinition.FullName == "<Module>" || typeDefinition.FullName == "AutoGeneratedProgram" || typeDefinition.DeclaringType != null) { continue; } _typeInstrumenter.InstrumentType( context, typeDefinition, instrumentedAssembly); } if (!instrumentedAssembly.Methods.Any()) { _logger.LogInformation("Nothing to instrument"); return(null); } _logger.LogInformation("Assembly instrumented"); var miniCoverTempPath = GetMiniCoverTempPath(); var instrumentedAssemblyFile = _fileSystem.FileInfo.FromFileName(Path.Combine(miniCoverTempPath, $"{Guid.NewGuid()}.dll")); var instrumentedPdbFile = FileUtils.GetPdbFile(instrumentedAssemblyFile); assemblyDefinition.Write(instrumentedAssemblyFile.FullName, new WriterParameters { WriteSymbols = true }); instrumentedAssembly.TempAssemblyFile = instrumentedAssemblyFile.FullName; instrumentedAssembly.TempPdbFile = instrumentedPdbFile.FullName; return(instrumentedAssembly); }
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); } }
public void InstrumentMethod( InstrumentationContext context, bool instrumentInstructions, MethodDefinition methodDefinition, InstrumentedAssembly instrumentedAssembly) { var originalMethod = ResolveOriginalMethod(methodDefinition); var instrumentedMethod = instrumentedAssembly.AddMethod(new InstrumentedMethod { Class = originalMethod.DeclaringType.FullName, Name = originalMethod.Name, FullName = originalMethod.FullName, }); var enterMethodInfo = hitServiceType.GetMethod("EnterMethod"); var disposeMethodInfo = methodContextType.GetMethod("Dispose"); var methodContextClassReference = methodDefinition.Module.GetOrImportReference(methodContextType); var enterMethodReference = methodDefinition.Module.GetOrImportReference(enterMethodInfo); var disposeMethodReference = methodDefinition.Module.GetOrImportReference(disposeMethodInfo); var ilProcessor = methodDefinition.Body.GetILProcessor(); ilProcessor.Body.InitLocals = true; ilProcessor.Body.SimplifyMacros(); var methodContextVariable = new VariableDefinition(methodContextClassReference); ilProcessor.Body.Variables.Add(methodContextVariable); ilProcessor.RemoveTailInstructions(); var instructions = ilProcessor.Body.Instructions.ToDictionary(i => i.Offset); var endFinally = ilProcessor.EncapsulateWithTryFinally(); ilProcessor.InsertBefore(endFinally, new[] { ilProcessor.Create(OpCodes.Ldloc, methodContextVariable), ilProcessor.Create(OpCodes.Callvirt, disposeMethodReference) }, true); ilProcessor.InsertBefore(ilProcessor.Body.Instructions[0], new[] { ilProcessor.Create(OpCodes.Ldstr, context.HitsPath), ilProcessor.Create(OpCodes.Ldstr, originalMethod.DeclaringType.Module.Assembly.Name.Name), ilProcessor.Create(OpCodes.Ldstr, originalMethod.DeclaringType.FullName), ilProcessor.Create(OpCodes.Ldstr, originalMethod.Name), ilProcessor.Create(OpCodes.Call, enterMethodReference), ilProcessor.Create(OpCodes.Stloc, methodContextVariable) }, true); if (instrumentInstructions && !methodDefinition.IsExcludedFromCodeCoverage()) { InstrumentInstructions( context, methodDefinition, instrumentedAssembly, instructions, ilProcessor, methodContextVariable, instrumentedMethod); } ilProcessor.Body.OptimizeMacros(); }