/* 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 System.Diagnostics; using System.Threading; using dnlib.DotNet; using dnlib.DotNet.Emit; namespace dnSpy.Contracts.Debugger.DotNet.Code { /// /// Method debug info /// public sealed class DbgMethodDebugInfo { /// /// Compiler used to compile the code /// public DbgCompilerKind Compiler { get; } /// /// Version number of this method debug info. If it gets incremented, any older instances with a different /// version should not be used again. /// public int DebugInfoVersion { get; } /// /// Gets the method /// public MethodDef Method { get; } /// /// Gets the parameters. There could be missing parameters, in which case use . This array isn't sorted. /// public DbgParameter[] Parameters { get; } /// /// Gets all statements, sorted by /// public DbgSourceStatement[] Statements { get; } /// /// Gets the async method debug info or null if it's not an async method /// public DbgAsyncMethodDebugInfo? AsyncInfo { get; } /// /// Gets the root scope /// public DbgMethodDebugScope Scope { get; } /// /// Constructor /// /// Compiler /// Debug info version /// Method /// Parameters or null /// Statements /// Root scope /// Async info or null public DbgMethodDebugInfo(DbgCompilerKind compiler, int debugInfoVersion, MethodDef method, DbgParameter[]? parameters, DbgSourceStatement[] statements, DbgMethodDebugScope scope, DbgAsyncMethodDebugInfo? asyncMethodDebugInfo) { if (statements is null) throw new ArgumentNullException(nameof(statements)); Compiler = compiler; Method = method ?? throw new ArgumentNullException(nameof(method)); Parameters = parameters ?? Array.Empty(); if (statements.Length > 1) Array.Sort(statements, DbgSourceStatement.SpanStartComparer); DebugInfoVersion = debugInfoVersion; Statements = statements; Scope = scope ?? throw new ArgumentNullException(nameof(scope)); AsyncInfo = asyncMethodDebugInfo; } /// /// Gets step ranges /// /// Source statement spans /// public DbgILSpan[] GetRanges(DbgILSpan[] sourceILSpans) { var list = new List(sourceILSpans.Length + GetUnusedILSpans().Length + 1); list.AddRange(sourceILSpans); list.AddRange(GetUnusedILSpans()); return DbgILSpan.OrderAndCompactList(list).ToArray(); } /// /// Gets unused step ranges /// /// public DbgILSpan[] GetUnusedRanges() => GetUnusedILSpans(); DbgILSpan[] GetUnusedILSpans() { if (cachedUnusedILSpans is not null) return cachedUnusedILSpans; var list = new List(Statements.Length); foreach (var s in Statements) list.Add(s.ILSpan); return cachedUnusedILSpans = GetUnusedILSpans(list).ToArray(); } DbgILSpan[]? cachedUnusedILSpans; List GetUnusedILSpans(List list) { uint codeSize = (uint)GetCodeSize(Method.Body); list = DbgILSpan.OrderAndCompactList(list); var res = new List(); if (list.Count == 0) { if (codeSize > 0) res.Add(new DbgILSpan(0, codeSize)); return res; } uint prevEnd = 0; for (int i = 0; i < list.Count; i++) { var span = list[i]; Debug.Assert(span.Start >= prevEnd); uint length = span.Start - prevEnd; if (length > 0) res.Add(new DbgILSpan(prevEnd, length)); prevEnd = span.End; } Debug.Assert(prevEnd <= codeSize); if (prevEnd < codeSize) res.Add(new DbgILSpan(prevEnd, codeSize - prevEnd)); return res; } static int GetCodeSize(CilBody? body) { if (body is null || body.Instructions.Count == 0) return 0; var instr = body.Instructions[body.Instructions.Count - 1]; return (int)instr.Offset + instr.GetSize(); } /// /// Gets a /// /// IL offset /// public DbgSourceStatement? GetSourceStatementByCodeOffset(uint ilOffset) { foreach (var statement in Statements) { if (statement.ILSpan.Start <= ilOffset && ilOffset < statement.ILSpan.End) return statement; } return null; } /// /// Gets all ILSpans of a statement /// /// Statement span /// public DbgILSpan[] GetILSpansOfStatement(DbgTextSpan statementSpan) { if (statementsDict is null) Interlocked.CompareExchange(ref statementsDict, CreateStatementsDict(Statements), null); Debug2.Assert(statementsDict is not null); if (statementsDict.TryGetValue(statementSpan, out var list)) { var spans = list.ToArray(); #if DEBUG for (int i = 1; i < spans.Length; i++) Debug.Assert(spans[i - 1].End <= spans[i].Start); #endif return spans; } return Array.Empty(); } Dictionary>? statementsDict; static Dictionary> CreateStatementsDict(DbgSourceStatement[] statements) { var dict = new Dictionary>(statements.Length); foreach (var statement in statements) { dict.TryGetValue(statement.TextSpan, out var list); list.Add(statement.ILSpan); dict[statement.TextSpan] = list; } return dict; } } struct SmallList { T firstValue; bool hasFirstValue; List? list; public void Add(T value) { if (!hasFirstValue) { firstValue = value; hasFirstValue = true; } else { if (list is null) list = new List(2) { firstValue }; list.Add(value); } } public T[] ToArray() { if (list is not null) return list.ToArray(); if (hasFirstValue) return new[] { firstValue }; return Array.Empty(); } } }