示例#1
0
        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);
            }
        }
示例#2
0
        public InstrumentedAssembly InstrumentAssemblyFile(
            InstrumentationContext context,
            FileInfo assemblyFile)
        {
            var assemblyDirectory = assemblyFile.Directory;

            var resolver = new CustomAssemblyResolver(assemblyDirectory, _assemblyResolverLogger);

            _logger.LogTrace("Assembly resolver search directories: {directories}", new object[] { resolver.GetSearchDirectories() });

            try
            {
                using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyFile.FullName, new ReaderParameters {
                    ReadSymbols = true, AssemblyResolver = resolver
                }))
                {
                    return(InstrumentAssemblyDefinition(context, assemblyDefinition));
                }
            }
            catch (BadImageFormatException)
            {
                _logger.LogInformation("Invalid assembly format");
                return(null);
            }
        }
示例#3
0
        private void VisitAssemblyGroup(
            InstrumentationContext context,
            InstrumentationResult result,
            IEnumerable <FileInfo> groupFiles)
        {
            var firstAssemblyFile = groupFiles.First();

            var instrumentedAssembly = _assemblyInstrumenter.InstrumentAssembly(
                context,
                firstAssemblyFile);

            if (instrumentedAssembly == null)
            {
                return;
            }

            foreach (var assemblyFile in groupFiles)
            {
                var pdbFile            = FileUtils.GetPdbFile(assemblyFile);
                var assemblyBackupFile = FileUtils.GetBackupFile(assemblyFile);
                var pdbBackupFile      = FileUtils.GetBackupFile(pdbFile);

                //Backup
                File.Copy(assemblyFile.FullName, assemblyBackupFile.FullName, true);
                File.Copy(pdbFile.FullName, pdbBackupFile.FullName, true);

                //Override assembly
                File.Copy(instrumentedAssembly.TempAssemblyFile, assemblyFile.FullName, true);
                File.Copy(instrumentedAssembly.TempPdbFile, pdbFile.FullName, true);

                //Copy instrumentation dependencies
                var assemblyDirectory = assemblyFile.Directory;

                var hitServicesPath    = Path.GetFileName(hitServicesAssembly.Location);
                var newHitServicesPath = Path.Combine(assemblyDirectory.FullName, hitServicesPath);
                if (!File.Exists(newHitServicesPath))
                {
                    File.Copy(hitServicesAssembly.Location, newHitServicesPath, true);
                    result.AddExtraAssembly(newHitServicesPath);
                }

                instrumentedAssembly.AddLocation(
                    assemblyFile.FullName,
                    assemblyBackupFile.FullName,
                    pdbFile.FullName,
                    pdbBackupFile.FullName
                    );

                var hitServicesAssemblyVersion = FileVersionInfo.GetVersionInfo(hitServicesAssembly.Location);
                foreach (var depsJsonFile in assemblyDirectory.GetFiles("*.deps.json"))
                {
                    DepsJsonUtils.PatchDepsJson(depsJsonFile, hitServicesAssemblyVersion.ProductVersion);
                }
            }

            result.AddInstrumentedAssembly(instrumentedAssembly);

            File.Delete(instrumentedAssembly.TempAssemblyFile);
            File.Delete(instrumentedAssembly.TempPdbFile);
        }
示例#4
0
        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);
            }
        }
示例#5
0
        public InstrumentationResult Execute(InstrumentationContext context)
        {
            context.Workdir = context.Workdir.AddEndingDirectorySeparator();

            var result = new InstrumentationResult
            {
                SourcePath = context.Workdir.FullName,
                HitsPath   = context.HitsPath
            };

            var assemblyGroups = context.Assemblies
                                 .Where(ShouldInstrumentAssemblyFile)
                                 .GroupBy(FileUtils.GetFileHash)
                                 .ToArray();

            foreach (var assemblyGroup in assemblyGroups)
            {
                VisitAssemblyGroup(
                    context,
                    result,
                    assemblyGroup);
            }

            return(result);
        }
