/* 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, } }