// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
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 ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.CSharp;
using Mono.Cecil;
namespace ICSharpCode.Decompiler
{
[Obsolete]
public enum DecompiledLanguages
{
IL,
CSharp
}
///
/// Maps the source code to IL.
///
public sealed class SourceCodeMapping
{
[Obsolete("Use StartLocation instead - there might be multiple statements per line (e.g. for loops)")]
public int SourceCodeLine {
get {
return this.StartLocation.Line;
}
}
///
/// Gets or sets the start location of the instruction.
///
public TextLocation StartLocation { get; set; }
///
/// Gets or sets the end location of the instruction.
///
public TextLocation EndLocation { 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.
///
/// 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;
internal MemberMapping()
{
}
public MemberMapping(MethodDefinition method)
{
this.MetadataToken = method.MetadataToken.ToInt32();
this.MemberCodeMappings = new List();
this.MemberReference = method;
this.CodeSize = method.Body.CodeSize;
}
///
/// Gets or sets the type of the mapping.
///
public MemberReference MemberReference { get; internal set; }
///
/// Metadata token of the member.
///
public int 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.
/// The actual member reference.
internal static MemberMapping CreateCodeMapping(
this MethodDefinition member,
List codeMappings,
MemberReference actualMemberReference = null)
{
if (member == null || !member.HasBody)
return null;
if (codeMappings == null)
return null;
// create IL/CSharp code mappings - used in debugger
MemberMapping currentMemberMapping = null;
if (codeMappings.Find(map => map.MetadataToken == member.MetadataToken.ToInt32()) == null) {
currentMemberMapping = new MemberMapping() {
MetadataToken = member.MetadataToken.ToInt32(),
MemberCodeMappings = new List(),
MemberReference = actualMemberReference ?? member,
CodeSize = member.Body.CodeSize
};
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 GetInstructionByLineNumber(
this List codeMappings,
int lineNumber,
out int 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.
/// Token.
/// IL offset.
/// True, if perfect match.
/// A code mapping.
public static SourceCodeMapping GetInstructionByTokenAndOffset(
this List codeMappings,
int 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 GetInstructionByTokenAndOffset(
this List codeMappings,
int 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;
}
}
}