// 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 } /// /// Interface for decompliler classes : AstBuilder & ReflectionDisassembler. /// public interface ICodeMappings { /// /// Gets the code mappings. /// Dictionary> CodeMappings { get; } /// /// Gets the MembeReference that is decompiled (a TypeDefinition, MethodDefinition, etc) /// Dictionary DecompiledMemberReferences { get; } } /// /// Maps the source code to IL. /// public sealed class SourceCodeMapping { /// /// Gets or sets the source code line number in the output. /// public int SourceCodeLine { get; internal set; } /// /// Gets or sets IL Range offset for the source code line. E.g.: 13-19 <-> 135. /// public ILRange ILInstructionOffset { get; internal set; } /// /// Gets or sets the member mapping this source code mapping belongs to. /// public MemberMapping MemberMapping { get; internal set; } /// /// Retrieves the array that contains the IL range and the missing gaps between ranges. /// /// The array representation of the step aranges. public int[] ToArray(bool isMatch) { var currentList = new List(); // add list for the current source code line currentList.AddRange(ILRange.OrderAndJoint(MemberMapping.MemberCodeMappings .FindAll(m => m.SourceCodeLine == this.SourceCodeLine) .ConvertAll(m => m.ILInstructionOffset))); if (!isMatch) { // add inverted currentList.AddRange(MemberMapping.InvertedList); } else { // if the current list contains the last mapping, add also the last gap var lastInverted = MemberMapping.InvertedList.LastOrDefault(); if (lastInverted != null && lastInverted.From == currentList[currentList.Count - 1].To) currentList.Add(lastInverted); } // set the output var resultList = new List(); foreach (var element in ILRange.OrderAndJoint(currentList)) { resultList.Add(element.From); resultList.Add(element.To); } return resultList.ToArray(); } } /// /// Stores the method information and its source code mappings. /// public sealed class MemberMapping { IEnumerable invertedList; /// /// Gets or sets the type of the mapping. /// public MemberReference MemberReference { get; internal set; } /// /// Metadata token of the method. /// public uint MetadataToken { get; internal set; } /// /// Gets or sets the code size for the member mapping. /// public int CodeSize { get; internal set; } /// /// Gets or sets the source code mappings. /// public List MemberCodeMappings { get; internal 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 InvertedList { get { if (invertedList == null) { var list = MemberCodeMappings.ConvertAll( s => new ILRange { From = s.ILInstructionOffset.From, To = s.ILInstructionOffset.To }); invertedList = ILRange.OrderAndJoint(ILRange.Invert(list, CodeSize)); } return invertedList; } } } /// /// Code mappings helper class. /// public static class CodeMappings { /// /// Create code mapping for a method. /// /// Method to create the mapping for. /// Source code mapping storage. /// True, if a full type was decompiled; false otherwise. internal static MemberMapping CreateCodeMapping( this MethodDefinition member, List codeMappings, bool isTypeDecompiled) { if (member == null || !member.HasBody) return null; if (codeMappings == null) return null; // create IL/CSharp code mappings - used in debugger MemberMapping currentMemberMapping = null; string key = isTypeDecompiled ? member.DeclaringType.FullName : member.FullName; if (codeMappings.Find(map => (int)map.MetadataToken == member.MetadataToken.ToInt32()) == null) { currentMemberMapping = new MemberMapping() { MetadataToken = (uint)member.MetadataToken.ToInt32(), MemberCodeMappings = new List(), CodeSize = member.Body.CodeSize }; if (isTypeDecompiled) currentMemberMapping.MemberReference = member.DeclaringType.Resolve(); else currentMemberMapping.MemberReference = member.Resolve(); codeMappings.Add(currentMemberMapping); } return currentMemberMapping; } /// /// Gets source code mapping and metadata token based on type name and line number. /// /// Code mappings storage. /// Member reference name. /// Line number. /// Metadata token. /// public static SourceCodeMapping GetInstructionByTypeAndLine( this List codeMappings, string memberReferenceName, int lineNumber, out uint metadataToken) { if (codeMappings == null) throw new ArgumentException("CodeMappings storage must be valid!"); foreach (var maping in codeMappings) { var map = maping.MemberCodeMappings.Find(m => m.SourceCodeLine == lineNumber); if (map != null) { metadataToken = maping.MetadataToken; return map; } } metadataToken = 0; return null; } /// /// Gets a mapping given a type, a token and an IL offset. /// /// Code mappings storage. /// Member reference name. /// Token. /// IL offset. /// True, if perfect match. /// A code mapping. public static SourceCodeMapping GetInstructionByTypeTokenAndOffset( this List codeMappings, uint token, int ilOffset, out bool isMatch) { isMatch = false; if (codeMappings == null) throw new ArgumentNullException("CodeMappings storage must be valid!"); var maping = codeMappings.Find(m => m.MetadataToken == token); if (maping == null) return null; // try find an exact match var map = maping.MemberCodeMappings.Find(m => m.ILInstructionOffset.From <= ilOffset && ilOffset < m.ILInstructionOffset.To); if (map == null) { // get the immediate next one map = maping.MemberCodeMappings.Find(m => m.ILInstructionOffset.From >= ilOffset); isMatch = false; if (map == null) map = maping.MemberCodeMappings.LastOrDefault(); // get the last return map; } isMatch = true; return map; } /// /// Gets the source code and type name from metadata token and offset. /// /// Code mappings storage. /// Metadata token. /// IL offset. /// Type definition. /// Line number. /// It is possible to exist to different types from different assemblies with the same metadata token. public static bool GetSourceCodeFromMetadataTokenAndOffset( this List codeMappings, uint token, int ilOffset, out MemberReference member, out int line) { member = null; line = 0; if (codeMappings == null) throw new ArgumentException("CodeMappings storage must be valid!"); var mapping = codeMappings.Find(m => m.MetadataToken == token); if (mapping == null) return false; 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) return false; } } member = mapping.MemberReference; line = codeMapping.SourceCodeLine; return true; } /// /// Create a key by replacing "::" with ".", "+" with "/", " " with ""; /// /// Item to convert. /// public static string CreateKey(this string item) { return item.Replace("+", "/").Replace("::", ".").Replace(" ", string.Empty); } } }