Fix argument ordering bug (#58)

This commit is contained in:
Patrick Stevens
2025-06-20 12:40:45 +01:00
committed by GitHub
parent 71b12b5684
commit 5a7cf11a5f
11 changed files with 221 additions and 47 deletions

View File

@@ -244,6 +244,7 @@ type DumpedAssembly =
interface IDisposable with
member this.Dispose () = this.PeReader.Dispose ()
type TypeResolutionResult =
| FirstLoadAssy of WoofWare.PawPrint.AssemblyReference
| Resolved of DumpedAssembly * TypeInfo<TypeDefn>
@@ -516,3 +517,46 @@ module Assembly =
match assemblies.TryGetValue assy.Name.FullName with
| false, _ -> TypeResolutionResult.FirstLoadAssy assy
| true, toAssy -> resolveTypeFromName toAssy assemblies ty.Namespace ty.Name genericArgs
[<RequireQualifiedAccess>]
module DumpedAssembly =
let resolveBaseType
(bct : BaseClassTypes<DumpedAssembly>)
(loadedAssemblies : ImmutableDictionary<string, DumpedAssembly>)
(source : AssemblyName)
(baseTypeInfo : BaseTypeInfo option)
: ResolvedBaseType
=
let rec go (baseType : BaseTypeInfo option) =
match baseType with
| Some (BaseTypeInfo.TypeRef r) ->
let assy = loadedAssemblies.[source.FullName]
// TODO: generics
match Assembly.resolveTypeRef loadedAssemblies assy assy.TypeRefs.[r] None with
| TypeResolutionResult.FirstLoadAssy _ ->
failwith
"seems pretty unlikely that we could have constructed this object without loading its base type"
| TypeResolutionResult.Resolved (assy, typeInfo) ->
match TypeInfo.isBaseType bct _.Name assy.Name typeInfo.TypeDefHandle with
| Some v -> v
| None -> go typeInfo.BaseType
| Some (BaseTypeInfo.ForeignAssemblyType (assy, ty)) ->
let assy = loadedAssemblies.[assy.FullName]
match TypeInfo.isBaseType bct _.Name assy.Name ty with
| Some v -> v
| None ->
let ty = assy.TypeDefs.[ty]
go ty.BaseType
| Some (BaseTypeInfo.TypeSpec _) -> failwith "TODO"
| Some (BaseTypeInfo.TypeDef h) ->
let assy = loadedAssemblies.[source.FullName]
match TypeInfo.isBaseType bct _.Name assy.Name h with
| Some v -> v
| None ->
let ty = assy.TypeDefs.[h]
go ty.BaseType
| None -> ResolvedBaseType.Object
go baseTypeInfo

View File

@@ -132,6 +132,7 @@ type NullaryIlOp =
| Ldind_i
| Ldind_i1
| Ldind_i2
/// Loads a value of type int32 as an int32 onto the evaluation stack indirectly from the specified address.
| Ldind_i4
| Ldind_i8
| Ldind_u1

View File

@@ -90,6 +90,8 @@ type TypeInfoEval<'ret> =
type TypeInfoCrate =
abstract Apply<'ret> : TypeInfoEval<'ret> -> 'ret
abstract ToString : unit -> string
abstract BaseType : BaseTypeInfo option
abstract Assembly : AssemblyName
[<RequireQualifiedAccess>]
module TypeInfoCrate =
@@ -102,6 +104,10 @@ module TypeInfoCrate =
member _.Eval this = string<TypeInfo<_>> this
}
|> this.Apply
member this.BaseType = t.BaseType
member this.Assembly = t.Assembly
}
type BaseClassTypes<'corelib> =
@@ -242,10 +248,32 @@ module TypeInfo =
Events = events
}
let isBaseType<'corelib>
(baseClassTypes : BaseClassTypes<'corelib>)
(getName : 'corelib -> AssemblyName)
(typeAssy : AssemblyName)
(typeDefinitionHandle : TypeDefinitionHandle)
: ResolvedBaseType option
=
if typeAssy = getName baseClassTypes.Corelib then
if typeDefinitionHandle = baseClassTypes.Enum.TypeDefHandle then
Some ResolvedBaseType.Enum
elif typeDefinitionHandle = baseClassTypes.ValueType.TypeDefHandle then
Some ResolvedBaseType.ValueType
elif typeDefinitionHandle = baseClassTypes.DelegateType.TypeDefHandle then
Some ResolvedBaseType.Delegate
elif typeDefinitionHandle = baseClassTypes.Object.TypeDefHandle then
Some ResolvedBaseType.Object
else
None
else
None
let rec resolveBaseType<'corelib, 'generic>
(baseClassTypes : BaseClassTypes<'corelib>)
(getName : 'corelib -> AssemblyName)
(getType : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic>)
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic>)
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic>)
(sourceAssembly : AssemblyName)
(value : BaseTypeInfo option)
: ResolvedBaseType
@@ -256,37 +284,34 @@ module TypeInfo =
match value with
| BaseTypeInfo.TypeDef typeDefinitionHandle ->
if sourceAssembly = getName baseClassTypes.Corelib then
if typeDefinitionHandle = baseClassTypes.Enum.TypeDefHandle then
ResolvedBaseType.Enum
elif typeDefinitionHandle = baseClassTypes.ValueType.TypeDefHandle then
ResolvedBaseType.ValueType
elif typeDefinitionHandle = baseClassTypes.DelegateType.TypeDefHandle then
ResolvedBaseType.Delegate
else
let baseType = getType baseClassTypes.Corelib typeDefinitionHandle
resolveBaseType baseClassTypes getName getType sourceAssembly baseType.BaseType
else
failwith "unexpected base type not in corelib"
| BaseTypeInfo.TypeRef typeReferenceHandle -> failwith "todo"
match isBaseType baseClassTypes getName sourceAssembly typeDefinitionHandle with
| Some x -> x
| None ->
let baseType = getTypeDef baseClassTypes.Corelib typeDefinitionHandle
resolveBaseType baseClassTypes getName getTypeDef getTypeRef sourceAssembly baseType.BaseType
| BaseTypeInfo.TypeRef typeReferenceHandle ->
let typeRef = getTypeRef baseClassTypes.Corelib typeReferenceHandle
failwith $"{typeRef}"
| BaseTypeInfo.TypeSpec typeSpecificationHandle -> failwith "todo"
| BaseTypeInfo.ForeignAssemblyType (assemblyName, typeDefinitionHandle) ->
resolveBaseType
baseClassTypes
getName
getType
getTypeDef
getTypeRef
assemblyName
(Some (BaseTypeInfo.TypeDef typeDefinitionHandle))
let toTypeDefn
(corelib : BaseClassTypes<'corelib>)
(getName : 'corelib -> AssemblyName)
(getType : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic>)
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic>)
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic>)
(ty : TypeInfo<'generic>)
: TypeDefn
=
let stk =
match resolveBaseType corelib getName getType ty.Assembly ty.BaseType with
match resolveBaseType corelib getName getTypeDef getTypeRef ty.Assembly ty.BaseType with
| ResolvedBaseType.Enum
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
| ResolvedBaseType.Object -> SignatureTypeKind.Class

View File

@@ -10,6 +10,7 @@ open WoofWare.PawPrint.ExternImplementations
open WoofWare.PawPrint.Test
[<TestFixture>]
[<Parallelizable(ParallelScope.All)>]
module TestCases =
let assy = typeof<RunResult>.Assembly
@@ -72,6 +73,20 @@ module TestCases =
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 1) ]
}
{
FileName = "ArgumentOrdering.cs"
ExpectedReturnCode = 42
NativeImpls = MockEnv.make ()
LocalVariablesOfMain =
[
// localVar
CliType.Numeric (CliNumericType.Int32 42)
// t
CliType.ValueType [ CliType.Numeric (CliNumericType.Int32 42) ]
// return value
CliType.Numeric (CliNumericType.Int32 42)
]
}
{
FileName = "BasicLock.cs"
ExpectedReturnCode = 1

View File

@@ -27,6 +27,7 @@
<EmbeddedResource Include="sources\InstaQuit.cs" />
<EmbeddedResource Include="sources\Threads.cs" />
<EmbeddedResource Include="sources\ResizeArray.cs" />
<EmbeddedResource Include="sources\ArgumentOrdering.cs" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,19 @@
public class Program
{
public struct TestStruct
{
public int Value;
public TestStruct(ref int x)
{
Value = x;
}
}
public static int Main(string[] args)
{
int localVar = 42;
TestStruct t = new TestStruct(ref localVar);
return t.Value;
}
}

View File

@@ -30,10 +30,9 @@ module AbstractMachine =
targetAssy.TypeDefs.[instruction.ExecutingMethod.DeclaringType.Definition.Get]
let baseType =
TypeInfo.resolveBaseType
DumpedAssembly.resolveBaseType
baseClassTypes
_.Name
(fun x y -> x.TypeDefs.[y])
state._LoadedAssemblies
targetAssy.Name
targetType.BaseType

View File

@@ -63,7 +63,7 @@ type EvalStackValue =
override this.ToString () =
match this with
| EvalStackValue.Int32 i -> $"Int32(%i{i}"
| EvalStackValue.Int32 i -> $"Int32(%i{i})"
| EvalStackValue.Int64 i -> $"Int64(%i{i})"
| EvalStackValue.NativeInt src -> $"NativeInt(%O{src})"
| EvalStackValue.Float f -> $"Float(%f{f})"
@@ -141,6 +141,7 @@ module EvalStackValue =
| CliNumericType.Int32 _ ->
match popped with
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.Int32 i)
| EvalStackValue.UserDefinedValueType [ popped ] -> toCliTypeCoerced target popped
| i -> failwith $"TODO: %O{i}"
| CliNumericType.ProvenanceTrackedNativeInt64 _
| CliNumericType.Int64 _ ->
@@ -223,8 +224,14 @@ module EvalStackValue =
| CliType.ValueType fields ->
match popped with
| EvalStackValue.UserDefinedValueType popped ->
if fields.Length <> popped.Length then
failwith "mismatch"
List.map2 toCliTypeCoerced fields popped |> CliType.ValueType
| popped -> failwith $"todo: %O{popped}"
| popped ->
match fields with
| [ target ] -> toCliTypeCoerced target popped
| _ -> failwith "TODO"
let rec ofCliType (v : CliType) : EvalStackValue =
match v with

