Skip to content

Commit

Permalink
Handle duplicated assemblies as same assembly
Browse files Browse the repository at this point in the history
  • Loading branch information
lucaslorentz committed Feb 10, 2018
1 parent 1bde5e8 commit f4d5435
Show file tree
Hide file tree
Showing 14 changed files with 250 additions and 174 deletions.
210 changes: 116 additions & 94 deletions src/MiniCover/Instrumentation/Instrumenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.IO;
using System.Linq;
using System.Reflection;
using MiniCover.Utils;

namespace MiniCover.Instrumentation
{
Expand Down Expand Up @@ -47,30 +48,22 @@ public InstrumentationResult Execute()
InstrumentAssembly(fileName);
}

foreach (var assembly in result.Assemblies)
{
assembly.Value.Files = assembly.Value.Files.OrderBy(kv => kv.Key).ToDictionary(kv => kv.Key, kv => kv.Value);
}

return result;
}

private void InstrumentAssembly(string assemblyFile)
{
var pdbFile = Path.ChangeExtension(assemblyFile, "pdb");
if (!File.Exists(pdbFile))
return;

if (assemblyFile.EndsWith("uninstrumented.dll"))
return;

var pdbFile = Path.ChangeExtension(assemblyFile, "pdb");
var assemblyBackupFile = Path.ChangeExtension(assemblyFile, "uninstrumented.dll");
if (File.Exists(assemblyBackupFile))
File.Copy(assemblyBackupFile, assemblyFile, true);

var pdbBackupFile = Path.ChangeExtension(pdbFile, "uninstrumented.pdb");
if (File.Exists(pdbBackupFile))
File.Copy(pdbBackupFile, pdbFile, true);

RestoreBackups(assemblyFile, pdbFile, assemblyBackupFile, pdbBackupFile);

if (!File.Exists(pdbFile))
return;

if (!HasSourceFiles(assemblyFile))
return;
Expand All @@ -85,109 +78,138 @@ private void InstrumentAssembly(string assemblyFile)

var assemblyDirectory = Path.GetDirectoryName(assemblyFile);

var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(assemblyDirectory);
var assemblyHash = FileUtils.GetFileHash(assemblyFile);

using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyBackupFile, new ReaderParameters { ReadSymbols = true, AssemblyResolver = resolver }))
{
var instrumentedAssembly = result.AddInstrumentedAssembly(
assemblyDefinition.Name.Name,
Path.GetFullPath(assemblyBackupFile),
Path.GetFullPath(assemblyFile),
Path.GetFullPath(pdbBackupFile),
Path.GetFullPath(pdbFile)
);
var instrumentedAssembly = result.GetInstrumentedAssembly(assemblyHash);

var instrumentedConstructor = typeof(InstrumentedAttribute).GetConstructors().First();
var instrumentedReference = assemblyDefinition.MainModule.ImportReference(instrumentedConstructor);
assemblyDefinition.CustomAttributes.Add(new CustomAttribute(instrumentedReference));
if (instrumentedAssembly == null)
{
var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(assemblyDirectory);

var miniCoverAssemblyPath = typeof(HitService).GetTypeInfo().Assembly.Location;
var miniCoverAssemblyName = Path.GetFileName(miniCoverAssemblyPath);
var newMiniCoverAssemblyPath = Path.Combine(assemblyDirectory, miniCoverAssemblyName);
File.Copy(miniCoverAssemblyPath, newMiniCoverAssemblyPath, true);
result.AddExtraAssembly(newMiniCoverAssemblyPath);
using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyBackupFile, new ReaderParameters { ReadSymbols = true, AssemblyResolver = resolver }))
{
instrumentedAssembly = result.AddInstrumentedAssembly(assemblyHash, assemblyDefinition.Name.Name);

CreateAssemblyInit(assemblyDefinition);
var instrumentedConstructor = typeof(InstrumentedAttribute).GetConstructors().First();
var instrumentedReference = assemblyDefinition.MainModule.ImportReference(instrumentedConstructor);
assemblyDefinition.CustomAttributes.Add(new CustomAttribute(instrumentedReference));

var hitMethodInfo = typeof(HitService).GetMethod("Hit");
var hitMethodReference = assemblyDefinition.MainModule.ImportReference(hitMethodInfo);
CreateAssemblyInit(assemblyDefinition);

var methods = assemblyDefinition.GetAllMethods();
var hitMethodInfo = typeof(HitService).GetMethod("Hit");
var hitMethodReference = assemblyDefinition.MainModule.ImportReference(hitMethodInfo);

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

foreach (var documentGroup in documentsGroups)
{
var sourceRelativePath = GetSourceRelativePath(documentGroup.Key.Url);
if (sourceRelativePath == null)
continue;
var methods = assemblyDefinition.GetAllMethods();

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)
var documentsGroups = methods
.SelectMany(m => m.DebugInformation.SequencePoints, (m, s) => new
{
Method = m,
SequencePoint = s,
Document = s.Document
})
.GroupBy(j => j.Document)
.ToArray();

foreach (var methodGroup in methodGroups)
foreach (var documentGroup in documentsGroups)
{
var ilProcessor = methodGroup.Key.Body.GetILProcessor();
var sourceRelativePath = GetSourceRelativePath(documentGroup.Key.Url);
if (sourceRelativePath == null)
continue;

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

var instructions = methodGroup.Key.Body.Instructions.ToDictionary(i => i.Offset);
var fileLines = File.ReadAllLines(documentGroup.Key.Url);

foreach (var sequencePoint in methodGroup)
{
var code = ExtractCode(fileLines, sequencePoint);
if (code == null || code == "{" || code == "}")
continue;
var methodGroups = documentGroup
.GroupBy(j => j.Method, j => j.SequencePoint)
.ToArray();

var instruction = instructions[sequencePoint.Offset];
foreach (var methodGroup in methodGroups)
{
var ilProcessor = methodGroup.Key.Body.GetILProcessor();

// 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)
return;
ilProcessor.Body.SimplifyMacros();

var instructionId = ++id;
var instructions = methodGroup.Key.Body.Instructions.ToDictionary(i => i.Offset);

instrumentedAssembly.AddInstruction(sourceRelativePath, new InstrumentedInstruction
foreach (var sequencePoint in methodGroup)
{
Id = instructionId,
StartLine = sequencePoint.StartLine,
EndLine = sequencePoint.EndLine,
StartColumn = sequencePoint.StartColumn,
EndColumn = sequencePoint.EndColumn,
Assembly = assemblyDefinition.Name.Name,
Class = methodGroup.Key.DeclaringType.FullName,
Method = methodGroup.Key.Name,
MethodFullName = methodGroup.Key.FullName,
Instruction = instruction.ToString()
});

InstrumentInstruction(instructionId, instruction, hitMethodReference, methodGroup.Key, ilProcessor);
var code = ExtractCode(fileLines, sequencePoint);
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)
return;

var instructionId = ++id;

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

InstrumentInstruction(instructionId, instruction, hitMethodReference, methodGroup.Key, ilProcessor);
}

ilProcessor.Body.OptimizeMacros();
}

ilProcessor.Body.OptimizeMacros();
}

