Skip to content

Commit

Permalink
Add ldvirtftn
Browse files Browse the repository at this point in the history
  • Loading branch information
BadRyuner committed Jun 5, 2024
1 parent 687c1fe commit cb6db0e
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Linq;
using AsmResolver.DotNet;
using AsmResolver.DotNet.Signatures;
using AsmResolver.PE.DotNet.Cil;

namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel;

/// <summary>
/// Implements a CIL instruction handler for <c>ldvirtftn</c> instruction.
/// </summary>
[DispatcherTableEntry(CilCode.Ldvirtftn)]
public class LdvirtftnHandler : FallThroughOpCodeHandler
{
/// <inheritdoc/>
protected override CilDispatchResult DispatchInternal(CilExecutionContext context, CilInstruction instruction)
{
var stack = context.CurrentFrame.EvaluationStack;
var factory = context.Machine.ValueFactory;
var methods = context.Machine.ValueFactory.ClrMockMemory.MethodEntryPoints;
var type = context.Machine.ContextModule.CorLibTypeFactory.IntPtr;

var thisObject = stack.Pop().Contents;
if (!thisObject.IsFullyKnown)
throw new CilEmulatorException("Unable to resolve an unknown object.");

var thisObjectType = thisObject.AsObjectHandle(context.Machine).GetObjectType().Resolve();
if (thisObjectType == null)
throw new CilEmulatorException("Unable to resolve the type of object");

var virtualFunction = ((IMethodDescriptor)instruction.Operand!);
var virtualFunctionName = virtualFunction.Name;
var virtualFunctionSignature = virtualFunction.Signature;

do
{
// try resolve function
var resolvedVirtualFunction = thisObjectType.Methods
.FirstOrDefault(method => method.Name == virtualFunctionName
&& SignatureComparer.Default.Equals(method.Signature, virtualFunctionSignature));

// if resolved then push function pointer
if (resolvedVirtualFunction != null)
{
var functionPointer = methods.GetAddress(resolvedVirtualFunction);
stack.Push(factory.CreateNativeInteger(functionPointer), type);
return CilDispatchResult.Success();
}

// else switch to BaseType and try resolve again
thisObjectType = thisObjectType.BaseType?.Resolve();
} // or exit and throw CilEmulationException
while (thisObjectType != null);

throw new CilEmulatorException($"Unable to resolve a virtual function for type {thisObjectType!.FullName}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Reflection;
using System.Text;
using System.Threading;
using AsmResolver;
using AsmResolver.DotNet;
using AsmResolver.DotNet.Code.Cil;
using AsmResolver.DotNet.Signatures;
Expand Down Expand Up @@ -710,5 +711,41 @@ public void CallDelegate()
Assert.Single(_mainThread.CallStack.Peek().EvaluationStack);
Assert.Equal(5, _mainThread.CallStack.Peek().EvaluationStack.Peek().Contents.AsSpan().I32);
}

[Fact]
public void CallDelegateWithVirtualFunction()
{
var method = _fixture.MockModule
.LookupMember<TypeDefinition>(typeof(TestClass).MetadataToken)
.Methods.First(m => m.Name == nameof(TestClass.TestVirtualDelegateCall));

_vm.Invoker = DefaultInvokers.CreateDefaultShims().WithFallback(DefaultInvokers.StepIn).WithFallback(DefaultInvokers.ReflectionInvoke);
_mainThread.CallStack.Push(method);

var instructions = method.CilMethodBody!.Instructions;

var invokeDelegateOffset = instructions
.First(instruction => instruction.Operand is IMethodDescriptor descriptor
&& descriptor.Name == "Invoke")
.Offset;

var expectedResults = new string[] { "Mr.String", "Mocks.TestClass" };

for (int i = 0; i < expectedResults.Length; i++)
{
_mainThread.StepWhile(CancellationToken.None, context => context.CurrentFrame.ProgramCounter != invokeDelegateOffset);
_mainThread.Step(); // invoke: string Func<object>::Invoke()
// Expected stack:
// (0) root -> (1) TestVirtualDelegateCall -> (2) Func<object>::Invoke -> (3) *::ToString
Assert.Equal(4, _mainThread.CallStack.Count);

// exit from *::ToString
while (_mainThread.CallStack.Count != 2)
_mainThread.StepOut();

var returnedString = _mainThread.CallStack.Peek().EvaluationStack.Peek();
Assert.Equal(expectedResults[i], _vm.ObjectMarshaller.ToObject<string>(returnedString.Contents.AsSpan()));
}
}
}
}
16 changes: 16 additions & 0 deletions test/Platforms/Mocks/TestClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,5 +295,21 @@ public static int TestDelegateCall()
ReturnAnyIntDelegate del = ReturnAnyInt;
return del();
}

public static void TestVirtualDelegateCall()
{
var objects = new object[] { "Mr.String", new TestClass() };
for(int i = 0; i < objects.Length; i++)
{
var @object = objects[i];
var function = new Func<string>(@object.ToString);
_ = function();
}
}

public override string ToString()
{
return "Mocks.TestClass";
}
}
}

0 comments on commit cb6db0e

Please sign in to comment.