View File

@@ -295,7 +295,7 @@ module IlMachineState =
let baseType =
arg.BaseType
|> TypeInfo.resolveBaseType corelib _.Name (fun x y -> x.TypeDefs.[y]) assy.Name
|> DumpedAssembly.resolveBaseType corelib state._LoadedAssemblies assy.Name
let signatureTypeKind =
match baseType with
@@ -571,7 +571,9 @@ module IlMachineState =
state, newFrame, oldFrame
else
let args = ImmutableArray.CreateBuilder (methodToCall.Parameters.Length + 1)
let mutable afterPop = activeMethodState
let thisPointer, afterPop = activeMethodState |> MethodState.popFromStack
let mutable afterPop = afterPop
for i = 1 to methodToCall.Parameters.Length do
let poppedArg, afterPop' = afterPop |> MethodState.popFromStack
@@ -581,11 +583,12 @@ module IlMachineState =
afterPop <- afterPop'
args.Add poppedArg
let poppedArg, afterPop = afterPop |> MethodState.popFromStack
// it only matters that the RuntimePointer is a RuntimePointer, so that the coercion has a target of the
// right shape
args.Add (
EvalStackValue.toCliTypeCoerced (CliType.RuntimePointer (CliRuntimePointer.Unmanaged ())) poppedArg
EvalStackValue.toCliTypeCoerced
(CliType.RuntimePointer (CliRuntimePointer.Unmanaged ()))
thisPointer
)
args.Reverse ()
@@ -1160,7 +1163,24 @@ module IlMachineState =
match returnState.WasConstructingObj with
| Some constructing ->
// Assumption: a constructor can't also return a value.
state |> pushToEvalStack (CliType.OfManagedObject constructing) currentThread
// If we were constructing a reference type, we push a reference to it.
// Otherwise, extract the now-complete object from the heap and push it to the stack directly.
let constructed = state.ManagedHeap.NonArrayObjects.[constructing]
let resolvedBaseType =
DumpedAssembly.resolveBaseType
corelib
state._LoadedAssemblies
constructed.Type.Assembly
constructed.Type.BaseType
match resolvedBaseType with
| ResolvedBaseType.Object -> state |> pushToEvalStack (CliType.OfManagedObject constructing) currentThread
| ResolvedBaseType.ValueType ->
state
|> pushToEvalStack (CliType.ValueType (Seq.toList constructed.Fields.Values)) currentThread
| ResolvedBaseType.Enum -> failwith "TODO"
| ResolvedBaseType.Delegate -> failwith "TODO"
| None ->
match threadStateAtEndOfMethod.MethodState.EvaluationStack.Values with
| [] ->

