Skip to content

Commit

Permalink
BUGFIX: NewObj on structure should return the entire structure as opp…
Browse files Browse the repository at this point in the history
…osed to a pointer.
  • Loading branch information
Washi1337 committed Apr 1, 2024
1 parent 694ee61 commit d24c5a7
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 39 deletions.
13 changes: 13 additions & 0 deletions src/Core/Echo/Memory/BitVectorSpan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,20 @@ public string ToHexString()
/// <summary>
/// Copies the span into a new bit vector.
/// </summary>
/// <returns>The vector.</returns>
public BitVector ToVector() => new(this);

/// <summary>
/// Copies the span into a new bit vector that is rented from the provided pool.
/// </summary>
/// <param name="pool">The pool to rent the vector from.</param>
/// <returns>The vector.</returns>
public BitVector ToVector(BitVectorPool pool)
{
var result = pool.Rent(Count, false);
CopyTo(result);
return result;
}

private void AssertSameBitSize(BitVectorSpan other)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -13,17 +15,69 @@ public class RetHandler : ICilOpCodeHandler
/// <inheritdoc />
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,44 +18,15 @@ public class NewObjHandler : CallHandlerBase
/// <inheritdoc />
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
{
Expand All @@ -62,6 +35,75 @@ public override CilDispatchResult Dispatch(CilExecutionContext context, CilInstr
}
}

private CilDispatchResult HandleValueTypeNewObj(
CilExecutionContext context,
CilInstruction instruction,
IMethodDescriptor constructor,
TypeSignature instanceType,
IList<BitVector> 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<BitVector> 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();
}

}

/// <inheritdoc />
protected override bool ShouldPopInstanceObject(IMethodDescriptor method) => false;

Expand Down

0 comments on commit d24c5a7

Please sign in to comment.