Skip to content

Commit

Permalink
Merge pull request lucaslorentz#21 from lucaslorentz/handle-duplicate…
Browse files Browse the repository at this point in the history
…d-assemblies

Handle duplicated assemblies as same assembly
  • Loading branch information
lucaslorentz authored Feb 11, 2018
2 parents 1bde5e8 + a626d95 commit 089d8d4
Show file tree
Hide file tree
Showing 15 changed files with 314 additions and 197 deletions.
64 changes: 64 additions & 0 deletions src/MiniCover/Extensions/SequencePointExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Mono.Cecil.Cil;
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Collections.Generic;

namespace MiniCover.Extensions
{
public static class SequencePointExtensions
{
public static string ExtractCode(this SequencePoint sequencePoint, string[] fileLines)
{
if (sequencePoint.IsHidden)
return null;

if (sequencePoint.StartLine == sequencePoint.EndLine)
{
var lineIndex = sequencePoint.StartLine - 1;
if (lineIndex < 0 || lineIndex >= fileLines.Length)
return null;

var startIndex = sequencePoint.StartColumn - 1;
if (startIndex < 0 || startIndex >= fileLines[lineIndex].Length)
return null;

var length = sequencePoint.EndColumn - sequencePoint.StartColumn;
if (length <= 0 || startIndex + length > fileLines[lineIndex].Length)
return null;

return fileLines[lineIndex].Substring(startIndex, length);
}
else
{
var result = new List<string>();

var firstLineIndex = sequencePoint.StartLine - 1;
if (firstLineIndex < 0 || firstLineIndex >= fileLines.Length)
return null;

var startColumnIndex = sequencePoint.StartColumn - 1;
if (startColumnIndex < 0 || startColumnIndex >= fileLines[firstLineIndex].Length)
return null;

var lastLineIndex = sequencePoint.EndLine - 1;
if (lastLineIndex < firstLineIndex || lastLineIndex >= fileLines.Length)
return null;

var endLineLength = sequencePoint.EndColumn - 1;
if (endLineLength <= 0 || endLineLength > fileLines[lastLineIndex].Length)
return null;

result.Add(fileLines[firstLineIndex].Substring(startColumnIndex));

for (var l = firstLineIndex + 1; l < lastLineIndex; l++)
result.Add(fileLines[l]);

result.Add(fileLines[lastLineIndex].Substring(0, endLineLength));

return string.Join(Environment.NewLine, result);
}
}
}
}
239 changes: 129 additions & 110 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 @@ -42,71 +43,102 @@ public InstrumentationResult Execute()
HitsFile = hitsFile
};

foreach (var fileName in assemblies)
foreach (var assemblyFile in assemblies)
{
InstrumentAssembly(fileName);
RestoreBackup(assemblyFile);
}

foreach (var assembly in result.Assemblies)
var assemblyGroups = assemblies
.Where(ShouldInstrumentAssembly)
.GroupBy(FileUtils.GetFileHash)
.ToArray();

foreach (var assemblyGroup in assemblyGroups)
{
assembly.Value.Files = assembly.Value.Files.OrderBy(kv => kv.Key).ToDictionary(kv => kv.Key, kv => kv.Value);
VisitAssemblyGroup(assemblyGroup);
}

return result;
}

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

if (assemblyFile.EndsWith("uninstrumented.dll"))
return;
if (!File.Exists(GetPdbFile(assemblyFile)))
return false;

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

var pdbBackupFile = Path.ChangeExtension(pdbFile, "uninstrumented.pdb");
if (File.Exists(pdbBackupFile))
File.Copy(pdbBackupFile, pdbFile, true);
private void VisitAssemblyGroup(IEnumerable<string> assemblyFiles)
{
var firstAssemblyFile = assemblyFiles.First();

var instrumentedAssembly = InstrumentAssemblyIfNecessary(firstAssemblyFile);

if (!HasSourceFiles(assemblyFile))
if (instrumentedAssembly == null)
return;

if (IsInstrumented(assemblyFile))
throw new Exception($"Assembly \"{assemblyFile}\" is already instrumented");
foreach (var assemblyFile in assemblyFiles)
{
var pdbFile = GetPdbFile(assemblyFile);
var assemblyBackupFile = GetBackupFile(assemblyFile);
var pdbBackupFile = GetBackupFile(pdbFile);

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

Console.WriteLine($"Instrumenting assembly \"{assemblyFile}\"");
//Override assembly
File.Copy(instrumentedAssembly.TempAssemblyFile, assemblyFile, true);
File.Copy(instrumentedAssembly.TempPdbFile, pdbFile, true);

File.Copy(assemblyFile, assemblyBackupFile, true);
File.Copy(pdbFile, pdbBackupFile, true);
//Copy instrumentation dependencies
var assemblyDirectory = Path.GetDirectoryName(assemblyFile);
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);

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

result.AddInstrumentedAssembly(instrumentedAssembly);

File.Delete(instrumentedAssembly.TempAssemblyFile);
File.Delete(instrumentedAssembly.TempPdbFile);
}

private InstrumentedAssembly InstrumentAssemblyIfNecessary(string assemblyFile)
{
var assemblyDirectory = Path.GetDirectoryName(assemblyFile);

var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory(assemblyDirectory);

using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyBackupFile, new ReaderParameters { ReadSymbols = true, AssemblyResolver = resolver }))
using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyFile, 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)
);
if (!HasSourceFiles(assemblyDefinition))
return null;