assemblyDefinition.Write(assemblyFile, new WriterParameters { WriteSymbols = true });
}
}
else
{
var firstLocation = instrumentedAssembly.Locations.First();
File.Copy(firstLocation.File, assemblyFile, true);
File.Copy(firstLocation.PdbFile, pdbFile, true);
}

instrumentedAssembly.AddLocation(
Path.GetFullPath(assemblyFile),
Path.GetFullPath(assemblyBackupFile),
Path.GetFullPath(pdbFile),
Path.GetFullPath(pdbBackupFile)
);

//Copy instrumentation dependencies
var miniCoverAssemblyPath = typeof(HitService).GetTypeInfo().Assembly.Location;
var miniCoverAssemblyName = Path.GetFileName(miniCoverAssemblyPath);
var newMiniCoverAssemblyPath = Path.Combine(assemblyDirectory, miniCoverAssemblyName);
File.Copy(miniCoverAssemblyPath, newMiniCoverAssemblyPath, true);
result.AddExtraAssembly(newMiniCoverAssemblyPath);
}

private static void RestoreBackups(string assemblyFile, string pdbFile, string assemblyBackupFile, string pdbBackupFile)
{
if (File.Exists(assemblyBackupFile))
{
File.Copy(assemblyBackupFile, assemblyFile, true);
File.Delete(assemblyBackupFile);
}

assemblyDefinition.Write(assemblyFile, new WriterParameters { WriteSymbols = true });
if (File.Exists(pdbBackupFile))
{
File.Copy(pdbBackupFile, pdbFile, true);
File.Delete(pdbBackupFile);
}
}

