diff --git a/src/Core/Echo/Memory/BitVectorSpan.cs b/src/Core/Echo/Memory/BitVectorSpan.cs
index dab97aef..27349d06 100644
--- a/src/Core/Echo/Memory/BitVectorSpan.cs
+++ b/src/Core/Echo/Memory/BitVectorSpan.cs
@@ -362,7 +362,20 @@ public string ToHexString()
///
/// Copies the span into a new bit vector.
///
+ /// The vector.
public BitVector ToVector() => new(this);
+
+ ///
+ /// Copies the span into a new bit vector that is rented from the provided pool.
+ ///
+ /// The pool to rent the vector from.
+ /// The vector.
+ public BitVector ToVector(BitVectorPool pool)
+ {
+ var result = pool.Rent(Count, false);
+ CopyTo(result);
+ return result;
+ }
private void AssertSameBitSize(BitVectorSpan other)
{
diff --git a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs
index 68d53019..c0ad0fc7 100644
--- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs
+++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ControlFlow/RetHandler.cs
@@ -1,6 +1,8 @@
using AsmResolver.DotNet.Signatures.Types;
using AsmResolver.DotNet.Signatures;
using AsmResolver.PE.DotNet.Cil;
+using Echo.Memory;
+using Echo.Platforms.AsmResolver.Emulation.Stack;
namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ControlFlow
{
@@ -13,17 +15,69 @@ public class RetHandler : ICilOpCodeHandler
///
public CilDispatchResult Dispatch(CilExecutionContext context, CilInstruction instruction)
{
- var frame = context.Thread.CallStack.Pop();
- var genericContext = GenericContext.FromMethod(frame.Method);
- if (frame.Method.Signature!.ReturnsValue)
+ var calleeFrame = context.Thread.CallStack.Pop();
+ var callerFrame = context.CurrentFrame;
+
+ var genericContext = GenericContext.FromMethod(calleeFrame.Method);
+ if (calleeFrame.Method.Signature!.ReturnsValue)
+ {
+ // The method returns the value on top of the stack. Push it in the caller frame.
+ var returnType = calleeFrame.Method.Signature.ReturnType.InstantiateGenericTypes(genericContext);
+ var value = calleeFrame.EvaluationStack.Pop(returnType);
+ callerFrame.EvaluationStack.Push(value, returnType, true);
+ }
+ else if (callerFrame.Body is { } body)
{
- var returnType = frame.Method.Signature.ReturnType.InstantiateGenericTypes(genericContext);
+ // The method may still be a constructor called via newobj.
+ // In that case we need to push the created value, stored in the `this` pointer.
+
+ int index = body.Instructions.GetIndexByOffset(callerFrame.ProgramCounter) - 1;
+ if (index != -1 && body.Instructions[index].OpCode.Code == CilCode.Newobj)
+ {
+ var resultingType = calleeFrame.Method.DeclaringType!
+ .ToTypeSignature()
+ .InstantiateGenericTypes(genericContext);
- var value = frame.EvaluationStack.Pop(returnType);
- context.CurrentFrame.EvaluationStack.Push(value, returnType, true);
+ var slot = CreateResultingStackSlot(
+ context,
+ resultingType,
+ calleeFrame.ReadArgument(0)
+ );
+
+ callerFrame.EvaluationStack.Push(slot);
+ }
}
return CilDispatchResult.Success();
}
+
+ internal static StackSlot CreateResultingStackSlot(
+ CilExecutionContext context,
+ TypeSignature type,
+ BitVectorSpan thisPointer)
+ {
+ // For reference types, we can just wrap the address into a stack slot.
+ if (!type.IsValueType)
+ {
+ return new StackSlot(
+ thisPointer.ToVector(context.Machine.ValueFactory.BitVectorPool),
+ StackSlotTypeHint.Integer
+ );
+ }
+
+ // For value types, we need to push the resulting structure in its entirety.
+ var contents = context.Machine.ValueFactory.CreateValue(type, false);
+
+ // Read the entire structure behind the this-pointer.
+ if (thisPointer.IsFullyKnown)
+ {
+ context.Machine.Memory.Read(
+ thisPointer.ReadNativeInteger(context.Machine.Is32Bit),
+ contents
+ );
+ }
+
+ return new StackSlot(contents, StackSlotTypeHint.Structure);
+ }
}
}
\ 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 21229296..ac31b489 100644
--- a/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/NewObjHandler.cs
+++ b/src/Platforms/Echo.Platforms.AsmResolver/Emulation/Dispatch/ObjectModel/NewObjHandler.cs
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using AsmResolver.DotNet;
+using AsmResolver.DotNet.Signatures.Types;
using AsmResolver.PE.DotNet.Cil;
using Echo.Memory;
+using Echo.Platforms.AsmResolver.Emulation.Dispatch.ControlFlow;
using Echo.Platforms.AsmResolver.Emulation.Invocation;
namespace Echo.Platforms.AsmResolver.Emulation.Dispatch.ObjectModel
@@ -16,44 +18,15 @@ public class NewObjHandler : CallHandlerBase
///
public override CilDispatchResult Dispatch(CilExecutionContext context, CilInstruction instruction)
{
- var stack = context.CurrentFrame.EvaluationStack;
-
- // Allocate the new object.
- var constructor = (IMethodDescriptor) instruction.Operand!;
+ var constructor = (IMethodDescriptor)instruction.Operand!;
var instanceType = constructor.DeclaringType!.ToTypeSignature();
var arguments = GetArguments(context, constructor);
try
{
- var allocation = context.Machine.Allocator.Allocate(context, constructor, arguments);
- switch (allocation.ResultType)
- {
- case AllocationResultType.Inconclusive:
- throw new CilEmulatorException($"Allocation of object of type {instanceType} was inconclusive");
-
- case AllocationResultType.Allocated:
- // Insert the allocated "this" pointer into the arguments and call constructor.
- arguments.Insert(0, allocation.Address!);
- var result = HandleCall(context, instruction, constructor, arguments);
-
- // If successful, push the resulting object onto the stack.
- if (result.IsSuccess)
- stack.Push(allocation.Address!, instanceType);
-
- return result;
-
- case AllocationResultType.FullyConstructed:
- // Fully constructed objects do not have to be post-processed.
- stack.Push(allocation.Address!, instanceType);
- context.CurrentFrame.ProgramCounter += instruction.Size;
- return CilDispatchResult.Success();
-
- case AllocationResultType.Exception:
- return CilDispatchResult.Exception(allocation.ExceptionObject);
-
- default:
- throw new ArgumentOutOfRangeException();
- }
+ return instanceType.IsValueType
+ ? HandleValueTypeNewObj(context, instruction, constructor, instanceType, arguments)
+ : HandleReferenceTypeNewObj(context, instruction, constructor, instanceType, arguments);
}
finally
{
@@ -62,6 +35,75 @@ public override CilDispatchResult Dispatch(CilExecutionContext context, CilInstr
}
}
+ private CilDispatchResult HandleValueTypeNewObj(
+ CilExecutionContext context,
+ CilInstruction instruction,
+ IMethodDescriptor constructor,
+ TypeSignature instanceType,
+ IList arguments)
+ {
+ var factory = context.Machine.ValueFactory;
+ var callerFrame = context.CurrentFrame;
+ var callerStack = callerFrame.EvaluationStack;
+
+ // Stack allocate the structure.
+ long address = context.CurrentFrame.Allocate((int)factory.GetTypeContentsMemoryLayout(instanceType).Size);
+ var thisPointer = factory.CreateNativeInteger(address);
+
+ // Call the constructor with the constructor.
+ arguments.Insert(0, thisPointer);
+ var result = HandleCall(context, instruction, constructor, arguments);
+
+ // If we stepped over the call, we need to push the stack value ourselves.
+ if (result.IsSuccess && callerFrame == context.CurrentFrame)
+ callerStack.Push(RetHandler.CreateResultingStackSlot(context, instanceType, thisPointer));
+
+ return result;
+ }
+
+ private CilDispatchResult HandleReferenceTypeNewObj(
+ CilExecutionContext context,
+ CilInstruction instruction,
+ IMethodDescriptor constructor,
+ TypeSignature instanceType,
+ IList arguments)
+ {
+ var callerFrame = context.CurrentFrame;
+ var callerStack = callerFrame.EvaluationStack;
+
+ var allocation = context.Machine.Allocator.Allocate(context, constructor, arguments);
+ switch (allocation.ResultType)
+ {
+ case AllocationResultType.Inconclusive:
+ throw new CilEmulatorException(
+ $"Allocation of object of type {instanceType} was inconclusive");
+
+ case AllocationResultType.Allocated:
+ // Insert the allocated "this" pointer into the arguments and call constructor.
+ arguments.Insert(0, allocation.Address!);
+ var result = HandleCall(context, instruction, constructor, arguments);
+
+ // If we stepped over the call, we need to push the stack value ourselves.
+ if (result.IsSuccess && callerFrame == context.CurrentFrame)
+ callerStack.Push(allocation.Address!, instanceType);
+
+ return result;
+
+ case AllocationResultType.FullyConstructed:
+ // Fully constructed objects do not have to be post-processed.
+ callerStack.Push(allocation.Address!, instanceType);
+ callerFrame.ProgramCounter += instruction.Size;
+ return CilDispatchResult.Success();
+
+ case AllocationResultType.Exception:
+ return CilDispatchResult.Exception(allocation.ExceptionObject);
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
+ }
+
///
protected override bool ShouldPopInstanceObject(IMethodDescriptor method) => false;