var instrumentedConstructor = typeof(InstrumentedAttribute).GetConstructors().First();
var instrumentedReference = assemblyDefinition.MainModule.ImportReference(instrumentedConstructor);
assemblyDefinition.CustomAttributes.Add(new CustomAttribute(instrumentedReference));
if (assemblyDefinition.CustomAttributes.Any(a => a.AttributeType.Name == "InstrumentedAttribute"))
throw new Exception($"Assembly \"{assemblyFile}\" is already instrumented");

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);
Console.WriteLine($"Instrumenting assembly \"{assemblyDefinition.Name.Name}\"");

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

var instrumentedAttributeConstructor = typeof(InstrumentedAttribute).GetConstructors().First();
var instrumentedAttributeReference = assemblyDefinition.MainModule.ImportReference(instrumentedAttributeConstructor);
assemblyDefinition.CustomAttributes.Add(new CustomAttribute(instrumentedAttributeReference));

CreateAssemblyInit(assemblyDefinition);

Expand Down Expand Up @@ -153,7 +185,7 @@ private void InstrumentAssembly(string assemblyFile)

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

Expand All @@ -162,7 +194,7 @@ private void InstrumentAssembly(string assemblyFile)
// 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;
continue;

var instructionId = ++id;

Expand All @@ -173,7 +205,6 @@ private void InstrumentAssembly(string assemblyFile)
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,
Expand All @@ -187,7 +218,46 @@ private void InstrumentAssembly(string assemblyFile)
}
}

assemblyDefinition.Write(assemblyFile, new WriterParameters { WriteSymbols = true });
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 bool HasSourceFiles(AssemblyDefinition assemblyDefinition)
{
return assemblyDefinition
.GetAllMethods()
.SelectMany(m => m.DebugInformation.SequencePoints)
.Select(s => s.Document.Url)
.Distinct()
.Any(d => GetSourceRelativePath(d) != null);
}

private void RestoreBackup(string assemblyFile)
{
var pdbFile = GetPdbFile(assemblyFile);
var assemblyBackupFile = GetBackupFile(assemblyFile);
var pdbBackupFile = GetBackupFile(pdbFile);

if (File.Exists(assemblyBackupFile))
{
File.Copy(assemblyBackupFile, assemblyFile, true);
File.Delete(assemblyBackupFile);
}

if (File.Exists(pdbBackupFile))
{
File.Copy(pdbBackupFile, pdbFile, true);
File.Delete(pdbBackupFile);
}
}

Expand All @@ -209,28 +279,6 @@ private void CreateAssemblyInit(AssemblyDefinition assemblyDefinition)
ilProcessor.InsertBefore(initInstruction, pathParamLoadInstruction);
}

private bool HasSourceFiles(string assemblyFile)
{
using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyFile, new ReaderParameters { ReadSymbols = true }))
{
var methods = assemblyDefinition.GetAllMethods();

return methods
.SelectMany(m => m.DebugInformation.SequencePoints)
.Select(s => s.Document.Url)
.Distinct()
.Any(d => GetSourceRelativePath(d) != null);
}
}

private bool IsInstrumented(string assemblyFile)
{
using (var assemblyDefinition = AssemblyDefinition.ReadAssembly(assemblyFile))
{
return assemblyDefinition.CustomAttributes.Any(a => a.AttributeType.Name == "InstrumentedAttribute");
}
}

private void InstrumentInstruction(int instructionId, Instruction instruction,
MethodReference hitMethodReference, MethodDefinition method, ILProcessor ilProcessor)
{
Expand Down Expand Up @@ -294,56 +342,27 @@ private string GetSourceRelativePath(string path)
return path.Substring(normalizedWorkDir.Length);
}

private string ExtractCode(string[] fileLines, SequencePoint sequencePoint)
private string GetPdbFile(string assemblyFile)
{
if (sequencePoint.IsHidden)
return null;

if (sequencePoint.StartLine == sequencePoint.EndLine)
{
var lineIndex = sequencePoint.StartLine - 1;
if (lineIndex < 0 || lineIndex >= fileLines.Length)
return null;

var startIndex = sequencePoint.StartColumn - 1;
if (startIndex < 0 || startIndex >= fileLines[lineIndex].Length)
return null;

var length = sequencePoint.EndColumn - sequencePoint.StartColumn;
if (length <= 0 || startIndex + length > fileLines[lineIndex].Length)
return null;

return fileLines[lineIndex].Substring(startIndex, length);
}
else
{
var result = new List<string>();

var firstLineIndex = sequencePoint.StartLine - 1;
if (firstLineIndex < 0 || firstLineIndex >= fileLines.Length)
return null;

var startColumnIndex = sequencePoint.StartColumn - 1;
if (startColumnIndex < 0 || startColumnIndex >= fileLines[firstLineIndex].Length)
return null;

var lastLineIndex = sequencePoint.EndLine - 1;
if (lastLineIndex < firstLineIndex || lastLineIndex >= fileLines.Length)
return null;

var endLineLength = sequencePoint.EndColumn - 1;
if (endLineLength <= 0 || endLineLength > fileLines[lastLineIndex].Length)
return null;

result.Add(fileLines[firstLineIndex].Substring(startColumnIndex));
return Path.ChangeExtension(assemblyFile, "pdb");
}

for (var l = firstLineIndex + 1; l < lastLineIndex; l++)
result.Add(fileLines[l]);
private string GetBackupFile(string file)
{
return Path.ChangeExtension(file, $"uninstrumented{Path.GetExtension(file)}");
}

result.Add(fileLines[lastLineIndex].Substring(0, endLineLength));
private bool IsBackupFile(string file)
{
return Path.GetFileName(file).Contains(".uninstrumented");
}

return string.Join(Environment.NewLine, result);
}
private string GetMiniCoverTempPath()
{
var path = Path.Combine(Path.GetTempPath(), $"minicover");
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
return path;
}
}
}
Loading

0 comments on commit 089d8d4

Please sign in to comment.