Expand Down
21 changes: 12 additions & 9 deletions src/MiniCover/Instrumentation/Uninstrumenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,21 @@ public static class Uninstrumenter
{
public static void Execute(InstrumentationResult result)
{
foreach (var assembly in result.Assemblies.Values)
foreach (var assembly in result.Assemblies)
{
if (File.Exists(assembly.BackupFile))
foreach (var assemblyLocation in assembly.Locations)
{
File.Copy(assembly.BackupFile, assembly.File, true);
File.Delete(assembly.BackupFile);
}
if (File.Exists(assemblyLocation.BackupFile))
{
File.Copy(assemblyLocation.BackupFile, assemblyLocation.File, true);
File.Delete(assemblyLocation.BackupFile);
}

if (File.Exists(assembly.BackupPdbFile))
{
File.Copy(assembly.BackupPdbFile, assembly.PdbFile, true);
File.Delete(assembly.BackupPdbFile);
if (File.Exists(assemblyLocation.BackupPdbFile))
{
File.Copy(assemblyLocation.BackupPdbFile, assemblyLocation.PdbFile, true);
File.Delete(assemblyLocation.BackupPdbFile);
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions src/MiniCover/Model/AssemblyLocation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Collections.Generic;

namespace MiniCover.Model
{
public class AssemblyLocation
{
public string File { get; set; }
public string BackupFile { get; set; }
public string PdbFile { get; set; }
public string BackupPdbFile { get; set; }
}
}
44 changes: 30 additions & 14 deletions src/MiniCover/Model/InstrumentationResult.cs
Original file line number Diff line number Diff line change
@@ -1,43 +1,59 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using System;

namespace MiniCover.Model
{
public class InstrumentationResult
{
private Dictionary<string, InstrumentedAssembly> _assemblies;

public InstrumentationResult()
{
_assemblies = new Dictionary<string, InstrumentedAssembly>();
}

[JsonConstructor]
public InstrumentationResult(IEnumerable<InstrumentedAssembly> assemblies)
{
_assemblies = assemblies.ToDictionary(a => a.Hash);
}

[JsonProperty(Order = -2)]
public string SourcePath { get; set; }

[JsonProperty(Order = -2)]
public string HitsFile { get; set; }

public List<string> ExtraAssemblies = new List<string>();
public Dictionary<string, InstrumentedAssembly> Assemblies = new Dictionary<string, InstrumentedAssembly>();
public HashSet<string> ExtraAssemblies = new HashSet<string>();

public InstrumentedAssembly AddInstrumentedAssembly(string name, string backupFile, string file, string backupPdbFile, string pdbFile)
public IEnumerable<InstrumentedAssembly> Assemblies => _assemblies.Values;

public InstrumentedAssembly GetInstrumentedAssembly(string hash)
{
if (Assemblies.ContainsKey(name))
{
return Assemblies[name];
}
if (!_assemblies.TryGetValue(hash, out var instrumentedAssembly))
return null;

return instrumentedAssembly;
}

public InstrumentedAssembly AddInstrumentedAssembly(string hash, string name)
{
var instrumentedAssembly = new InstrumentedAssembly
{
BackupFile = backupFile,
File = file,
BackupPdbFile = backupPdbFile,
PdbFile = pdbFile
Hash = hash,
Name = name
};

Assemblies.Add(name, instrumentedAssembly);
_assemblies.Add(hash, instrumentedAssembly);

return instrumentedAssembly;
}

public void AddExtraAssembly(string file)
{
if (!ExtraAssemblies.Contains(file))
ExtraAssemblies.Add(file);
ExtraAssemblies.Add(file);
}
}
}
Loading

0 comments on commit f4d5435

Please sign in to comment.