context: /*
      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 <http://www.gnu.org/licenses/>.
  */
  
  using System;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.Diagnostics;
  using System.Runtime.CompilerServices;
  using System.Threading;
  using dnSpy.Contracts.Debugger;
  using dnSpy.Contracts.Debugger.CallStack;
  using dnSpy.Contracts.Debugger.DotNet.Disassembly;
  using dnSpy.Contracts.Debugger.DotNet.Evaluation;
  using dnSpy.Contracts.Debugger.DotNet.Mono;
  using dnSpy.Contracts.Debugger.Engine.Evaluation;
  using dnSpy.Contracts.Debugger.Evaluation;
  using dnSpy.Contracts.Disassembly;
  using dnSpy.Contracts.Metadata;
  using dnSpy.Debugger.DotNet.Metadata;
  using dnSpy.Debugger.DotNet.Mono.CallStack;
  using dnSpy.Debugger.DotNet.Mono.Impl.Evaluation.Hooks;
  using dnSpy.Debugger.DotNet.Mono.Properties;
  using Mono.Debugger.Soft;
  
  namespace dnSpy.Debugger.DotNet.Mono.Impl.Evaluation {
  	sealed class DbgMonoDebugInternalRuntimeImpl : DbgMonoDebugInternalRuntime, IDbgDotNetRuntime, IMonoDebugRuntime {
  		public override MonoDebugRuntimeKind Kind { get; }
  		public override DmdRuntime ReflectionRuntime { get; }
  		public override DbgRuntime Runtime { get; }
  		public DbgDotNetDispatcher Dispatcher { get; }
  		public DbgDotNetRuntimeFeatures Features { get; }
  
  		IMonoDebugValueConverter IMonoDebugRuntime.ValueConverter => monoDebugValueConverter;
  
  		readonly DbgEngineImpl engine;
  		readonly Dictionary<DmdWellKnownType, ClassHook> classHooks;
  		readonly IMonoDebugValueConverter monoDebugValueConverter;
  
  		public DbgMonoDebugInternalRuntimeImpl(DbgEngineImpl engine, DbgRuntime runtime, DmdRuntime reflectionRuntime, MonoDebugRuntimeKind monoDebugRuntimeKind) {
  			this.engine = engine ?? throw new ArgumentNullException(nameof(engine));
  			Runtime = runtime ?? throw new ArgumentNullException(nameof(runtime));
  			ReflectionRuntime = reflectionRuntime ?? throw new ArgumentNullException(nameof(reflectionRuntime));
  			Kind = monoDebugRuntimeKind;
  			Dispatcher = new DbgDotNetDispatcherImpl(engine);
  			Features = CalculateFeatures(engine.MonoVirtualMachine);
  			reflectionRuntime.GetOrCreateData(() => runtime);
  
  			monoDebugValueConverter = new MonoDebugValueConverterImpl(this);
  			classHooks = new Dictionary<DmdWellKnownType, ClassHook>();
  			foreach (var info in ClassHookProvider.Create(engine, this)) {
  				Debug.Assert(!(info.Hook is null));
  				Debug.Assert(!classHooks.ContainsKey(info.WellKnownType));
  				classHooks.Add(info.WellKnownType, info.Hook);
  			}
  		}
  
  		static DbgDotNetRuntimeFeatures CalculateFeatures(VirtualMachine vm) {
  			var res = DbgDotNetRuntimeFeatures.ObjectIds | DbgDotNetRuntimeFeatures.NoDereferencePointers;
  			if (!vm.Version.AtLeast(2, 24))
  				res |= DbgDotNetRuntimeFeatures.NoGenericMethods;
  			// We need FuncEvalOptions.ReturnOutThis support so func-eval of Task/ObjectIdForDebugger
  			// prop updates the struct's task field
  			if (!vm.Version.AtLeast(2, 35))
  				res |= DbgDotNetRuntimeFeatures.NoAsyncStepObjectId;
  			return res;
  		}
  
  		public ModuleId GetModuleId(DbgModule module) => engine.GetModuleId(module);
  
  		public DbgDotNetRawModuleBytes GetRawModuleBytes(DbgModule module) {
  			if (!module.IsDynamic)
  				return DbgDotNetRawModuleBytes.None;
  			if (Dispatcher.CheckAccess())
  				return GetRawModuleBytesCore(module);
  			return GetRawModuleBytesCore2(module);
  
  			DbgDotNetRawModuleBytes GetRawModuleBytesCore2(DbgModule module2) {
  				if (!Dispatcher.TryInvokeRethrow(() => GetRawModuleBytesCore(module2), out var result))
  					result = DbgDotNetRawModuleBytes.None;
  				return result;
  			}
  		}
  
  		DbgDotNetRawModuleBytes GetRawModuleBytesCore(DbgModule module) {
  			Dispatcher.VerifyAccess();
  			if (!module.IsDynamic)
  				return DbgDotNetRawModuleBytes.None;
  
  			return DbgDotNetRawModuleBytes.None;//TODO:
  		}
  
  		public bool TryGetMethodToken(DbgModule module, int methodToken, out int metadataMethodToken, out int metadataLocalVarSigTok) {
  			if (!module.IsDynamic) {
  				metadataMethodToken = 0;
  				metadataLocalVarSigTok = 0;
  				return false;
  			}
  
  			if (Dispatcher.CheckAccess())
  				return TryGetMethodTokenCore(module, methodToken, out metadataMethodToken, out metadataLocalVarSigTok);
  			return TryGetMethodTokenCore2(module, methodToken, out metadataMethodToken, out metadataLocalVarSigTok);
  
  			bool TryGetMethodTokenCore2(DbgModule module2, int methodToken2, out int metadataMethodToken2, out int metadataLocalVarSigTok2) {
  				int tmpMetadataMethodToken = 0, tmpMetadataLocalVarSigTok = 0;
  				if (!Dispatcher.TryInvokeRethrow(() => {
  					var res = TryGetMethodTokenCore(module2, methodToken2, out var metadataMethodToken3, out var metadataLocalVarSigTok3);
  					tmpMetadataMethodToken = metadataMethodToken3;
  					tmpMetadataLocalVarSigTok = metadataLocalVarSigTok3;
  					return res;
  				}, out var result)) {
  					metadataMethodToken2 = 0;
  					metadataLocalVarSigTok2 = 0;
  					return false;
  				}
  				metadataMethodToken2 = tmpMetadataMethodToken;
  				metadataLocalVarSigTok2 = tmpMetadataLocalVarSigTok;
  				return result;
  			}
  		}
  
  		bool TryGetMethodTokenCore(DbgModule module, int methodToken, out int metadataMethodToken, out int metadataLocalVarSigTok) {
  			Dispatcher.VerifyAccess();
  
  			//TODO:
  			metadataMethodToken = 0;
  			metadataLocalVarSigTok = 0;
  			return false;
  		}
  
  		sealed class GetFrameMethodState {
  			public bool Initialized;
  			public DmdMethodBase? Method;
  		}
  
  		public DmdMethodBase? GetFrameMethod(DbgEvaluationInfo evalInfo) {
  			if (Dispatcher.CheckAccess())
  				return GetFrameMethodCore(evalInfo);
  			return GetFrameMethod2(evalInfo);
  
  			DmdMethodBase? GetFrameMethod2(DbgEvaluationInfo evalInfo2) {
  				Dispatcher.TryInvokeRethrow(() => GetFrameMethodCore(evalInfo2), out var result);
  				return result;
  			}
  		}
  
  		DmdMethodBase? GetFrameMethodCore(DbgEvaluationInfo evalInfo) {
  			Dispatcher.VerifyAccess();
  			var state = evalInfo.Frame.GetOrCreateData<GetFrameMethodState>();
  			if (!state.Initialized) {
  				evalInfo.CancellationToken.ThrowIfCancellationRequested();
  				if (ILDbgEngineStackFrame.TryGetEngineStackFrame(evalInfo.Frame, out var ilFrame)) {
  					ilFrame.GetFrameMethodInfo(out var module, out var methodMetadataToken, out var genericTypeArguments, out var genericMethodArguments);
  					// Don't throw if it fails to resolve. Callers must be able to handle null return values
  					state.Method = TryGetMethod(module, methodMetadataToken, genericTypeArguments, genericMethodArguments);
  				}
  				state.Initialized = true;
  			}
  			return state.Method;
  		}
  
  		static DmdMethodBase? TryGetMethod(DmdModule module, int methodMetadataToken, IList<DmdType> genericTypeArguments, IList<DmdType> genericMethodArguments) {
  			var method = module?.ResolveMethod(methodMetadataToken, (IList<DmdType>?)null, null, DmdResolveOptions.None);
  			if (!(method is null)) {
  				if (genericTypeArguments.Count != 0) {
  					var type = method.ReflectedType!.MakeGenericType(genericTypeArguments);
  					method = type.GetMethod(method.Module, method.MetadataToken, throwOnError: true)!;
  				}
  				if (genericMethodArguments.Count != 0)
  					method = ((DmdMethodInfo)method).MakeGenericMethod(genericMethodArguments);
  			}
  			return method;
  		}
  
  		TypeMirror GetType(DbgEvaluationInfo evalInfo, DmdType type) =>
  			MonoDebugTypeCreator.GetType(engine, type, engine.CreateMonoTypeLoader(evalInfo));
  
  		public DbgDotNetValue? LoadFieldAddress(DbgEvaluationInfo evalInfo, DbgDotNetValue? obj, DmdFieldInfo field) => null;
  
  		public DbgDotNetValueResult LoadField(DbgEvaluationInfo evalInfo, DbgDotNetValue? obj, DmdFieldInfo field) {
  			if (Dispatcher.CheckAccess())
  				return LoadFieldCore(evalInfo, obj, field);
  			return LoadField2(evalInfo, obj, field);
  
  			DbgDotNetValueResult LoadField2(DbgEvaluationInfo evalInfo2, DbgDotNetValue? obj2, DmdFieldInfo field2) {
  				if (!Dispatcher.TryInvokeRethrow(() => LoadFieldCore(evalInfo2, obj2, field2), out var result))
  					result = DbgDotNetValueResult.CreateError(DispatcherConstants.ProcessExitedError);
  				return result;
  			}
  		}
  
  		DbgDotNetValueResult LoadFieldCore(DbgEvaluationInfo evalInfo, DbgDotNetValue? obj, DmdFieldInfo field) {
  			Dispatcher.VerifyAccess();
  			try {
  				var info = GetFieldValueLocationCore(evalInfo, obj, field);
  				if (!(info.errorMessage is null))
  					return DbgDotNetValueResult.CreateError(info.errorMessage);
  				return DbgDotNetValueResult.Create(engine.CreateDotNetValue_MonoDebug(info.valueLocation!));
  			}
  			catch (Exception ex) when (ExceptionUtils.IsInternalDebuggerError(ex)) {
  				return DbgDotNetValueResult.CreateError(PredefinedEvaluationErrorMessages.InternalDebuggerError);
  			}
  		}
  
  		(ValueLocation? valueLocation, string? errorMessage) GetFieldValueLocationCore(DbgEvaluationInfo evalInfo, DbgDotNetValue? obj, DmdFieldInfo field) {
  			if (!ILDbgEngineStackFrame.TryGetEngineStackFrame(evalInfo.Frame, out var ilFrame))
  				return (null, PredefinedEvaluationErrorMessages.InternalDebuggerError);
  
  			var fieldDeclType = field.DeclaringType!;
  			bool canNotAccessField = !engine.MonoVirtualMachine.Version.AtLeast(2, 15) && fieldDeclType.ContainsGenericParameters;
  			var monoFieldDeclType = GetType(evalInfo, fieldDeclType);
  			if (obj is null) {
  				if (!field.IsStatic)
  					return (null, PredefinedEvaluationErrorMessages.InternalDebuggerError);
  
  
gt: 				if (field.IsLiteral) {