ILRepack/IKVMLineIndexer.cs (154 lines of code) (raw):

using Mono.Cecil; using Mono.Cecil.Cil; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; namespace ILRepacking { /// <summary> /// This feature, when enabled, allows to store debug indexes within the assembly itself. /// It was inspired by IKVM (which does it for Java assemblies), and re-uses the same attributes. /// It then allows at runtime to display file:line information on all stacktraces, by resolving the IL offset provided. /// </summary> internal class IKVMLineIndexer { private readonly IRepackContext repack; private bool enabled; private string fileName; private TypeReference sourceFileAttributeTypeReference; private TypeReference lineNumberTableAttributeTypeReference; private MethodReference lineNumberTableAttributeConstructor1; private MethodReference lineNumberTableAttributeConstructor2; private MethodReference sourceFileAttributeConstructor; protected ModuleDefinition TargetAssemblyMainModule { get { return repack.TargetAssemblyMainModule; } } public IKVMLineIndexer(IRepackContext ilRepack, bool doLineIndexing) { repack = ilRepack; enabled = doLineIndexing; } public void Reset() { fileName = null; } public void PreMethodBodyRepack(MethodBody body, MethodDefinition parent) { if (!enabled || !parent.DebugInformation.HasSequencePoints) return; Reset(); if (!parent.CustomAttributes.Any(x => x.Constructor.DeclaringType.Name == "LineNumberTableAttribute")) { var lineNumberWriter = new LineNumberWriter(body.Instructions.Count / 4); foreach (var sp in parent.DebugInformation.SequencePoints) { AddSeqPoint(sp, lineNumberWriter); } PostMethodBodyRepack(parent, lineNumberWriter); } } private void AddSeqPoint(SequencePoint currentSeqPoint, LineNumberWriter lineNumberWriter) { if (currentSeqPoint != null) { if (fileName == null && currentSeqPoint.Document != null) { var url = currentSeqPoint.Document.Url; if (url != null) { try { fileName = new FileInfo(url).Name; } catch { // for mono } } } if (currentSeqPoint.StartLine == 0xFeeFee && currentSeqPoint.EndLine == 0xFeeFee) { if (lineNumberWriter.LineNo > 0) { lineNumberWriter.AddMapping(currentSeqPoint.Offset, -1); } } else { if (lineNumberWriter.LineNo != currentSeqPoint.StartLine) { lineNumberWriter.AddMapping(currentSeqPoint.Offset, currentSeqPoint.StartLine); } } } } private void PostMethodBodyRepack(MethodDefinition parent, LineNumberWriter lineNumberWriter) { if (lineNumberWriter.Count > 0) { CustomAttribute ca; if (lineNumberWriter.Count == 1) { ca = new CustomAttribute(lineNumberTableAttributeConstructor1) { ConstructorArguments = { new CustomAttributeArgument(TargetAssemblyMainModule.TypeSystem.UInt16, (ushort)lineNumberWriter.LineNo) } }; } else { ca = new CustomAttribute(lineNumberTableAttributeConstructor2) { ConstructorArguments = { new CustomAttributeArgument(new ArrayType(TargetAssemblyMainModule.TypeSystem.Byte), lineNumberWriter.ToArray().Select(b => new CustomAttributeArgument(TargetAssemblyMainModule.TypeSystem.Byte, b)).ToArray()) } }; } parent.CustomAttributes.Add(ca); if (fileName != null) { var type = parent.DeclaringType; var exist = type.CustomAttributes.FirstOrDefault(x => x.Constructor.DeclaringType.Name == "SourceFileAttribute"); if (exist == null) { // put the filename on the type first type.CustomAttributes.Add(new CustomAttribute(sourceFileAttributeConstructor) { ConstructorArguments = { new CustomAttributeArgument(TargetAssemblyMainModule.TypeSystem.String, fileName) } }); } else if (fileName != (string)exist.ConstructorArguments[0].Value) { // if already specified on the type, but different (e.g. for partial classes), put the attribute on the method. // Note: attribute isn't allowed for Methods, but that restriction doesn't apply to IL generation (or runtime use) parent.CustomAttributes.Add(new CustomAttribute(sourceFileAttributeConstructor) { ConstructorArguments = { new CustomAttributeArgument(TargetAssemblyMainModule.TypeSystem.String, fileName) } }); } } } } public void PostRepackReferences() { if (!enabled) return; IMetadataScope ikvmRuntimeReference = repack.TargetAssemblyMainModule.AssemblyReferences.FirstOrDefault(r => r.Name == "IKVM.Runtime"); if (ikvmRuntimeReference == null) { ikvmRuntimeReference = repack.MergeScope(repack.GlobalAssemblyResolver.Resolve(new AssemblyNameReference("IKVM.Runtime", null)).Name); } if (ikvmRuntimeReference == null) { enabled = false; } else { sourceFileAttributeTypeReference = new TypeReference("IKVM.Attributes", "SourceFileAttribute", TargetAssemblyMainModule, ikvmRuntimeReference); sourceFileAttributeConstructor = new MethodReference(".ctor", TargetAssemblyMainModule.TypeSystem.Void, sourceFileAttributeTypeReference) {HasThis = true, Parameters = {new ParameterDefinition(TargetAssemblyMainModule.TypeSystem.String)}}; lineNumberTableAttributeTypeReference = new TypeReference("IKVM.Attributes", "LineNumberTableAttribute", TargetAssemblyMainModule, ikvmRuntimeReference); lineNumberTableAttributeConstructor1 = new MethodReference(".ctor", TargetAssemblyMainModule.TypeSystem.Void, lineNumberTableAttributeTypeReference) {HasThis = true, Parameters = {new ParameterDefinition(TargetAssemblyMainModule.TypeSystem.UInt16)}}; lineNumberTableAttributeConstructor2 = new MethodReference(".ctor", TargetAssemblyMainModule.TypeSystem.Void, lineNumberTableAttributeTypeReference) {HasThis = true, Parameters = {new ParameterDefinition(new ArrayType(TargetAssemblyMainModule.TypeSystem.Byte))}}; } } } }