示例#6
0
        private static string GetSourceRelativePath(InstrumentationContext context, string path)
        {
            var    file         = new Uri(path);
            var    folder       = new Uri(context.Workdir.FullName);
            string relativePath =
                Uri.UnescapeDataString(
                    folder.MakeRelativeUri(file)
                    .ToString()
                    .Replace('/', Path.DirectorySeparatorChar)
                    );

            return(relativePath);
        }
示例#7
0
        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);
        }
示例#8
0
        private void VisitAssemblyGroup(
            InstrumentationContext context,
            InstrumentationResult result,
            IEnumerable <FileInfo> assemblyFiles)
        {
            using (_logger.BeginScope("Checking assembly files {assemblies}", assemblyFiles.Select(f => f.FullName), LogLevel.Information))
            {
                var instrumentedAssembly = _assemblyInstrumenter.InstrumentAssemblyFile(
                    context,
                    assemblyFiles.First());

                if (instrumentedAssembly == null)
                {
                    return;
                }

                foreach (var assemblyFile in assemblyFiles)
                {
                    if (_loadedAssemblyFiles.Contains(assemblyFile.FullName))
                    {
                        _logger.LogInformation("Skipping loaded assembly {assemblyFile}", assemblyFile.FullName);
                        continue;
                    }

                    var pdbFile            = FileUtils.GetPdbFile(assemblyFile);
                    var assemblyBackupFile = FileUtils.GetBackupFile(assemblyFile);
                    var pdbBackupFile      = FileUtils.GetBackupFile(pdbFile);

                    //Backup
                    File.Copy(assemblyFile.FullName, assemblyBackupFile.FullName, true);
                    File.Copy(pdbFile.FullName, pdbBackupFile.FullName, true);

                    //Override assembly
                    File.Copy(instrumentedAssembly.TempAssemblyFile, assemblyFile.FullName, true);
                    File.Copy(instrumentedAssembly.TempPdbFile, pdbFile.FullName, true);

                    //Copy instrumentation dependencies
                    var assemblyDirectory = assemblyFile.Directory;

                    var hitServicesPath    = Path.GetFileName(hitServicesAssembly.Location);
                    var newHitServicesPath = Path.Combine(assemblyDirectory.FullName, hitServicesPath);
                    File.Copy(hitServicesAssembly.Location, newHitServicesPath, true);
                    result.AddExtraAssembly(newHitServicesPath);

                    instrumentedAssembly.AddLocation(
                        assemblyFile.FullName,
                        assemblyBackupFile.FullName,
                        pdbFile.FullName,
                        pdbBackupFile.FullName
                        );

                    var hitServicesAssemblyVersion = FileVersionInfo.GetVersionInfo(hitServicesAssembly.Location);
                    foreach (var depsJsonFile in assemblyDirectory.GetFiles("*.deps.json"))
                    {
                        DepsJsonUtils.PatchDepsJson(depsJsonFile, hitServicesAssemblyVersion.ProductVersion);
                    }
                }

                result.AddInstrumentedAssembly(instrumentedAssembly);

                File.Delete(instrumentedAssembly.TempAssemblyFile);
                File.Delete(instrumentedAssembly.TempPdbFile);
            }
        }
示例#9
0
        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);
                }
            }
        }
示例#10
0
 private void InstrumentInstructions(
     InstrumentationContext context,
     MethodDefinition methodDefinition,
     InstrumentedAssembly instrumentedAssembly,
     IList <(SequencePoint sequencePoint, Instruction instruction)> sequencePointsInstructions,
示例#11
0
        public void InstrumentMethod(
            InstrumentationContext context,
            bool instrumentInstructions,
            MethodDefinition methodDefinition,
            InstrumentedAssembly instrumentedAssembly)
        {
            var originalMethod = methodDefinition.ResolveOriginalMethod();

            var instrumentedMethod = instrumentedAssembly.AddMethod(new InstrumentedMethod
            {
                Class    = originalMethod.DeclaringType.FullName,
                Name     = originalMethod.Name,
                FullName = originalMethod.FullName,
            });

            var methodContextClassReference = methodDefinition.Module.GetOrImportReference(methodContextType);
            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();
        }
示例#12
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);
            }
        }