/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see .
*/
using System;
using System.Collections.Generic;
using dnlib.DotNet;
namespace dnSpy.Contracts.Decompiler {
///
/// Method debug info
///
public sealed class MethodDebugInfo {
///
/// Compiler name () or null
///
public string? CompilerName { get; }
///
/// Decompiler options version number
///
public int DecompilerSettingsVersion { get; }
///
/// Gets the state machine kind
///
public StateMachineKind StateMachineKind { get; }
///
/// Gets the method
///
public MethodDef Method { get; }
///
/// Gets the kickoff method or null
///
public MethodDef? KickoffMethod { get; }
///
/// Gets the parameters. There could be missing parameters, in which case use . This array isn't sorted.
///
public SourceParameter[] Parameters { get; }
///
/// Gets all statements, sorted by
///
public SourceStatement[] Statements { get; }
///
/// Gets async info or null if none
///
public AsyncMethodDebugInfo? AsyncInfo { get; }
///
/// Gets the root scope
///
public MethodDebugScope Scope { get; }
///
/// Method span or the default value (position 0, length 0) if it's not known
///
public TextSpan Span { get; }
///
/// true if is a valid method span
///
public bool HasSpan => Span.Start != 0 && Span.End != 0;
///
/// Constructor
///
/// Compiler name () or null
/// Decompiler settings version number. This version number should get incremented when the settings change.
/// State machine kind
/// Method
/// Kickoff method or null
/// Parameters or null
/// Statements
/// Root scope
/// Method span or null to calculate it from
/// Async info or null
public MethodDebugInfo(string? compilerName, int decompilerSettingsVersion, StateMachineKind stateMachineKind, MethodDef method, MethodDef? kickoffMethod, SourceParameter[]? parameters, SourceStatement[] statements, MethodDebugScope scope, TextSpan? methodSpan, AsyncMethodDebugInfo? asyncMethodDebugInfo) {
if (statements is null)
throw new ArgumentNullException(nameof(statements));
CompilerName = compilerName;
Method = method ?? throw new ArgumentNullException(nameof(method));
KickoffMethod = kickoffMethod;
Parameters = parameters ?? Array.Empty();
if (statements.Length > 1)
Array.Sort(statements, SourceStatement.SpanStartComparer);
DecompilerSettingsVersion = decompilerSettingsVersion;
Statements = statements;
Scope = scope ?? throw new ArgumentNullException(nameof(scope));
Span = methodSpan ?? CalculateMethodSpan(statements) ?? new TextSpan(0, 0);
AsyncInfo = asyncMethodDebugInfo;
}
static TextSpan? CalculateMethodSpan(SourceStatement[] statements) {
int min = int.MaxValue;
int max = int.MinValue;
foreach (var statement in statements) {
if (min > statement.TextSpan.Start)
min = statement.TextSpan.Start;
if (max < statement.TextSpan.End)
max = statement.TextSpan.End;
}
return min <= max ? TextSpan.FromBounds(min, max) : (TextSpan?)null;
}
///
/// Gets a
///
/// Offset of start of line
/// Offset of end of line
/// Position in text document
///
public SourceStatement? GetSourceStatementByTextOffset(int lineStart, int lineEnd, int textPosition) {
if (lineStart >= Span.End || lineEnd < Span.Start)
return null;
SourceStatement? intersection = null;
foreach (var statement in Statements) {
if (statement.TextSpan.Start <= textPosition) {
if (textPosition < statement.TextSpan.End)
return statement;
if (textPosition == statement.TextSpan.End) {
// If it matches more than one statement, pick the smallest one. More specifically,
// use the first statement if they're identical; that way we use the smallest
// IL offset since Statements is sorted by IL offset.
if (intersection is null || statement.TextSpan.Start > intersection.Value.TextSpan.Start)
intersection = statement;
}
}
}
if (intersection is not null)
return intersection;
var list = new List();
foreach (var statement in Statements) {
if (lineStart < statement.TextSpan.End && lineEnd > statement.TextSpan.Start)
list.Add(statement);
}
list.Sort((a, b) => {
var d = Math.Abs(a.TextSpan.Start - textPosition) - Math.Abs(b.TextSpan.Start - textPosition);
if (d != 0)
return d;
return (int)(a.ILSpan.Start - b.ILSpan.Start);
});
if (list.Count > 0)
return list[0];
return null;
}
///
/// Gets a
///
/// IL offset
///
public SourceStatement? GetSourceStatementByCodeOffset(uint ilOffset) {
foreach (var statement in Statements) {
if (statement.ILSpan.Start <= ilOffset && ilOffset < statement.ILSpan.End)
return statement;
}
return null;
}
}
///
/// State machine kind
///
public enum StateMachineKind {
///
/// Not a state machine
///
None,
///
/// Iterator method state machine
///
IteratorMethod,
///
/// Async method state machine
///
AsyncMethod,
}
}