/* 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 dnSpy.Contracts.Decompiler; using dnSpy.Contracts.Disassembly; using dnSpy.Decompiler.Utils; namespace dnSpy.Debugger.DotNet.Disassembly { struct DecompiledCodeProvider { readonly IDecompiler decompiler; readonly MethodDef method; readonly CancellationToken cancellationToken; DecompilerOutputImpl? output; MethodDebugInfo? debugInfo; public DecompiledCodeProvider(IDecompiler? decompiler, MethodDef method, CancellationToken cancellationToken) { this.decompiler = decompiler ?? throw new ArgumentNullException(nameof(decompiler)); this.method = method ?? throw new ArgumentNullException(nameof(method)); this.cancellationToken = cancellationToken; output = null; debugInfo = null; } public bool TryDecompile() { Debug2.Assert(output is null); output = new DecompilerOutputImpl(); if (!StateMachineHelpers.TryGetKickoffMethod(method, out var containingMethod)) containingMethod = method; var ctx = new DecompilationContext() { CancellationToken = cancellationToken, CalculateILSpans = true, AsyncMethodBodyDecompilation = false, }; var info = TryDecompileCode(containingMethod, method.MDToken.Raw, ctx, output); if (info.debugInfo is null && containingMethod != method) { output.Clear(); // The decompiler can't decompile the iterator / async method, try again, // but only decompile the MoveNext method info = TryDecompileCode(method, method.MDToken.Raw, ctx, output); } debugInfo = info.debugInfo; return debugInfo is not null; } public NativeVariableInfo[] CreateNativeVariableInfo() { if (debugInfo is null) return Array.Empty(); var list = new List(); foreach (var arg in debugInfo.Parameters) { var p = arg.Parameter; if (p is null || p.Index < 0) continue; var name = arg.Name; if (string.IsNullOrEmpty(name)) continue; list.Add(new NativeVariableInfo(isLocal: false, p.Index, name)); } foreach (var scope in GetScopes(debugInfo.Scope)) { foreach (var local in scope.Locals) { var l = local.Local; if (l is null) continue; var name = local.Name; if (string.IsNullOrEmpty(name)) continue; list.Add(new NativeVariableInfo(isLocal: true, l.Index, name)); } } return list.Count == 0 ? Array.Empty() : list.ToArray(); } static IEnumerable GetScopes(MethodDebugScope scope) { yield return scope; foreach (var childScope in scope.Scopes) { foreach (var s in GetScopes(childScope)) yield return s; } } (MethodDebugInfo debugInfo, MethodDebugInfo? stateMachineDebugInfo) TryDecompileCode(MethodDef method, uint methodToken, DecompilationContext ctx, DecompilerOutputImpl output) { output.Initialize(methodToken); decompiler.Decompile(method, output, ctx); var info = output.TryGetMethodDebugInfo(); cancellationToken.ThrowIfCancellationRequested(); return info; } public SourceStatementProvider CreateCodeProvider() { if (output is null || debugInfo is null) return default; return new SourceStatementProvider(output.ToString(), debugInfo); } public ILSourceStatementProvider CreateILCodeProvider() { Debug.Assert(decompiler.GenericGuid == DecompilerConstants.LANGUAGE_IL); if (output is null || debugInfo is null) return default; return new ILSourceStatementProvider(output.ToString(), debugInfo); } } }