/* 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.ObjectModel; using System.Diagnostics; using dnSpy.Contracts.Debugger; using dnSpy.Contracts.Debugger.DotNet.Code; using dnSpy.Contracts.Debugger.DotNet.Evaluation; using dnSpy.Contracts.Debugger.DotNet.Evaluation.ExpressionCompiler; using dnSpy.Contracts.Debugger.DotNet.Evaluation.Formatters; using dnSpy.Contracts.Debugger.DotNet.Text; using dnSpy.Contracts.Debugger.Engine.Evaluation; using dnSpy.Contracts.Debugger.Engine.Evaluation.Internal; using dnSpy.Contracts.Debugger.Evaluation; using dnSpy.Contracts.Debugger.Text; using dnSpy.Debugger.DotNet.Evaluation.Engine.Interpreter; using dnSpy.Debugger.DotNet.Metadata; using dnSpy.Debugger.DotNet.Properties; namespace dnSpy.Debugger.DotNet.Evaluation.Engine { sealed class DbgEngineExpressionEvaluatorImpl : DbgEngineExpressionEvaluator, IDebuggerDisplayAttributeEvaluator { readonly DbgModuleReferenceProvider dbgModuleReferenceProvider; readonly DbgDotNetExpressionCompiler expressionCompiler; readonly DbgDotNetILInterpreter dnILInterpreter; readonly DbgAliasProvider dbgAliasProvider; readonly IPredefinedEvaluationErrorMessagesHelper predefinedEvaluationErrorMessagesHelper; public DbgEngineExpressionEvaluatorImpl(DbgModuleReferenceProvider dbgModuleReferenceProvider, DbgDotNetExpressionCompiler expressionCompiler, DbgDotNetILInterpreter dnILInterpreter, DbgAliasProvider dbgAliasProvider, IPredefinedEvaluationErrorMessagesHelper predefinedEvaluationErrorMessagesHelper) { this.dbgModuleReferenceProvider = dbgModuleReferenceProvider ?? throw new ArgumentNullException(nameof(dbgModuleReferenceProvider)); this.expressionCompiler = expressionCompiler ?? throw new ArgumentNullException(nameof(expressionCompiler)); this.dnILInterpreter = dnILInterpreter ?? throw new ArgumentNullException(nameof(dnILInterpreter)); this.dbgAliasProvider = dbgAliasProvider ?? throw new ArgumentNullException(nameof(dbgAliasProvider)); this.predefinedEvaluationErrorMessagesHelper = predefinedEvaluationErrorMessagesHelper ?? throw new ArgumentNullException(nameof(predefinedEvaluationErrorMessagesHelper)); } public override DbgEngineEEAssignmentResult Assign(DbgEvaluationInfo evalInfo, string expression, string valueExpression, DbgEvaluationOptions options) { var dispatcher = evalInfo.Runtime.GetDotNetRuntime().Dispatcher; if (dispatcher.CheckAccess()) return AssignCore(evalInfo, expression, valueExpression, options); return Assign(dispatcher, evalInfo, expression, valueExpression, options); DbgEngineEEAssignmentResult Assign(DbgDotNetDispatcher dispatcher2, DbgEvaluationInfo evalInfo2, string expression2, string valueExpression2, DbgEvaluationOptions options2) { if (!dispatcher2.TryInvokeRethrow(() => AssignCore(evalInfo2, expression2, valueExpression2, options2), out var result)) result = new DbgEngineEEAssignmentResult(DbgEEAssignmentResultFlags.None, DispatcherConstants.ProcessExitedError); return result; } } DbgEngineEEAssignmentResult AssignCore(DbgEvaluationInfo evalInfo, string expression, string valueExpression, DbgEvaluationOptions options) { var resultFlags = DbgEEAssignmentResultFlags.None; try { var info = dbgAliasProvider.GetAliases(evalInfo); var refsResult = dbgModuleReferenceProvider.GetModuleReferences(evalInfo.Runtime, evalInfo.Frame, info.typeReferences); if (refsResult.ErrorMessage is not null) return new DbgEngineEEAssignmentResult(resultFlags, refsResult.ErrorMessage); var compRes = expressionCompiler.CompileAssignment(evalInfo, refsResult.ModuleReferences!, info.aliases, expression, valueExpression, options); evalInfo.CancellationToken.ThrowIfCancellationRequested(); if (compRes.IsError) return new DbgEngineEEAssignmentResult(resultFlags | DbgEEAssignmentResultFlags.CompilerError, compRes.ErrorMessage); var state = dnILInterpreter.CreateState(compRes.Assembly!); Debug.Assert(compRes.CompiledExpressions!.Length == 1); ref var exprInfo = ref compRes.CompiledExpressions[0]; if (exprInfo.ErrorMessage is not null) return new DbgEngineEEAssignmentResult(resultFlags | DbgEEAssignmentResultFlags.CompilerError, exprInfo.ErrorMessage); resultFlags |= DbgEEAssignmentResultFlags.ExecutedCode; var res = dnILInterpreter.Execute(evalInfo, state, exprInfo.TypeName, exprInfo.MethodName, options, out _); if (res.HasError) return new DbgEngineEEAssignmentResult(resultFlags, res.ErrorMessage); if (res.ValueIsException) { res.Value!.Dispose(); var error = string.Format(dnSpy_Debugger_DotNet_Resources.Method_X_ThrewAnExceptionOfType_Y, expression, res.Value.Type.FullName); return new DbgEngineEEAssignmentResult(resultFlags, error); } res.Value?.Dispose(); return new DbgEngineEEAssignmentResult(); } catch (Exception ex) when (ExceptionUtils.IsInternalDebuggerError(ex)) { return new DbgEngineEEAssignmentResult(resultFlags, PredefinedEvaluationErrorMessages.InternalDebuggerError); } } public override object? CreateExpressionEvaluatorState() => new EvaluateImplExpressionState(); public override DbgEngineEvaluationResult Evaluate(DbgEvaluationInfo evalInfo, string expression, DbgEvaluationOptions options, object? state) { var dispatcher = evalInfo.Runtime.GetDotNetRuntime().Dispatcher; if (dispatcher.CheckAccess()) return EvaluateCore(evalInfo, expression, options, state); return Evaluate(dispatcher, evalInfo, expression, options, state); DbgEngineEvaluationResult Evaluate(DbgDotNetDispatcher dispatcher2, DbgEvaluationInfo evalInfo2, string expression2, DbgEvaluationOptions options2, object? state2) { if (!dispatcher2.TryInvokeRethrow(() => EvaluateCore(evalInfo2, expression2, options2, state2), out var result)) result = new DbgEngineEvaluationResult(DispatcherConstants.ProcessExitedError); return result; } } DbgEngineEvaluationResult EvaluateCore(DbgEvaluationInfo evalInfo, string expression, DbgEvaluationOptions options, object? state) { var res = EvaluateImpl(evalInfo, expression, options | DbgEvaluationOptions.NoName, state); if (res.Error is not null) return new DbgEngineEvaluationResult(res.Error, res.Flags); Debug2.Assert(res.Value is not null); try { return new DbgEngineEvaluationResult(new DbgEngineValueImpl(res.Value), res.FormatSpecifiers, res.Flags); } catch { res.Value.Dispose(); throw; } } sealed class EvaluateImplExpressionState { public readonly struct Key { readonly DbgEngineExpressionEvaluatorImpl ee; readonly int debugInfoVersion; readonly object memberModule; readonly int memberToken; readonly int memberVersion; readonly DbgModuleReference[] moduleReferences; readonly DbgMethodDebugScope? scope; readonly DbgDotNetAlias[] aliases; readonly DbgEvaluationOptions options; readonly string expression; public Key(DbgEngineExpressionEvaluatorImpl ee, int debugInfoVersion, object memberModule, int memberToken, int memberVersion, DbgModuleReference[] moduleReferences, DbgMethodDebugScope? scope, DbgDotNetAlias[] aliases, DbgEvaluationOptions options, string expression) { this.ee = ee; this.debugInfoVersion = debugInfoVersion; this.memberModule = memberModule; this.memberToken = memberToken; this.memberVersion = memberVersion; this.moduleReferences = moduleReferences; this.scope = scope; this.aliases = aliases; this.options = options; this.expression = expression; } public bool Equals(in Key other) => scope == other.scope && moduleReferences == other.moduleReferences && ee == other.ee && debugInfoVersion == other.debugInfoVersion && memberModule == other.memberModule && memberToken == other.memberToken && memberVersion == other.memberVersion && Equals(aliases, other.aliases) && options == other.options && StringComparer.Ordinal.Equals(expression, other.expression); static bool Equals(DbgDotNetAlias[] a, DbgDotNetAlias[] b) { if (a.Length != b.Length) return false; for (int i = 0; i < a.Length; i++) { if (!Equals(a[i], b[i])) return false; } return true; } static bool Equals(DbgDotNetAlias a, DbgDotNetAlias b) => a.Kind == b.Kind && Equals(a.CustomTypeInfo, b.CustomTypeInfo) && a.CustomTypeInfoId == b.CustomTypeInfoId && StringComparer.Ordinal.Equals(a.Name, b.Name) && StringComparer.Ordinal.Equals(a.Type, b.Type); static bool Equals(ReadOnlyCollection? a, ReadOnlyCollection? b) { if (a == b) return true; if (a is null || b is null) return false; if (a.Count != b.Count) return false; for (int i = 0; i < a.Count; i++) { if (a[i] != b[i]) return false; } return true; } } public Key CachedKey; public DbgDotNetCompilationResult CompilationResult; public DbgDotNetILInterpreterState? ILInterpreterState; public EvaluateImplResult? EvaluateImplResult; } EvaluateImplResult? GetMethodInterpreterState(DbgEvaluationInfo evalInfo, string expression, DbgEvaluationOptions options, object? stateObj, out EvaluateImplExpressionState? evalExprState) { var languageDebugInfo = evalInfo.Context.TryGetLanguageDebugInfo(); if (languageDebugInfo is null) { evalExprState = null; return new EvaluateImplResult(dnSpy_Debugger_DotNet_Resources.CantEvaluateWhenCurrentFrameIsNative, CreateName(expression), null, null, 0, PredefinedDbgValueNodeImageNames.Error, null); } var methodDebugInfo = languageDebugInfo.MethodDebugInfo; var module = evalInfo.Frame.Module ?? throw new InvalidOperationException(); var info = dbgAliasProvider.GetAliases(evalInfo); return GetInterpreterStateCommon(evalInfo, null, methodDebugInfo.DebugInfoVersion, module, methodDebugInfo.Method.MDToken.ToInt32(), languageDebugInfo.MethodVersion, MethodDebugScopeUtils.GetScope(methodDebugInfo.Scope, languageDebugInfo.ILOffset), info.aliases, info.typeReferences, options, expression, stateObj, null, out evalExprState); } EvaluateImplResult? GetTypeInterpreterState(DbgEvaluationInfo evalInfo, DmdType type, string expression, DbgEvaluationOptions options, object? stateObj, out EvaluateImplExpressionState? evalExprState) { if (type.TypeSignatureKind != DmdTypeSignatureKind.Type) { evalExprState = null; return new EvaluateImplResult(dnSpy_Debugger_DotNet_Resources.CantEvaluateWhenCurrentFrameIsNative, CreateName(expression), null, null, 0, PredefinedDbgValueNodeImageNames.Error, null); } // This is for evaluating DebuggerDisplayAttribute expressions only, so don't use any aliases. // But pass in the same typeReferences so the module references array doesn't get recreated. var aliases = Array.Empty(); var info = dbgAliasProvider.GetAliases(evalInfo); var typeReferences = info.typeReferences; return GetInterpreterStateCommon(evalInfo, type.Module, 0, type.Module, type.MetadataToken, 0, null, aliases, typeReferences, options, expression, stateObj, type, out evalExprState); } EvaluateImplResult? GetInterpreterStateCommon(DbgEvaluationInfo evalInfo, DmdModule? reflectionModule, int debugInfoVersion, object memberModule, int memberToken, int memberVersion, DbgMethodDebugScope? scope, DbgDotNetAlias[] aliases, DmdType[] typeReferences, DbgEvaluationOptions options, string expression, object? stateObj, DmdType? type, out EvaluateImplExpressionState? evalExprState) { evalExprState = null; EvaluateImplExpressionState? evalState; if (stateObj is not null) { evalState = stateObj as EvaluateImplExpressionState; Debug2.Assert(evalState is not null); if (evalState is null) throw new ArgumentException("Invalid expression evaluator state. It must be null or created by " + nameof(DbgExpressionEvaluator) + "." + nameof(DbgExpressionEvaluator.CreateExpressionEvaluatorState) + "()"); } else evalState = evalInfo.Context.GetOrCreateData(); var refsResult = reflectionModule is not null ? dbgModuleReferenceProvider.GetModuleReferences(evalInfo.Runtime, reflectionModule, typeReferences) : dbgModuleReferenceProvider.GetModuleReferences(evalInfo.Runtime, evalInfo.Frame, typeReferences); if (refsResult.ErrorMessage is not null) return new EvaluateImplResult(refsResult.ErrorMessage, CreateName(expression), null, null, 0, PredefinedDbgValueNodeImageNames.Error, null); var keyOptions = options & ~(DbgEvaluationOptions.NoSideEffects | DbgEvaluationOptions.NoFuncEval); Debug2.Assert(refsResult.ModuleReferences is not null); var key = new EvaluateImplExpressionState.Key(this, debugInfoVersion, memberModule, memberToken, memberVersion, refsResult.ModuleReferences, scope, aliases, keyOptions, expression); if (!evalState.CachedKey.Equals(key)) { evalState.CompilationResult = type is not null ? expressionCompiler.CompileTypeExpression(evalInfo, type, refsResult.ModuleReferences, aliases, expression, keyOptions) : expressionCompiler.CompileExpression(evalInfo, refsResult.ModuleReferences, aliases, expression, keyOptions); evalState.CachedKey = key; evalState.EvaluateImplResult = GetEvaluateImplResult(evalState.CompilationResult, expression); if (evalState.EvaluateImplResult is null) evalState.ILInterpreterState = dnILInterpreter.CreateState(evalState.CompilationResult.Assembly!); else evalState.ILInterpreterState = null; } evalExprState = evalState; return evalState.EvaluateImplResult; } static EvaluateImplResult? GetEvaluateImplResult(in DbgDotNetCompilationResult compRes, string expression) { if (compRes.IsError) return new EvaluateImplResult(compRes.ErrorMessage!, CreateName(expression), null, null, 0, PredefinedDbgValueNodeImageNames.Error, null); Debug.Assert(compRes.CompiledExpressions!.Length == 1); if (compRes.CompiledExpressions.Length != 1) return new EvaluateImplResult(PredefinedEvaluationErrorMessages.InternalDebuggerError, CreateName(expression), null, null, 0, PredefinedDbgValueNodeImageNames.Error, null); var exprInfo = compRes.CompiledExpressions[0]; if (exprInfo.ErrorMessage is not null) return new EvaluateImplResult(exprInfo.ErrorMessage, exprInfo.Name, null, exprInfo.FormatSpecifiers, exprInfo.Flags & ~DbgEvaluationResultFlags.SideEffects, exprInfo.ImageName, null); return null; } static bool HasAllowFuncEval(ReadOnlyCollection? formatSpecifiers) => formatSpecifiers?.Contains(PredefinedFormatSpecifiers.AllowFuncEval) == true; internal EvaluateImplResult EvaluateImpl(DbgEvaluationInfo evalInfo, string expression, DbgEvaluationOptions options, object? stateObj) { try { var errorRes = GetMethodInterpreterState(evalInfo, expression, options, stateObj, out var state); if (errorRes is not null) return errorRes.Value; Debug.Assert(state!.CompilationResult.CompiledExpressions!.Length == 1); ref var exprInfo = ref state.CompilationResult.CompiledExpressions[0]; if ((options & DbgEvaluationOptions.NoSideEffects) != 0 && (exprInfo.Flags & DbgEvaluationResultFlags.SideEffects) != 0 && !HasAllowFuncEval(exprInfo.FormatSpecifiers)) return new EvaluateImplResult(PredefinedEvaluationErrorMessages.ExpressionCausesSideEffects, exprInfo.Name, null, exprInfo.FormatSpecifiers, exprInfo.Flags, exprInfo.ImageName, null); var res = dnILInterpreter.Execute(evalInfo, state.ILInterpreterState!, exprInfo.TypeName, exprInfo.MethodName, options, out var expectedType); if (res.HasError) return new EvaluateImplResult(res.ErrorMessage!, exprInfo.Name, null, exprInfo.FormatSpecifiers, exprInfo.Flags & ~DbgEvaluationResultFlags.SideEffects, exprInfo.ImageName, expectedType); if (res.ValueIsException) return new EvaluateImplResult(null, exprInfo.Name, res.Value, exprInfo.FormatSpecifiers, (exprInfo.Flags & ~DbgEvaluationResultFlags.SideEffects) | DbgEvaluationResultFlags.ThrownException, PredefinedDbgValueNodeImageNames.Error, expectedType); return new EvaluateImplResult(null, exprInfo.Name, res.Value, exprInfo.FormatSpecifiers, exprInfo.Flags, exprInfo.ImageName, expectedType); } catch (Exception ex) when (ExceptionUtils.IsInternalDebuggerError(ex)) { return new EvaluateImplResult(PredefinedEvaluationErrorMessages.InternalDebuggerError, DbgDotNetEngineValueNodeFactoryExtensions.errorName, null, null, DbgEvaluationResultFlags.None, PredefinedDbgValueNodeImageNames.Error, null); } } static DbgDotNetText CreateName(string expression) => new DbgDotNetText(new DbgDotNetTextPart(DbgTextColor.Error, expression)); DbgDotNetEvalResult IDebuggerDisplayAttributeEvaluator.Evaluate(DbgEvaluationInfo evalInfo, DbgDotNetValue obj, string expression, DbgEvaluationOptions options, object? state) { var dispatcher = evalInfo.Runtime.GetDotNetRuntime().Dispatcher; if (dispatcher.CheckAccess()) return EvaluateCore(evalInfo, obj, expression, options, state); return Evaluate2(dispatcher, evalInfo, obj, expression, options, state); DbgDotNetEvalResult Evaluate2(DbgDotNetDispatcher dispatcher2, DbgEvaluationInfo evalInfo2, DbgDotNetValue obj2, string expression2, DbgEvaluationOptions options2, object? state2) { if (!dispatcher2.TryInvokeRethrow(() => EvaluateCore(evalInfo2, obj2, expression2, options2, state2), out var result)) result = new DbgDotNetEvalResult(DispatcherConstants.ProcessExitedError); return result; } } DbgDotNetEvalResult EvaluateCore(DbgEvaluationInfo evalInfo, DbgDotNetValue obj, string expression, DbgEvaluationOptions options, object? state) { var res = EvaluateImpl(evalInfo, obj, expression, options, state); if (res.Error is not null) return new DbgDotNetEvalResult(predefinedEvaluationErrorMessagesHelper.GetErrorMessage(res.Error), res.FormatSpecifiers, res.Flags); return new DbgDotNetEvalResult(res.Value!, res.FormatSpecifiers, res.Flags); } EvaluateImplResult EvaluateImpl(DbgEvaluationInfo evalInfo, DbgDotNetValue obj, string expression, DbgEvaluationOptions options, object? stateObj) { try { var type = obj.Type; if (type.IsGenericType) type = type.GetGenericTypeDefinition(); var errorRes = GetTypeInterpreterState(evalInfo, type, expression, options | DbgEvaluationOptions.NoName, stateObj, out var state); if (errorRes is not null) return errorRes.Value; var genericTypeArguments = obj.Type.GetGenericArguments(); var genericMethodArguments = Array.Empty(); Debug.Assert(state!.CompilationResult.CompiledExpressions!.Length == 1); ref var exprInfo = ref state.CompilationResult.CompiledExpressions[0]; if ((options & DbgEvaluationOptions.NoSideEffects) != 0 && (exprInfo.Flags & DbgEvaluationResultFlags.SideEffects) != 0 && !HasAllowFuncEval(exprInfo.FormatSpecifiers)) return new EvaluateImplResult(PredefinedEvaluationErrorMessages.ExpressionCausesSideEffects, exprInfo.Name, null, exprInfo.FormatSpecifiers, exprInfo.Flags, exprInfo.ImageName, null); var argumentsProvider = new TypeArgumentsProvider(obj); var localsProvider = DummyLocalsProvider.Instance; var res = dnILInterpreter.Execute(evalInfo, genericTypeArguments, genericMethodArguments, argumentsProvider, localsProvider, state.ILInterpreterState!, exprInfo.TypeName, exprInfo.MethodName, options, out var expectedType); if (res.HasError) return new EvaluateImplResult(res.ErrorMessage, exprInfo.Name, null, exprInfo.FormatSpecifiers, exprInfo.Flags & ~DbgEvaluationResultFlags.SideEffects, exprInfo.ImageName, expectedType); if (res.ValueIsException) return new EvaluateImplResult(null, exprInfo.Name, res.Value, exprInfo.FormatSpecifiers, exprInfo.Flags & ~DbgEvaluationResultFlags.SideEffects, PredefinedDbgValueNodeImageNames.Error, expectedType); return new EvaluateImplResult(null, exprInfo.Name, res.Value, exprInfo.FormatSpecifiers, exprInfo.Flags, exprInfo.ImageName, expectedType); } catch (Exception ex) when (ExceptionUtils.IsInternalDebuggerError(ex)) { return new EvaluateImplResult(PredefinedEvaluationErrorMessages.InternalDebuggerError, DbgDotNetEngineValueNodeFactoryExtensions.errorName, null, null, DbgEvaluationResultFlags.None, PredefinedDbgValueNodeImageNames.Error, null); } } sealed class TypeArgumentsProvider : VariablesProvider { readonly DbgDotNetValue argument; public TypeArgumentsProvider(DbgDotNetValue argument) => this.argument = argument; public override void Initialize(DbgEvaluationInfo evalInfo, DmdMethodBase method, DmdMethodBody body) { } public override DbgDotNetValue? GetValueAddress(int index, DmdType targetType) => null; public override DbgDotNetValueResult GetVariable(int index) { if (index == 0) return DbgDotNetValueResult.Create(argument); return DbgDotNetValueResult.CreateError(PredefinedEvaluationErrorMessages.InternalDebuggerError); } public override string? SetVariable(int index, DmdType targetType, object? value) => PredefinedEvaluationErrorMessages.InternalDebuggerError; public override bool CanDispose(DbgDotNetValue value) => value != argument; public override void Clear() { } } sealed class DummyLocalsProvider : VariablesProvider { public static readonly DummyLocalsProvider Instance = new DummyLocalsProvider(); DummyLocalsProvider() { } public override void Initialize(DbgEvaluationInfo evalInfo, DmdMethodBase method, DmdMethodBody body) { } public override DbgDotNetValue? GetValueAddress(int index, DmdType targetType) => null; public override DbgDotNetValueResult GetVariable(int index) => DbgDotNetValueResult.CreateError(PredefinedEvaluationErrorMessages.InternalDebuggerError); public override string? SetVariable(int index, DmdType targetType, object? value) => PredefinedEvaluationErrorMessages.InternalDebuggerError; public override bool CanDispose(DbgDotNetValue value) => true; public override void Clear() { } } } readonly struct EvaluateImplResult { public readonly string? Error; public readonly DbgDotNetText Name; public readonly DbgDotNetValue? Value; public readonly ReadOnlyCollection? FormatSpecifiers; public readonly DbgEvaluationResultFlags Flags; public readonly string ImageName; public readonly DmdType? Type; public EvaluateImplResult(string? error, DbgDotNetText name, DbgDotNetValue? value, ReadOnlyCollection? formatSpecifiers, DbgEvaluationResultFlags flags, string imageName, DmdType? type) { Error = error; Name = name; Value = value; FormatSpecifiers = formatSpecifiers; Flags = flags; ImageName = imageName; Type = type; } } }