View File

@@ -659,12 +659,61 @@ module NullaryIlOp =
| Ldind_i -> failwith "TODO: Ldind_i unimplemented"
| Ldind_i1 -> failwith "TODO: Ldind_i1 unimplemented"
| Ldind_i2 -> failwith "TODO: Ldind_i2 unimplemented"
| Ldind_i4 -> failwith "TODO: Ldind_i4 unimplemented"
| Ldind_i4 ->
let popped, state = IlMachineState.popEvalStack currentThread state
let value =
let load (c : CliType) =
match c with
| CliType.Bool _ -> failwith "bool"
| CliType.Numeric numeric ->
match numeric with
| CliNumericType.Int32 i -> i
| _ -> failwith $"TODO: {numeric}"
| CliType.Char _ -> failwith "tried to load a Char as a i4"
| CliType.ObjectRef _ -> failwith "tried to load an ObjectRef as a i4"
| CliType.RuntimePointer _ -> failwith "tried to load a RuntimePointer as a i4"
| CliType.ValueType cliTypes -> failwith "todo"
match popped with
| EvalStackValue.ManagedPointer src ->
match src with
| ManagedPointerSource.Null -> failwith "unexpected null pointer in Ldind_i4"
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
let methodState =
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
load methodState
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
let methodState =
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables
.[int<uint16> whichVar]
load methodState
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
| s -> failwith $"TODO(Ldind_i4): {s}"
let state =
state
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 value)) currentThread
|> IlMachineState.advanceProgramCounter currentThread
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
| Ldind_i8 -> failwith "TODO: Ldind_i8 unimplemented"
| Ldind_u1 ->
let popped, state = IlMachineState.popEvalStack currentThread state
let value =
let load (c : CliType) =
match c with
| CliType.Bool b -> b
| CliType.Numeric numeric -> failwith $"tried to load a Numeric as a u8: {numeric}"
| CliType.Char _ -> failwith "tried to load a Char as a u8"
| CliType.ObjectRef _ -> failwith "tried to load an ObjectRef as a u8"
| CliType.RuntimePointer _ -> failwith "tried to load a RuntimePointer as a u8"
| CliType.ValueType cliTypes -> failwith "todo"
match popped with
| EvalStackValue.NativeInt nativeIntSource -> failwith $"TODO: in Ldind_u1, {nativeIntSource}"
| EvalStackValue.ManagedPointer src ->
@@ -674,25 +723,14 @@ module NullaryIlOp =
let methodState =
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
match methodState with
| CliType.Bool b -> b
| CliType.Numeric numeric -> failwith $"tried to load a Numeric as a u8: {numeric}"
| CliType.Char _ -> failwith "tried to load a Char as a u8"
| CliType.ObjectRef _ -> failwith "tried to load an ObjectRef as a u8"
| CliType.RuntimePointer _ -> failwith "tried to load a RuntimePointer as a u8"
| CliType.ValueType cliTypes -> failwith "todo"
load methodState
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
let methodState =
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables
.[int<uint16> whichVar]
match methodState with
| CliType.Bool b -> b
| CliType.Numeric numeric -> failwith $"tried to load a Numeric as a u8: {numeric}"
| CliType.Char _ -> failwith "tried to load a Char as a u8"
| CliType.ObjectRef _ -> failwith "tried to load an ObjectRef as a u8"
| CliType.RuntimePointer _ -> failwith "tried to load a RuntimePointer as a u8"
| CliType.ValueType cliTypes -> failwith "todo"
load methodState
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
| popped -> failwith $"unexpected Ldind_u1 input: {popped}"

