/* 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 . */ // Test class that doesn't call a real CLR so only the interpreter code can be tested. It doesn't // support calling methods so all called methods are hard coded to return certain values. If a test // adds a new call, this class would need to be updated. using System; using System.Collections.Generic; using dnSpy.Debugger.DotNet.Metadata; namespace dnSpy.Debugger.DotNet.Interpreter.Tests.Fake { sealed class TestRuntimeImpl : TestRuntime { public override DmdRuntime Runtime { get; } public override DebuggerRuntime DebuggerRuntime => debuggerRuntime; readonly FakeDebuggerRuntime debuggerRuntime; public TestRuntimeImpl(DmdRuntime runtime) { Runtime = runtime ?? throw new ArgumentNullException(nameof(runtime)); debuggerRuntime = new FakeDebuggerRuntime(runtime); } public override void SetMethodExecState(ILValue[] arguments, DmdMethodBody body) => debuggerRuntime.SetMethodExecState(arguments, body); } sealed class ConstantStringILValue : TypeILValue { public string Value { get; } public override DmdType Type { get; } public ConstantStringILValue(DmdType type, string value) { Type = type; Value = value; } public override string ToString() => "\"" + Value + "\""; } sealed class FakeDebuggerRuntime : DebuggerRuntime { readonly DmdRuntime runtime; public FakeDebuggerRuntime(DmdRuntime runtime) { this.runtime = runtime ?? throw new ArgumentNullException(nameof(runtime)); staticFields = new StaticFields(); } sealed class StaticFields { public Dictionary AllFields { get; } = new Dictionary(DmdMemberInfoEqualityComparer.DefaultMember); readonly HashSet initdTypes = new HashSet(DmdMemberInfoEqualityComparer.DefaultType); public void Clear() { AllFields.Clear(); initdTypes.Clear(); } void InitFields(DmdType type) { if (!initdTypes.Contains(type)) { initdTypes.Add(type); foreach (var f in type.Fields) { if (f.IsStatic) AllFields[f] = CreateDefaultValue(f.FieldType); } } } public ILValue? LoadField(DmdFieldInfo field) { InitFields(field.ReflectedType); return AllFields[field]; } public bool StoreField(DmdFieldInfo field, ILValue value) { InitFields(field.ReflectedType); if (!AllFields.ContainsKey(field)) return false; AllFields[field] = value; return true; } } ILValue[] arguments; ILValue[] locals; readonly StaticFields staticFields; public void SetMethodExecState(ILValue[] arguments, DmdMethodBody body) { staticFields.Clear(); this.arguments = (ILValue[])arguments.Clone(); locals = new ILValue[body.LocalVariables.Count]; for (int i = 0; i < locals.Length; i++) locals[i] = CreateDefaultValue(body.LocalVariables[i].LocalType); } static ILValue CreateDefaultValue(DmdType type) { switch (DmdType.GetTypeCode(type)) { case TypeCode.Boolean: case TypeCode.Char: case TypeCode.SByte: case TypeCode.Byte: case TypeCode.Int16: case TypeCode.UInt16: case TypeCode.Int32: case TypeCode.UInt32: return new ConstantInt32ILValue(type.AppDomain, 0); case TypeCode.Int64: case TypeCode.UInt64: return new ConstantInt64ILValue(type.AppDomain, 0); case TypeCode.Single: case TypeCode.Double: return new ConstantFloatILValue(type.AppDomain, 0); } if (type == type.AppDomain.System_IntPtr || type == type.AppDomain.System_UIntPtr) return IntPtr.Size == 4 ? ConstantNativeIntILValue.Create32(type.AppDomain, 0) : ConstantNativeIntILValue.Create64(type.AppDomain, 0); if (type.IsValueType) return new FakeValueType(type); return new NullObjectRefILValue(); } public override void Initialize(DmdMethodBase method, DmdMethodBody body) { } sealed class FakeValueType : TypeILValue { readonly Dictionary fields; public override DmdType Type { get; } public FakeValueType(FakeValueType other) { Type = other.Type; fields = new Dictionary(other.fields, DmdMemberInfoEqualityComparer.DefaultMember); } public FakeValueType(DmdType type) { Type = type; fields = new Dictionary(DmdMemberInfoEqualityComparer.DefaultMember); ResetFields(); } public void ResetFields() { foreach (var f in Type.Fields) { if (!f.IsStatic) fields[f] = CreateDefaultValue(f.FieldType); } } const int DUMMY_PTR_BASE = 0x4736DC7B; public override bool Call(bool isCallvirt, DmdMethodBase method, ILValue[] arguments, out ILValue? returnValue) { var ad = method.AppDomain; var type = method.ReflectedType; if (type == ad.GetWellKnownType(DmdWellKnownType.System_RuntimeTypeHandle)) { if (method.Name == "get_Value") { returnValue = ad.Runtime.PointerSize == 4 ? ConstantNativeIntILValue.Create32(ad, DUMMY_PTR_BASE + 0) : ConstantNativeIntILValue.Create32(ad, DUMMY_PTR_BASE + 0); return true; } } else if (type == ad.GetWellKnownType(DmdWellKnownType.System_RuntimeFieldHandle)) { if (method.Name == "get_Value") { returnValue = ad.Runtime.PointerSize == 4 ? ConstantNativeIntILValue.Create32(ad, DUMMY_PTR_BASE + 1) : ConstantNativeIntILValue.Create32(ad, DUMMY_PTR_BASE + 1); return true; } } else if (type == ad.GetWellKnownType(DmdWellKnownType.System_RuntimeMethodHandle)) { if (method.Name == "get_Value") { returnValue = ad.Runtime.PointerSize == 4 ? ConstantNativeIntILValue.Create32(ad, DUMMY_PTR_BASE + 2) : ConstantNativeIntILValue.Create32(ad, DUMMY_PTR_BASE + 2); return true; } } else if (type.FullName == "dnSpy.Debugger.DotNet.Interpreter.Tests.MyStruct") { switch (method.Name) { case "InstanceMethod1": returnValue = new ConstantInt32ILValue(ad, 123); return true; case "InstanceMethod2": returnValue = arguments[0].Clone(); return true; case "InstanceMethod3": returnValue = arguments[1].Clone(); return true; case "InstanceMethod4": returnValue = arguments[2].Clone(); return true; } } returnValue = null; return false; } public override ILValue Clone() => new FakeValueType(this); public override ILValue? LoadField(DmdFieldInfo field) => fields[field]; public override bool StoreField(DmdFieldInfo field, ILValue value) { if (!fields.ContainsKey(field)) return false; fields[field] = value; return true; } public override ILValue? LoadFieldAddress(DmdFieldInfo field) { if (!fields.ContainsKey(field)) return null; return new FakeFieldAddress(fields, field); } public override bool InitializeObject(DmdType type) { ResetFields(); return true; } } sealed class FakeReferenceType : TypeILValue { readonly Dictionary fields; public override DmdType Type { get; } public FakeReferenceType(DmdType type) { Type = type; fields = new Dictionary(DmdMemberInfoEqualityComparer.DefaultMember); ResetFields(); } void ResetFields() { foreach (var f in Type.Fields) { if (!f.IsStatic) fields[f] = CreateDefaultValue(f.FieldType); } } public override ILValue? LoadField(DmdFieldInfo field) => fields[field]; public override bool StoreField(DmdFieldInfo field, ILValue value) { if (!fields.ContainsKey(field)) return false; fields[field] = value; return true; } public override ILValue? LoadFieldAddress(DmdFieldInfo field) { if (!fields.ContainsKey(field)) return null; return new FakeFieldAddress(fields, field); } public override bool Call(bool isCallvirt, DmdMethodBase method, ILValue[] arguments, out ILValue? returnValue) { var ad = method.AppDomain; var type = method.ReflectedType; if (type.FullName == "dnSpy.Debugger.DotNet.Interpreter.Tests.MyClass") { switch (method.Name) { case "InstanceMethod1": returnValue = new ConstantInt32ILValue(ad, 123); return true; case "InstanceMethod2": returnValue = arguments[0].Clone(); return true; case "InstanceMethod3": returnValue = arguments[1].Clone(); return true; case "InstanceMethod4": returnValue = arguments[2].Clone(); return true; } } returnValue = null; return false; } } sealed class FakeFieldAddress : ByRefILValue, IEquatable { public Dictionary Fields { get; } public DmdFieldInfo Field { get; } public FakeFieldAddress(Dictionary fields, DmdFieldInfo field) { Fields = fields; Field = field; } public override ILValue? LoadIndirect(DmdType type, LoadValueType loadValueType) => Fields[Field]; public override bool StoreIndirect(DmdType type, LoadValueType loadValueType, ILValue value) { Fields[Field] = value; return true; } public override bool CopyObject(DmdType type, ILValue source) { Fields[Field] = source.LoadIndirect(type.AppDomain.System_Object, LoadValueType.Ref); return true; } public override bool InitializeObject(DmdType type) => Fields[Field].InitializeObject(type); public override DmdType Type => Field.FieldType; public bool Equals(FakeFieldAddress other) => Fields == other.Fields && Field == other.Field; } abstract class ArgOrLocalAddress : ByRefILValue, IEquatable { public ILValue[] Collection { get; } public long Index { get; } protected ArgOrLocalAddress(ILValue[] collection, long index) { Collection = collection; Index = index; } public override ILValue? LoadIndirect(DmdType type, LoadValueType loadValueType) => Collection[Index]; public override bool StoreIndirect(DmdType type, LoadValueType loadValueType, ILValue value) { Collection[Index] = value; return true; } public override bool CopyObject(DmdType type, ILValue source) { Collection[Index] = source.LoadIndirect(type.AppDomain.System_Object, LoadValueType.Ref); return true; } public override bool InitializeObject(DmdType type) => Collection[Index].InitializeObject(type); public override ILValue? LoadField(DmdFieldInfo field) => Collection[Index].LoadField(field); public override ILValue? LoadFieldAddress(DmdFieldInfo field) => Collection[Index].LoadFieldAddress(field); public override bool StoreField(DmdFieldInfo field, ILValue value) => Collection[Index].StoreField(field, value); public override bool Call(bool isCallvirt, DmdMethodBase method, ILValue[] arguments, out ILValue? returnValue) => Collection[Index].Call(isCallvirt, method, arguments, out returnValue); public override DmdType Type => Collection[Index].Type.MakeByRefType(); public bool Equals(ArgOrLocalAddress other) => Collection == other.Collection && Index == other.Index; } sealed class ArgumentAddress : ArgOrLocalAddress { public ArgumentAddress(ILValue[] arguments, long index) : base(arguments, index) { } } sealed class LocalAddress : ArgOrLocalAddress { public LocalAddress(ILValue[] locals, long index) : base(locals, index) { } } public override int PointerSize => runtime.PointerSize; public override ILValue? LoadArgument(int index) => arguments[index]; public override ILValue? LoadLocal(int index) => locals[index]; public override ILValue? LoadArgumentAddress(int index, DmdType type) => new ArgumentAddress(arguments, index); public override ILValue? LoadLocalAddress(int index, DmdType type) => new LocalAddress(locals, index); public override bool StoreArgument(int index, DmdType type, ILValue value) { arguments[index] = value; return true; } public override bool StoreLocal(int index, DmdType type, ILValue value) { locals[index] = value; return true; } static void InitializeValue(ref ILValue value, DmdType valueType) { if (value is not null) return; if (!valueType.IsValueType) { value = new NullObjectRefILValue(); return; } switch (DmdType.GetTypeCode(valueType)) { case TypeCode.Boolean: value = new ConstantInt32ILValue(valueType.AppDomain, 0); return; case TypeCode.Char: value = new ConstantInt32ILValue(valueType.AppDomain, 0); return; case TypeCode.SByte: value = new ConstantInt32ILValue(valueType.AppDomain, 0); return; case TypeCode.Byte: value = new ConstantInt32ILValue(valueType.AppDomain, 0); return; case TypeCode.Int16: value = new ConstantInt32ILValue(valueType.AppDomain, 0); return; case TypeCode.UInt16: value = new ConstantInt32ILValue(valueType.AppDomain, 0); return; case TypeCode.Int32: value = new ConstantInt32ILValue(valueType.AppDomain, 0); return; case TypeCode.UInt32: value = new ConstantInt32ILValue(valueType.AppDomain, 0); return; case TypeCode.Int64: value = new ConstantInt64ILValue(valueType.AppDomain, 0); return; case TypeCode.UInt64: value = new ConstantInt64ILValue(valueType.AppDomain, 0); return; case TypeCode.Single: value = new ConstantFloatILValue(valueType.AppDomain, 0); return; case TypeCode.Double: value = new ConstantFloatILValue(valueType.AppDomain, 0); return; } if (valueType == valueType.AppDomain.System_IntPtr || valueType == valueType.AppDomain.System_UIntPtr) { value = valueType.AppDomain.Runtime.PointerSize == 4 ? ConstantNativeIntILValue.Create32(valueType.AppDomain, 0) : ConstantNativeIntILValue.Create64(valueType.AppDomain, 0); return; } throw new InvalidOperationException(); } sealed class SZArrayILValue : TypeILValue { public override DmdType Type { get; } readonly ILValue[] elements; public ILValue this[long index] { get { ref var value = ref elements[index]; InitializeValue(ref value, Type.GetElementType()); return value; } set => elements[index] = value ?? throw new InvalidOperationException(); } public SZArrayILValue(DmdType elementType, long length) { Type = elementType.MakeArrayType(); elements = new ILValue[length]; } public override ILValue? LoadSZArrayElement(LoadValueType loadValueType, long index, DmdType elementType) => this[index]; public override ILValue? LoadSZArrayElementAddress(long index, DmdType elementType) => new SZArrayAddress(this, index); public override bool StoreSZArrayElement(LoadValueType loadValueType, long index, ILValue value, DmdType elementType) { this[index] = value; return true; } public override bool GetSZArrayLength(out long length) { length = elements.Length; return true; } } sealed class SZArrayAddress : ByRefILValue, IEquatable { public SZArrayILValue ArrayValue { get; } public long Index { get; } public SZArrayAddress(SZArrayILValue arrayValue, long index) { ArrayValue = arrayValue; Index = index; } public override ILValue? LoadIndirect(DmdType type, LoadValueType loadValueType) => ArrayValue[Index]; public override bool StoreIndirect(DmdType type, LoadValueType loadValueType, ILValue value) { ArrayValue[Index] = value; return true; } public override bool CopyObject(DmdType type, ILValue source) { ArrayValue[Index] = source.LoadIndirect(type.AppDomain.System_Object, LoadValueType.Ref); return true; } public override bool InitializeObject(DmdType type) => ArrayValue[Index].InitializeObject(type); public override ILValue? LoadField(DmdFieldInfo field) => ArrayValue[Index].LoadField(field); public override ILValue? LoadFieldAddress(DmdFieldInfo field) => ArrayValue[Index].LoadFieldAddress(field); public override bool StoreField(DmdFieldInfo field, ILValue value) => ArrayValue[Index].StoreField(field, value); public override bool Call(bool isCallvirt, DmdMethodBase method, ILValue[] arguments, out ILValue? returnValue) => ArrayValue[Index].Call(isCallvirt, method, arguments, out returnValue); public override DmdType Type => ArrayValue.Type.MakeByRefType(); public bool Equals(SZArrayAddress other) => ArrayValue == other.ArrayValue && Index == other.Index; } public override ILValue CreateSZArray(DmdType elementType, long length) => new SZArrayILValue(elementType, length); public override ILValue? CreateRuntimeTypeHandle(DmdType type) { var rht = type.AppDomain.GetWellKnownType(DmdWellKnownType.System_RuntimeTypeHandle); return new FakeValueType(rht); } public override ILValue? CreateRuntimeFieldHandle(DmdFieldInfo field) { var rht = field.ReflectedType.AppDomain.GetWellKnownType(DmdWellKnownType.System_RuntimeFieldHandle); return new FakeValueType(rht); } public override ILValue? CreateRuntimeMethodHandle(DmdMethodBase method) { var rht = method.ReflectedType.AppDomain.GetWellKnownType(DmdWellKnownType.System_RuntimeMethodHandle); return new FakeValueType(rht); } public override ILValue? CreateTypeNoConstructor(DmdType type) { if (type.IsValueType) return new FakeValueType(type); return new FakeReferenceType(type); } public override ILValue? Box(ILValue value, DmdType type) => new BoxedValueTypeILValue(value, type); public override bool CallStatic(DmdMethodBase method, ILValue[] arguments, out ILValue? returnValue) { var ad = method.AppDomain; var type = method.ReflectedType; if (type.FullName == "dnSpy.Debugger.DotNet.Interpreter.Tests.MyStruct") { switch (method.Name) { case "StaticMethod1": returnValue = new ConstantInt32ILValue(ad, 123); return true; case "StaticMethod2": returnValue = arguments[0].Clone(); return true; case "StaticMethod3": returnValue = arguments[1].Clone(); return true; case "StaticMethod4": returnValue = arguments[2].Clone(); return true; } } else if (type.FullName == "dnSpy.Debugger.DotNet.Interpreter.Tests.MyClass") { switch (method.Name) { case "StaticMethod1": returnValue = new ConstantInt32ILValue(ad, 123); return true; case "StaticMethod2": returnValue = arguments[0].Clone(); return true; case "StaticMethod3": returnValue = arguments[1].Clone(); return true; case "StaticMethod4": returnValue = arguments[2].Clone(); return true; } } returnValue = null; return false; } public override ILValue? CreateInstance(DmdConstructorInfo ctor, ILValue[] arguments) { var type = ctor.ReflectedType; if (type.FullName == "dnSpy.Debugger.DotNet.Interpreter.Tests.MyStruct") { if (arguments.Length == 2) return new FakeValueType(ctor.ReflectedType); } else if (type.FullName == "dnSpy.Debugger.DotNet.Interpreter.Tests.MyClass") { if (arguments.Length == 0) return new FakeReferenceType(ctor.ReflectedType); if (arguments.Length == 2) return new FakeReferenceType(ctor.ReflectedType); } return null; } public override bool CallStaticIndirect(DmdMethodSignature methodSig, ILValue methodAddress, ILValue[] arguments, out ILValue? returnValue) => throw new NotImplementedException(); public override ILValue? LoadStaticField(DmdFieldInfo field) => staticFields.LoadField(field); public override ILValue? LoadStaticFieldAddress(DmdFieldInfo field) => new FakeFieldAddress(staticFields.AllFields, field); public override bool StoreStaticField(DmdFieldInfo field, ILValue value) { staticFields.StoreField(field, value); return true; } public override ILValue LoadString(DmdType type, string value) => new ConstantStringILValue(type, value); public override int? CompareSigned(ILValue left, ILValue right) => null; public override int? CompareUnsigned(ILValue left, ILValue right) => null; public override bool? Equals(ILValue left, ILValue right) { if (left is ArgOrLocalAddress addr1 && right is ArgOrLocalAddress addr2) return addr1.Equals(addr2); if (left is SZArrayAddress arad1 && right is SZArrayAddress arad2) return arad1.Equals(arad2); return null; } } sealed class BoxedValueTypeILValue : TypeILValue { public override DmdType Type { get; } readonly ILValue value; public BoxedValueTypeILValue(ILValue value, DmdType type) { this.value = value.Clone(); Type = type; } public override ILValue? UnboxAny(DmdType type) => value.Clone(); } }