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