View File

@@ -206,6 +206,11 @@ module internal UnaryMetadataIlOp =
let fields = List.rev fieldZeros
// Note: this is a bit unorthodox for value types, which *aren't* heap-allocated.
// We'll perform their construction on the heap, though, to keep the interface
// of Newobj uniform.
// On completion of the constructor, we'll copy the value back off the heap,
// and put it on the eval stack directly.
let allocatedAddr, state =
IlMachineState.allocateManagedObject ctorType fields state
@@ -255,7 +260,7 @@ module internal UnaryMetadataIlOp =
let baseType =
defn.BaseType
|> TypeInfo.resolveBaseType baseClassTypes _.Name (fun x y -> x.TypeDefs.[y]) defn.Assembly
|> DumpedAssembly.resolveBaseType baseClassTypes state._LoadedAssemblies defn.Assembly
let signatureTypeKind =
match baseType with
@@ -312,10 +317,9 @@ module internal UnaryMetadataIlOp =
let ty = activeAssy.TypeDefs.[td]
let baseTy =
TypeInfo.resolveBaseType
DumpedAssembly.resolveBaseType
baseClassTypes
_.Name
(fun x y -> x.TypeDefs.[y])
state._LoadedAssemblies
activeAssy.Name
ty.BaseType
@@ -602,7 +606,7 @@ module internal UnaryMetadataIlOp =
| true, v -> IlMachineState.pushToEvalStack v.Fields.[field.Name] thread state
| ManagedPointerSource.Null -> failwith "TODO: raise NullReferenceException"
| EvalStackValue.ObjectRef managedHeapAddress -> failwith $"todo: {managedHeapAddress}"
| EvalStackValue.UserDefinedValueType _ -> failwith "todo"
| EvalStackValue.UserDefinedValueType _ as udvt -> IlMachineState.pushToEvalStack' udvt thread state
state
|> IlMachineState.advanceProgramCounter thread
@@ -745,6 +749,7 @@ module internal UnaryMetadataIlOp =
baseClassTypes
_.Name
(fun x y -> x.TypeDefs.[y])
(fun x y -> x.TypeRefs.[y] |> failwithf "%+A")
(elementType
|> TypeInfo.mapGeneric (fun i _ ->
{