From 81bf8c30db2f8202d9f5af1c2f3bfdcf46103a29 Mon Sep 17 00:00:00 2001 From: Washi Date: Sat, 23 Mar 2024 20:08:46 +0100 Subject: [PATCH] Add constrained opcode handling. --- .../Emulation/CilThread.cs | 53 ++++--- .../Dispatch/ObjectModel/CallHandler.cs | 16 +- .../Dispatch/ObjectModel/CallHandlerBase.cs | 131 +++++++++++++++- .../Dispatch/ObjectModel/CallVirtHandler.cs | 146 ++++-------------- .../ObjectModel/ConstrainedHandler.cs | 23 +++ .../Dispatch/ObjectModel/NewObjHandler.cs | 1 - .../Emulation/Stack/CallFrame.cs | 56 +++---- .../Emulation/CilVirtualMachineTest.cs | 39 +++++ .../Dispatch/ObjectModel/CallHandlerTest.cs | 47 ++++++ .../ObjectModel/CallVirtHandlerTest.cs | 34 +++- test/Platforms/Mocks/Mocks.csproj | 6 +- test/Platforms/Mocks/SimpleClass.cs | 2 + 12 files changed, 367 insertions(+), 187 deletions(-) create mode 100644 src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/ConstrainedHandler.cs diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilThread.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilThread.cs index 8fd524b3..db1cfbfe 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilThread.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/CilThread.cs @@ -242,27 +242,44 @@ private void Step(CilExecutionContext context) // Determine the next instruction to dispatch. int pc = currentFrame.ProgramCounter; - var instruction = body.Instructions.GetByOffset(pc); - if (instruction is null) - throw new CilEmulatorException($"Invalid program counter in {currentFrame}."); + int index = body.Instructions.GetIndexByOffset(pc); - // Are we entering any protected regions? - UpdateExceptionHandlerStack(); - - // Dispatch the instruction. - var result = Machine.Dispatcher.Dispatch(context, instruction); - - if (!result.IsSuccess) + // Note: This is a loop because instructions can have prefixes. A single step must execute an entire + // instruction including its prefixes. + bool continuing = true; + while (continuing) { - var exceptionObject = result.ExceptionObject; - if (exceptionObject.IsNull) - throw new CilEmulatorException("A null exception object was thrown."); - - // If there were any errors thrown after dispatching, it may trigger the execution of one of the - // exception handlers in the entire call stack. - if (!UnwindCallStack(ref exceptionObject)) - throw new EmulatedException(exceptionObject); + var instruction = body.Instructions[index]; + if (instruction is null) + throw new CilEmulatorException($"Invalid program counter in {currentFrame}."); + + // Continue until we are no longer a prefix instruction. + continuing = instruction.OpCode.OpCodeType == CilOpCodeType.Prefix; + index++; + + // Are we entering any protected regions? + UpdateExceptionHandlerStack(); + + // Dispatch the instruction. + var result = Machine.Dispatcher.Dispatch(context, instruction); + + // Did we throw an exception? + if (!result.IsSuccess) + { + var exceptionObject = result.ExceptionObject; + if (exceptionObject.IsNull) + throw new CilEmulatorException("A null exception object was thrown."); + + // If there were any errors thrown after dispatching, it may trigger the execution of one of the + // exception handlers in the entire call stack. If no handler is catching it, we communicate it to + // the caller of the Echo API itself. + if (!UnwindCallStack(ref exceptionObject)) + throw new EmulatedException(exceptionObject); + } } + + // Reset any prefix-related flags. + currentFrame.ConstrainedType = null; } private void UpdateExceptionHandlerStack() diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandler.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandler.cs index b4352908..70cce60d 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandler.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandler.cs @@ -12,11 +12,23 @@ namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel public class CallHandler : CallHandlerBase { /// - protected override MethodDevirtualizationResult DevirtualizeMethodInternal(CilExecutionContext context, - CilInstruction instruction, + protected override MethodDevirtualizationResult DevirtualizeMethodInternal( + CilExecutionContext context, IMethodDescriptor method, IList arguments) { + // If we are constraining on a specific declaring type, we need to simulate a "virtual dispatch" on that type. + if (context.CurrentFrame.ConstrainedType?.Resolve() is { } constrainedType) + { + var resolvedBaseMethod = method.Resolve(); + if (resolvedBaseMethod is { IsVirtual: true }) + { + var implementationMethod = FindMethodImplementationInType(constrainedType, resolvedBaseMethod); + if (implementationMethod is not null) + return MethodDevirtualizationResult.Success(implementationMethod); + } + } + return MethodDevirtualizationResult.Success(method); } } diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandlerBase.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandlerBase.cs index bb444833..4e572951 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandlerBase.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallHandlerBase.cs @@ -47,6 +47,7 @@ protected virtual bool ShouldPopInstanceObject(IMethodDescriptor method) /// /// The context to evaluate the instruction in. /// The instruction to dispatch and evaluate. + /// The method that is called. /// The arguments to call the method with. /// The dispatching result. protected CilDispatchResult HandleCall( @@ -115,12 +116,13 @@ private static BitVector GetInstancePointer(CilExecutionContext context, IMethod return stack.Pop(declaringType); } - private MethodDevirtualizationResult DevirtualizeMethod(CilExecutionContext context, + private MethodDevirtualizationResult DevirtualizeMethod( + CilExecutionContext context, CilInstruction instruction, IMethodDescriptor method, IList arguments) { - var result = DevirtualizeMethodInternal(context, instruction, method, arguments); + var result = DevirtualizeMethodInternal(context, method, arguments); if (!result.IsUnknown) return result; @@ -137,14 +139,14 @@ private MethodDevirtualizationResult DevirtualizeMethod(CilExecutionContext cont /// Devirtualizes and resolves the method that is referenced by the provided instruction. /// /// The execution context the instruction is evaluated in. - /// The instruction that is being evaluated. - /// + /// The method that is being devirtualized. /// The arguments pushed onto the stack. /// The result of the devirtualization. - protected abstract MethodDevirtualizationResult DevirtualizeMethodInternal(CilExecutionContext context, - CilInstruction instruction, + protected abstract MethodDevirtualizationResult DevirtualizeMethodInternal( + CilExecutionContext context, IMethodDescriptor method, - IList arguments); + IList arguments + ); private static IMethodDescriptor InstantiateDeclaringType(IMethodDescriptor caller, IMethodDescriptor callee) { @@ -208,5 +210,120 @@ private static CilDispatchResult Invoke(CilExecutionContext context, IMethodDesc throw new ArgumentOutOfRangeException(); } } + + protected static MethodDefinition? FindMethodImplementationInType(TypeDefinition? type, MethodDefinition? baseMethod) + { + if (type is null || baseMethod is null || !baseMethod.IsVirtual) + return baseMethod; + + var implementation = default(MethodDefinition); + var declaringType = baseMethod.DeclaringType!; + + // If this is a static method, it means we must be implementing a 'static abstract' interface method. + if (baseMethod.IsStatic && !declaringType.IsInterface) + return baseMethod; + + while (type is not null && !SignatureComparer.Default.Equals(type, declaringType)) + { + if (baseMethod.IsStatic) + { + // Static base methods can only be implemented through explicit interface implementation. + implementation = TryFindExplicitInterfaceImplementationInType(type, baseMethod); + } + else + { + // Prioritize interface implementations. + if (declaringType.IsInterface) + { + implementation = TryFindExplicitInterfaceImplementationInType(type, baseMethod) + ?? TryFindImplicitInterfaceImplementationInType(type, baseMethod); + } + + // Try to find other implicit implementations. + implementation ??= TryFindImplicitImplementationInType(type, baseMethod); + } + + if (implementation is not null) + break; + + // Move up type hierarchy tree. + type = type.BaseType?.Resolve(); + } + + // If there's no override, just use the base implementation (if available). + if (implementation is null && !baseMethod.IsAbstract) + implementation = baseMethod; + + return implementation; + } + + private static MethodDefinition? TryFindImplicitImplementationInType(TypeDefinition type, MethodDefinition baseMethod) + { + foreach (var method in type.Methods) + { + if (method is { IsVirtual: true, IsReuseSlot: true } + && method.Name == baseMethod.Name + && SignatureComparer.Default.Equals(method.Signature, baseMethod.Signature)) + { + return method; + } + } + + return null; + } + + private static MethodDefinition? TryFindImplicitInterfaceImplementationInType(TypeDefinition type, MethodDefinition baseMethod) + { + // Find the correct interface implementation and instantiate any generics. + MethodSignature? baseMethodSig = null; + foreach (var interfaceImpl in type.Interfaces) + { + if (SignatureComparer.Default.Equals(interfaceImpl.Interface?.ToTypeSignature().GetUnderlyingTypeDefOrRef(), baseMethod.DeclaringType)) + { + baseMethodSig = baseMethod.Signature?.InstantiateGenericTypes(GenericContext.FromType(interfaceImpl.Interface!)); + break; + } + } + if (baseMethodSig is null) + return null; + + // Find implemented method in type. + for (int i = 0; i < type.Methods.Count; i++) + { + var method = type.Methods[i]; + // Only public virtual instance methods can implicity implement interface methods. (ECMA-335, 6th edition, II.12.2) + if (method is { IsPublic: true, IsVirtual: true, IsStatic: false } + && method.Name == baseMethod.Name + && SignatureComparer.Default.Equals(method.Signature, baseMethodSig)) + { + return method; + } + } + + return null; + } + + private static MethodDefinition? TryFindExplicitInterfaceImplementationInType(TypeDefinition type, MethodDefinition baseMethod) + { + for (int i = 0; i < type.MethodImplementations.Count; i++) + { + var impl = type.MethodImplementations[i]; + if (impl.Declaration is null || impl.Declaration.Name != baseMethod.Name) + continue; + + // Compare underlying TypeDefOrRef and instantiate any generics to ensure correct comparison. + var declaringType = impl.Declaration?.DeclaringType?.ToTypeSignature().GetUnderlyingTypeDefOrRef(); + if (!SignatureComparer.Default.Equals(declaringType, baseMethod.DeclaringType)) + continue; + + var context = GenericContext.FromMethod(impl.Declaration!); + var implMethodSig = impl.Declaration!.Signature?.InstantiateGenericTypes(context); + var baseMethodSig = baseMethod.Signature?.InstantiateGenericTypes(context); + if (SignatureComparer.Default.Equals(baseMethodSig, implMethodSig)) + return impl.Body?.Resolve(); + } + + return null; + } } } \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallVirtHandler.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallVirtHandler.cs index 182a4ee2..a9f10671 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallVirtHandler.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/CallVirtHandler.cs @@ -13,145 +13,61 @@ namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel [DispatcherTableEntry(CilCode.Callvirt)] public class CallVirtHandler : CallHandlerBase { - private static readonly SignatureComparer Comparer = new(); - /// - protected override MethodDevirtualizationResult DevirtualizeMethodInternal(CilExecutionContext context, - CilInstruction instruction, + protected override MethodDevirtualizationResult DevirtualizeMethodInternal( + CilExecutionContext context, IMethodDescriptor method, IList arguments) { switch (arguments[0].AsSpan()) { case { IsFullyKnown: false }: + // We cannot dereference unknown pointers. return MethodDevirtualizationResult.Unknown(); case { IsZero.Value: TrileanValue.True }: - return MethodDevirtualizationResult.Exception(context.Machine - .Heap.AllocateObject( - context.Machine.ValueFactory.NullReferenceExceptionType, - true) - .AsObjectHandle(context.Machine)); + // We are trying to dereference a null pointer. + return MethodDevirtualizationResult.Exception( + context.Machine.Heap.AllocateObject( + context.Machine.ValueFactory.NullReferenceExceptionType, + true) + .AsObjectHandle(context.Machine) + ); case var objectPointer: - var objectType = objectPointer.AsObjectHandle(context.Machine).GetObjectType(); - var implementation = FindMethodImplementationInType(objectType.Resolve(), method.Resolve()); + // Determine the type to apply virtual dispatch on. + var objectType = context.CurrentFrame.ConstrainedType + ?? objectPointer.AsObjectHandle(context.Machine).GetObjectType(); + // Find the implementation. + var implementation = FindMethodImplementationInType(objectType.Resolve(), method.Resolve()); if (implementation is null) { - return MethodDevirtualizationResult.Exception(context.Machine - .Heap.AllocateObject( - context.Machine.ValueFactory.MissingMethodExceptionType, - true) - .AsObjectHandle(context.Machine)); + // There is no implementation for the method. + return MethodDevirtualizationResult.Exception( + context.Machine.Heap.AllocateObject( + context.Machine.ValueFactory.MissingMethodExceptionType, + true) + .AsObjectHandle(context.Machine) + ); } - // instantiate generics + // Instantiate any generics. var genericContext = GenericContext.FromMethod(method); if (genericContext.IsEmpty) return MethodDevirtualizationResult.Success(implementation); - - var methodRef = objectType.ToTypeDefOrRef().CreateMemberReference(implementation.Name!, implementation.Signature!); - return MethodDevirtualizationResult.Success(genericContext.Method != null ? methodRef.MakeGenericInstanceMethod(genericContext.Method.TypeArguments.ToArray()) : methodRef); - } - } - - private static MethodDefinition? FindMethodImplementationInType(TypeDefinition? type, MethodDefinition? baseMethod) - { - if (type is null || baseMethod is null || !baseMethod.IsVirtual) - return baseMethod; - - var implementation = default(MethodDefinition); - var declaringType = baseMethod.DeclaringType!; - while (type is not null && !Comparer.Equals(type, declaringType)) - { - // Prioritize interface implementations. - if (declaringType.IsInterface) - implementation = TryFindExplicitInterfaceImplementationInType(type, baseMethod) - ?? TryFindImplicitInterfaceImplementationInType(type, baseMethod); - - // Try to find other implicit implementations. - implementation ??= TryFindImplicitImplementationInType(type, baseMethod); - - if (implementation is not null) - break; - - // Move up type hierarchy tree. - type = type.BaseType?.Resolve(); - } - - // If there's no override, just use the base implementation (if available). - if (implementation is null && !baseMethod.IsAbstract) - implementation = baseMethod; - - return implementation; - } - private static MethodDefinition? TryFindImplicitImplementationInType(TypeDefinition type, MethodDefinition baseMethod) - { - foreach (var method in type.Methods) - { - if (method.IsVirtual - && method.IsReuseSlot - && method.Name == baseMethod.Name - && Comparer.Equals(method.Signature, baseMethod.Signature)) - { - return method; - } - } - - return null; - } - - private static MethodDefinition? TryFindImplicitInterfaceImplementationInType(TypeDefinition type, MethodDefinition baseMethod) - { - // Find the correct interface implementation and instantiate any generics. - MethodSignature? baseMethodSig = null; - foreach (var interfaceImpl in type.Interfaces) - { - if (Comparer.Equals(interfaceImpl.Interface?.ToTypeSignature().GetUnderlyingTypeDefOrRef(), baseMethod.DeclaringType)) - { - baseMethodSig = baseMethod.Signature?.InstantiateGenericTypes(GenericContext.FromType(interfaceImpl.Interface!)); - break; - } - } - if (baseMethodSig is null) - return null; + var instantiated = objectType + .ToTypeDefOrRef() + .CreateMemberReference(implementation.Name!, implementation.Signature!); - // Find implemented method in type. - for (int i = 0; i < type.Methods.Count; i++) - { - var method = type.Methods[i]; - // Only public virtual instance methods can implicity implement interface methods. (ECMA-335, 6th edition, II.12.2) - if (method.IsPublic - && method.IsVirtual - && !method.IsStatic - && method.Name == baseMethod.Name - && Comparer.Equals(method.Signature, baseMethodSig)) - return method; + return MethodDevirtualizationResult.Success(genericContext.Method != null + ? instantiated.MakeGenericInstanceMethod(genericContext.Method.TypeArguments.ToArray()) + : instantiated + ); } - - return null; } - private static MethodDefinition? TryFindExplicitInterfaceImplementationInType(TypeDefinition type, MethodDefinition baseMethod) - { - for (int i = 0; i < type.MethodImplementations.Count; i++) - { - var impl = type.MethodImplementations[i]; - - // Compare underlying TypeDefOrRef and instantiate any generics to ensure correct comparison. - if (!Comparer.Equals(impl.Declaration?.DeclaringType?.ToTypeSignature().GetUnderlyingTypeDefOrRef(), baseMethod.DeclaringType)) - continue; - - var context = GenericContext.FromMethod(impl.Declaration!); - var implMethodSig = impl.Declaration?.Signature?.InstantiateGenericTypes(context); - var baseMethodSig = baseMethod.Signature?.InstantiateGenericTypes(context); - if (Comparer.Equals(baseMethodSig, implMethodSig)) - return impl.Body?.Resolve(); - } - - return null; - } + } } \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/ConstrainedHandler.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/ConstrainedHandler.cs new file mode 100644 index 00000000..dde37dac --- /dev/null +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/ConstrainedHandler.cs @@ -0,0 +1,23 @@ +using AsmResolver.DotNet; +using AsmResolver.DotNet.Signatures; +using AsmResolver.PE.DotNet.Cil; + +namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel; + +/// +/// Implements a CIL instruction handler for constrained operations. +/// +[DispatcherTableEntry(CilCode.Constrained)] +public class ConstrainedHandler : FallThroughOpCodeHandler +{ + /// + protected override CilDispatchResult DispatchInternal(CilExecutionContext context, CilInstruction instruction) + { + var genericContext = GenericContext.FromMethod(context.CurrentFrame.Method); + context.CurrentFrame.ConstrainedType = ((ITypeDescriptor)instruction.Operand!) + .ToTypeSignature() + .InstantiateGenericTypes(genericContext); + + return CilDispatchResult.Success(); + } +} \ No newline at end of file diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/NewObjHandler.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/NewObjHandler.cs index 1f5dce0e..21229296 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/NewObjHandler.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/NewObjHandler.cs @@ -67,7 +67,6 @@ public override CilDispatchResult Dispatch(CilExecutionContext context, CilInstr /// protected override MethodDevirtualizationResult DevirtualizeMethodInternal(CilExecutionContext context, - CilInstruction instruction, IMethodDescriptor method, IList arguments) { diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Stack/CallFrame.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Stack/CallFrame.cs index 5e6ee22c..2f569a9b 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Stack/CallFrame.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Stack/CallFrame.cs @@ -70,9 +70,9 @@ internal CallFrame(IMethodDescriptor method, ValueFactory factory, bool isRoot) _initializeLocals = body.InitializeLocals; LocalsCount = body.LocalVariables.Count; - foreach (var local in body.LocalVariables) - AllocateFrameField(local.VariableType); - + for (int i = 0; i < body.LocalVariables.Count; i++) + AllocateFrameField(body.LocalVariables[i].VariableType); + Body = body; } @@ -101,7 +101,8 @@ internal CallFrame(IMethodDescriptor method, ValueFactory factory, bool isRoot) } InitializeExceptionHandlerFrames(); - + return; + void AllocateFrameField(TypeSignature type) { _offsets.Add(currentOffset); @@ -114,51 +115,32 @@ void AllocateFrameField(TypeSignature type) /// /// Gets a value indicating the frame is the root frame of the call stack. /// - public bool IsRoot - { - get; - } + public bool IsRoot { get; } /// /// Gets the method which this frame was associated with. /// - public IMethodDescriptor Method - { - get; - } + public IMethodDescriptor Method { get; } /// /// Gets the managed body of the method that this frame is associated with (if available). /// - public CilMethodBody? Body - { - get; - } + public CilMethodBody? Body { get; } /// /// Gets the number of locals stored in the frame. /// - public int LocalsCount - { - get; - } + public int LocalsCount { get; } /// /// Gets the offset within the method body of the next instruction to evaluate. /// - public int ProgramCounter - { - get; - set; - } + public int ProgramCounter { get; set; } /// /// Gets a virtual evaluation stack associated stored the frame. /// - public EvaluationStack EvaluationStack - { - get; - } + public EvaluationStack EvaluationStack { get; } /// /// Gets a collection of exception handler frames present in the method body. @@ -168,10 +150,7 @@ public EvaluationStack EvaluationStack /// /// Gets the stack of currently active exception handler frames in the method. /// - public ExceptionHandlerStack ExceptionHandlerStack - { - get; - } = new(); + public ExceptionHandlerStack ExceptionHandlerStack { get; } = new(); /// /// Gets the number of bytes (excluding the evaluation stack) the stack frame spans. @@ -181,12 +160,13 @@ public ExceptionHandlerStack ExceptionHandlerStack /// /// Gets a value indicating whether the frame can be extended with extra stack memory. /// - public bool CanAllocateMemory - { - get; - internal set; - } + public bool CanAllocateMemory { get; internal set; } + /// + /// Gets or sets the current type that the following call instruction is constrained by, if any. + /// + public ITypeDescriptor? ConstrainedType { get; set; } + /// public AddressRange AddressRange => new(_baseAddress, _baseAddress + _localStorage.ByteCount); diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/CilVirtualMachineTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/CilVirtualMachineTest.cs index 496189d5..41178aaa 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/CilVirtualMachineTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/CilVirtualMachineTest.cs @@ -6,6 +6,7 @@ using AsmResolver.DotNet; using AsmResolver.DotNet.Code.Cil; using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; using AsmResolver.PE.DotNet.Cil; using Echo.Memory; using Echo.Platforms.AsmResolver.Emulation; @@ -623,5 +624,43 @@ public void StaticFieldWithInitialValueUpdate() _mainThread.Call(increment, Array.Empty()); Assert.Equal(1337 + 3, _vm.StaticFields.GetFieldSpan(counter).I32); } + + [Fact] + public void StepPrefixedInstructionsShouldStepOverAllInstructions() + { + var factory = _fixture.MockModule.CorLibTypeFactory; + var toString = factory.Object.Type + .CreateMemberReference("ToString", MethodSignature.CreateInstance(factory.String)); + + var dummyMethod = new MethodDefinition( + "DummyMethod", + MethodAttributes.Static, + MethodSignature.CreateStatic(factory.String, 1, + new GenericParameterSignature(GenericParameterType.Method, 0)) + ); + dummyMethod.GenericParameters.Add(new GenericParameter("T")); + dummyMethod.CilMethodBody = new CilMethodBody(dummyMethod) + { + Instructions = + { + {Ldarga_S, dummyMethod.Parameters[0]}, + {Constrained, new GenericParameterSignature(GenericParameterType.Method, 0).ToTypeDefOrRef()}, + {Callvirt, toString}, + Ret + } + }; + dummyMethod.CilMethodBody.Instructions.CalculateOffsets(); + + var frame = _mainThread.CallStack.Push(dummyMethod.MakeGenericInstanceMethod(factory.Int32)); + frame.WriteArgument(0, new BitVector(1337)); + + var instructions = dummyMethod.CilMethodBody.Instructions; + + Assert.Equal(instructions[0].Offset, frame.ProgramCounter); + _mainThread.Step(); + Assert.Equal(instructions[1].Offset, frame.ProgramCounter); + _mainThread.Step(); + Assert.Equal(instructions[3].Offset, frame.ProgramCounter); + } } } \ No newline at end of file diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CallHandlerTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CallHandlerTest.cs index 3a90a966..c56a9866 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CallHandlerTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CallHandlerTest.cs @@ -313,5 +313,52 @@ public void CallStepInWithThrowingInitializer() var exception = Assert.Throws(() => Context.Thread.StepOut()); Assert.Equal(nameof(TypeInitializationException), exception.ExceptionObject.GetObjectType().Name); } + + [Fact] + public void ConstrainedCallAbstractStatic() + { + var factory = ModuleFixture.MockModule.CorLibTypeFactory; + + // Set up dummy metadata. + var module = new ModuleDefinition("Dummy"); + + // static abstract IFoo::Method() + var interfaceType = new TypeDefinition(null, "IFoo", TypeAttributes.Interface); + var interfaceMethod = new MethodDefinition( + "Method", + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Abstract + | MethodAttributes.Virtual | MethodAttributes.Static, + MethodSignature.CreateStatic(factory.Void) + ); + interfaceType.Methods.Add(interfaceMethod); + module.TopLevelTypes.Add(interfaceType); + + // static Foo::Method() that implements IFoo::Method() + var classType = new TypeDefinition(null, "Foo", TypeAttributes.Interface); + classType.Interfaces.Add(new InterfaceImplementation(interfaceType)); + var classMethod = new MethodDefinition( + "Method", + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, + MethodSignature.CreateStatic(factory.Void) + ); + classType.MethodImplementations.Add(new MethodImplementation(interfaceMethod, classMethod)); + classType.Methods.Add(classMethod); + module.TopLevelTypes.Add(classType); + + // Allocate a Foo object. + long objectPointer = Context.Machine.Heap.AllocateObject(classType, true); + Context.CurrentFrame.EvaluationStack.Push(new StackSlot( + Context.Machine.ValueFactory.CreateNativeInteger(objectPointer), + StackSlotTypeHint.Integer)); + + // Execute a constrained callvirt. + Context.Machine.Invoker = DefaultInvokers.StepIn; + Context.CurrentFrame.ConstrainedType = classType; + var result = Dispatcher.Dispatch(Context, new CilInstruction(CilOpCodes.Call, interfaceMethod)); + + // Verify we entered Foo::Method() + Assert.Equal(CilDispatchResult.Success(), result); + Assert.Equal(classMethod, Context.CurrentFrame.Method, SignatureComparer.Default); + } } } \ No newline at end of file diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CallVirtHandlerTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CallVirtHandlerTest.cs index c4bfaf53..383c1986 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CallVirtHandlerTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/Emulation/Dispatch/ObjectModel/CallVirtHandlerTest.cs @@ -1,7 +1,11 @@ using System.Collections.Generic; using System.Linq; using AsmResolver.DotNet; +using AsmResolver.DotNet.Code.Cil; +using AsmResolver.DotNet.Signatures; +using AsmResolver.DotNet.Signatures.Types; using AsmResolver.PE.DotNet.Cil; +using AsmResolver.PE.DotNet.Metadata.Tables.Rows; using Echo.Memory; using Echo.Platforms.AsmResolver.Emulation; using Echo.Platforms.AsmResolver.Emulation.Dispatch; @@ -38,7 +42,7 @@ public void CallVirtInstanceShouldStepInOriginalMethodIfNoOverride() var result = Dispatcher.Dispatch(Context, new CilInstruction(CilOpCodes.Callvirt, method)); // Verify that we jumped into the base method implementation. - Assert.True(result.IsSuccess); + Assert.Equal(CilDispatchResult.Success(), result); Assert.Same(method, Context.CurrentFrame.Method); } @@ -63,7 +67,7 @@ public void CallVirtInstanceShouldStepInOverrideMethodIfAvailable() var result = Dispatcher.Dispatch(Context, new CilInstruction(CilOpCodes.Callvirt, baseMethod)); // Verify that we jumped into the overridden method. - Assert.True(result.IsSuccess); + Assert.Equal(CilDispatchResult.Success(), result); Assert.Same(overriddenMethod, Context.CurrentFrame.Method); } @@ -105,10 +109,32 @@ public void CallVirtOnUnknownShouldDispatchToUnknownResolver() // Execute a callvirt. var result = Dispatcher.Dispatch(Context, new CilInstruction(CilOpCodes.Callvirt, baseMethod)); - Assert.True(result.IsSuccess); + Assert.Equal(CilDispatchResult.Success(), result); Assert.Equal(resolver.LastResolveMethodAttempt, baseMethod); } - + + [Fact] + public void ConstrainedCallVirt() + { + // Lookup metadata. + var factory = ModuleFixture.MockModule.CorLibTypeFactory; + var objectToString = factory.Object.Type + .CreateMemberReference("ToString", MethodSignature.CreateInstance(factory.String)); + var int32ToString = factory.Int32.Type + .CreateMemberReference("ToString", MethodSignature.CreateInstance(factory.String)); + + // Set up stack and constrained type. + Context.CurrentFrame.ConstrainedType = factory.Int32; + Context.CurrentFrame.EvaluationStack.Push(new StackSlot(1337, StackSlotTypeHint.Integer)); + + // Execute a callvirt. + var result = Dispatcher.Dispatch(Context, new CilInstruction(CilOpCodes.Callvirt, objectToString)); + + // Verify we entered Int32::ToString + Assert.Equal(CilDispatchResult.Success(), result); + Assert.Equal(int32ToString, Context.CurrentFrame.Method, SignatureComparer.Default); + } + private sealed class MyUnknownResolver : ThrowUnknownResolver { public IMethodDescriptor? LastResolveMethodAttempt; diff --git a/test/Platforms/Mocks/Mocks.csproj b/test/Platforms/Mocks/Mocks.csproj index e4350165..18886d40 100644 --- a/test/Platforms/Mocks/Mocks.csproj +++ b/test/Platforms/Mocks/Mocks.csproj @@ -1,7 +1,9 @@ - netstandard2.1 + net6.0 + 12 + true @@ -9,4 +11,4 @@ - + \ No newline at end of file diff --git a/test/Platforms/Mocks/SimpleClass.cs b/test/Platforms/Mocks/SimpleClass.cs index 30a13d70..6ddf7a2e 100644 --- a/test/Platforms/Mocks/SimpleClass.cs +++ b/test/Platforms/Mocks/SimpleClass.cs @@ -138,5 +138,7 @@ public static T GenericMethod(T value) return ret; } + + public static string GenericToString(T value) => value.ToString(); } } \ No newline at end of file