// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // This code is distributed under MIT X11 license (for details please see \doc\license.txt) using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using ICSharpCode.Decompiler.Ast; using ICSharpCode.Decompiler.Disassembler; using ICSharpCode.Decompiler.ILAst; using Mono.Cecil; namespace ICSharpCode.Decompiler { public enum DecompiledLanguages { IL, CSharp } /// /// Maps the source code to IL. /// public class SourceCodeMapping { /// /// Gets or sets the source code line number in the output. /// public int SourceCodeLine { get; set; } /// /// Gets or sets IL Range offset for the source code line. E.g.: 13-19 <-> 135. /// public ILRange ILInstructionOffset { get; set; } /// /// Gets or sets the member mapping this source code mapping belongs to. /// public MemberMapping MemberMapping { get; set; } /// /// Retrieves the array that contains the IL range and the missing gaps between ranges. /// /// public int[] ToArray() { var resultList = new List(); // add list for the current source code line var currentList = MemberMapping.MemberCodeMappings.FindAll(m => m.SourceCodeLine == this.SourceCodeLine); foreach (var element in currentList.Distinct(new SourceCodeMappingComparer())) { resultList.Add(element.ILInstructionOffset.From); resultList.Add(element.ILInstructionOffset.To); } // add inverted var invertedList = MemberMapping.GetInvertedList(); if (invertedList != null && invertedList.Count() > 0) { foreach (var range in invertedList) { resultList.Add(range.From); resultList.Add(range.To); } } return resultList.ToArray(); } sealed class SourceCodeMappingComparer : IEqualityComparer { public bool Equals(SourceCodeMapping x, SourceCodeMapping y) { //Check whether the compared objects reference the same data. if (Object.ReferenceEquals(x, y)) return true; //Check whether any of the compared objects is null. if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null)) return false; return x.ILInstructionOffset.From == y.ILInstructionOffset.From && x.ILInstructionOffset.To == y.ILInstructionOffset.To && x.SourceCodeLine == y.SourceCodeLine; } public int GetHashCode(SourceCodeMapping map) { //Check whether the object is null if (Object.ReferenceEquals(map, null)) return 0; //Get hash code for the ILInstructionOffset field if it is not null. int hashRange = map.ILInstructionOffset == null ? 0 : map.ILInstructionOffset.GetHashCode(); //Get hash code for the SourceCodeLine field. int hashLine = map.SourceCodeLine.GetHashCode(); //Calculate the hash code for the product. return hashRange ^ hashLine; } } } /// /// Stores the method information and its source code mappings. /// public sealed class MemberMapping { /// /// Gets or sets the type of the mapping. /// public TypeDefinition Type { get; set; } /// /// Metadata token of the method. /// public uint MetadataToken { get; set; } /// /// Gets or sets the code size for the member mapping. /// public int CodeSize { get; set; } /// /// Gets or sets the source code mappings. /// public List MemberCodeMappings { get; set; } /// /// Gets the inverted IL Ranges.
/// E.g.: for (0-9, 11-14, 14-18, 21-25) => (9-11,18-21). ///
/// IL Range inverted list. public IEnumerable GetInvertedList() { var list = MemberCodeMappings.ConvertAll( s => new ILRange { From = s.ILInstructionOffset.From, To = s.ILInstructionOffset.To }); return ILRange.Invert(list, CodeSize); } } public static class CodeMappings { public static ConcurrentDictionary> GetStorage(DecompiledLanguages language) { ConcurrentDictionary> storage = null; switch (language) { case DecompiledLanguages.IL: storage = ILCodeMapping.SourceCodeMappings; break; case DecompiledLanguages.CSharp: storage = CSharpCodeMapping.SourceCodeMappings; break; default: throw new System.Exception("Invalid value for DecompiledLanguages"); } return storage; } /// /// Create code mapping for a method. /// /// Method to create the mapping for. /// Source code mapping storage. public static MemberMapping CreateCodeMapping( this MethodDefinition member, ConcurrentDictionary> codeMappings) { // create IL/CSharp code mappings - used in debugger MemberMapping currentMemberMapping = null; if (codeMappings.ContainsKey(member.DeclaringType.FullName)) { var mapping = codeMappings[member.DeclaringType.FullName]; if (mapping.Find(map => (int)map.MetadataToken == member.MetadataToken.ToInt32()) == null) { currentMemberMapping = new MemberMapping() { MetadataToken = (uint)member.MetadataToken.ToInt32(), Type = member.DeclaringType.Resolve(), MemberCodeMappings = new List(), CodeSize = member.Body.CodeSize }; mapping.Add(currentMemberMapping); } } return currentMemberMapping; } /// /// Gets source code mapping and metadata token based on type name and line number. /// /// Code mappings storage. /// Type name. /// Line number. /// Metadata token. /// public static SourceCodeMapping GetInstructionByTypeAndLine( this ConcurrentDictionary> codeMappings, string typeName, int lineNumber, out uint metadataToken) { if (!codeMappings.ContainsKey(typeName)) { metadataToken = 0; return null; } if (lineNumber <= 0) { metadataToken = 0; return null; } var methodMappings = codeMappings[typeName]; foreach (var maping in methodMappings) { var map = maping.MemberCodeMappings.Find(m => m.SourceCodeLine == lineNumber); if (map != null) { metadataToken = maping.MetadataToken; return map; } } metadataToken = 0; return null; } /// /// Gets the source code and type name from metadata token and offset. /// /// Code mappings storage. /// Metadata token. /// IL offset. /// Type definition. /// Line number. public static bool GetSourceCodeFromMetadataTokenAndOffset( this ConcurrentDictionary> codeMappings, uint token, int ilOffset, out TypeDefinition type, out int line) { type = null; line = 0; foreach (var typename in codeMappings.Keys) { var mapping = codeMappings[typename].Find(m => m.MetadataToken == token); if (mapping == null) continue; var codeMapping = mapping.MemberCodeMappings.Find( cm => cm.ILInstructionOffset.From <= ilOffset && ilOffset <= cm.ILInstructionOffset.To - 1); if (codeMapping == null) { codeMapping = mapping.MemberCodeMappings.Find(cm => (cm.ILInstructionOffset.From >= ilOffset)); if (codeMapping == null) { codeMapping = mapping.MemberCodeMappings.LastOrDefault(); if (codeMapping == null) continue; } } type = mapping.Type; line = codeMapping.SourceCodeLine; return true; } return false; } } }