/*
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();
}
}
}