From a0ee1f971388fa05b3641751dfa4eab7bc236edd Mon Sep 17 00:00:00 2001
From: Patrick Stevens <3138005+Smaug123@users.noreply.github.com>
Date: Sun, 15 Jun 2025 18:42:37 +0100
Subject: [PATCH] Add method generics to type resolution (#48)
---
WoofWare.PawPrint.Domain/Assembly.fs | 6 ++-
WoofWare.PawPrint.Domain/MethodInfo.fs | 35 ++++++++++++---
WoofWare.PawPrint.Domain/TypeInfo.fs | 3 +-
WoofWare.PawPrint/BasicCliType.fs | 2 +-
WoofWare.PawPrint/Corelib.fs | 6 +++
WoofWare.PawPrint/EvalStack.fs | 2 +-
WoofWare.PawPrint/Exceptions.fs | 8 ++--
WoofWare.PawPrint/IlMachineState.fs | 59 +++++++++++++++++---------
WoofWare.PawPrint/MethodState.fs | 4 +-
WoofWare.PawPrint/Program.fs | 1 +
WoofWare.PawPrint/UnaryMetadataIlOp.fs | 2 +
11 files changed, 92 insertions(+), 36 deletions(-)
diff --git a/WoofWare.PawPrint.Domain/Assembly.fs b/WoofWare.PawPrint.Domain/Assembly.fs
index 5d3656f..0517017 100644
--- a/WoofWare.PawPrint.Domain/Assembly.fs
+++ b/WoofWare.PawPrint.Domain/Assembly.fs
@@ -61,7 +61,11 @@ type DumpedAssembly =
///
/// Dictionary of all method definitions in this assembly, keyed by their handle.
///
- Methods : IReadOnlyDictionary>
+ Methods :
+ IReadOnlyDictionary<
+ MethodDefinitionHandle,
+ WoofWare.PawPrint.MethodInfo
+ >
///
/// Dictionary of all member references in this assembly, keyed by their handle.
diff --git a/WoofWare.PawPrint.Domain/MethodInfo.fs b/WoofWare.PawPrint.Domain/MethodInfo.fs
index dbc7927..50bc841 100644
--- a/WoofWare.PawPrint.Domain/MethodInfo.fs
+++ b/WoofWare.PawPrint.Domain/MethodInfo.fs
@@ -145,7 +145,8 @@ type MethodInstructions =
/// Represents detailed information about a method in a .NET assembly.
/// This is a strongly-typed representation of MethodDefinition from System.Reflection.Metadata.
///
-type MethodInfo<'typeGenerics when 'typeGenerics :> IComparable<'typeGenerics> and 'typeGenerics : comparison> =
+type MethodInfo<'typeGenerics, 'methodGenerics
+ when 'typeGenerics :> IComparable<'typeGenerics> and 'typeGenerics : comparison> =
{
///
/// The type that declares this method, along with its assembly information.
@@ -175,7 +176,7 @@ type MethodInfo<'typeGenerics when 'typeGenerics :> IComparable<'typeGenerics> a
///
/// The generic type parameters defined by this method, if any.
///
- Generics : GenericParameter ImmutableArray
+ Generics : 'methodGenerics ImmutableArray
///
/// The signature of the method, including return type and parameter types.
@@ -215,11 +216,11 @@ type MethodInfo<'typeGenerics when 'typeGenerics :> IComparable<'typeGenerics> a
[]
module MethodInfo =
- let mapTypeGenerics<'a, 'b
+ let mapTypeGenerics<'a, 'b, 'methodGen
when 'a :> IComparable<'a> and 'a : comparison and 'b : comparison and 'b :> IComparable<'b>>
(f : int -> 'a -> 'b)
- (m : MethodInfo<'a>)
- : MethodInfo<'b>
+ (m : MethodInfo<'a, 'methodGen>)
+ : MethodInfo<'b, 'methodGen>
=
{
DeclaringType = m.DeclaringType |> ConcreteType.mapGeneric f
@@ -235,6 +236,26 @@ module MethodInfo =
IsStatic = m.IsStatic
}
+ let mapMethodGenerics<'a, 'b, 'typeGen when 'typeGen :> IComparable<'typeGen> and 'typeGen : comparison>
+ (f : int -> 'a -> 'b)
+ (m : MethodInfo<'typeGen, 'a>)
+ : MethodInfo<'typeGen, 'b>
+ =
+ {
+ DeclaringType = m.DeclaringType
+ Handle = m.Handle
+ Name = m.Name
+ Instructions = m.Instructions
+ Parameters = m.Parameters
+ Generics = m.Generics |> Seq.mapi f |> ImmutableArray.CreateRange
+ Signature = m.Signature
+ CustomAttributes = m.CustomAttributes
+ MethodAttributes = m.MethodAttributes
+ ImplAttributes = m.ImplAttributes
+ IsStatic = m.IsStatic
+ }
+
+
type private Dummy = class end
type private MethodBody =
@@ -582,7 +603,7 @@ module MethodInfo =
(peReader : PEReader)
(metadataReader : MetadataReader)
(methodHandle : MethodDefinitionHandle)
- : MethodInfo option
+ : MethodInfo option
=
let logger = loggerFactory.CreateLogger "MethodInfo"
let assemblyName = metadataReader.GetAssemblyDefinition().GetAssemblyName ()
@@ -660,7 +681,7 @@ module MethodInfo =
let rec resolveBaseType
(methodGenerics : TypeDefn ImmutableArray option)
- (executingMethod : MethodInfo)
+ (executingMethod : MethodInfo)
(td : TypeDefn)
: ResolvedBaseType
=
diff --git a/WoofWare.PawPrint.Domain/TypeInfo.fs b/WoofWare.PawPrint.Domain/TypeInfo.fs
index b34e2cc..d9a2f1b 100644
--- a/WoofWare.PawPrint.Domain/TypeInfo.fs
+++ b/WoofWare.PawPrint.Domain/TypeInfo.fs
@@ -34,7 +34,7 @@ type TypeInfo<'generic> =
///
/// All methods defined within this type.
///
- Methods : WoofWare.PawPrint.MethodInfo list
+ Methods : WoofWare.PawPrint.MethodInfo list
///
/// Method implementation mappings for this type, often used for interface implementations
@@ -128,6 +128,7 @@ type BaseClassTypes<'corelib> =
RuntimeMethodHandle : TypeInfo
RuntimeFieldHandle : TypeInfo
RuntimeTypeHandle : TypeInfo
+ RuntimeType : TypeInfo
}
[]
diff --git a/WoofWare.PawPrint/BasicCliType.fs b/WoofWare.PawPrint/BasicCliType.fs
index 37ecaa5..a392178 100644
--- a/WoofWare.PawPrint/BasicCliType.fs
+++ b/WoofWare.PawPrint/BasicCliType.fs
@@ -57,7 +57,7 @@ type CliNumericType =
| Float32 of float32
| Float64 of float
/// Not a real CLI numeric type! Represents an int64 obtained by taking a NativeInt from the eval stack.
- | ProvenanceTrackedNativeInt64 of MethodInfo
+ | ProvenanceTrackedNativeInt64 of MethodInfo
type CliValueType =
private
diff --git a/WoofWare.PawPrint/Corelib.fs b/WoofWare.PawPrint/Corelib.fs
index 64d73a4..cbdbe19 100644
--- a/WoofWare.PawPrint/Corelib.fs
+++ b/WoofWare.PawPrint/Corelib.fs
@@ -104,6 +104,11 @@ module Corelib =
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "RuntimeTypeHandle" then Some v else None)
|> Seq.exactlyOne
+ let runtimeTypeType =
+ corelib.TypeDefs
+ |> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "RuntimeType" then Some v else None)
+ |> Seq.exactlyOne
+
let runtimeFieldHandleType =
corelib.TypeDefs
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "RuntimeFieldHandle" then Some v else None)
@@ -132,4 +137,5 @@ module Corelib =
RuntimeTypeHandle = runtimeTypeHandleType
RuntimeMethodHandle = runtimeMethodHandleType
RuntimeFieldHandle = runtimeFieldHandleType
+ RuntimeType = runtimeTypeType
}
diff --git a/WoofWare.PawPrint/EvalStack.fs b/WoofWare.PawPrint/EvalStack.fs
index 3803744..48f4737 100644
--- a/WoofWare.PawPrint/EvalStack.fs
+++ b/WoofWare.PawPrint/EvalStack.fs
@@ -16,7 +16,7 @@ type ManagedPointerSource =
[]
type NativeIntSource =
| Verbatim of int64
- | FunctionPointer of MethodInfo
+ | FunctionPointer of MethodInfo
override this.ToString () : string =
match this with
diff --git a/WoofWare.PawPrint/Exceptions.fs b/WoofWare.PawPrint/Exceptions.fs
index 43fe1c2..3409841 100644
--- a/WoofWare.PawPrint/Exceptions.fs
+++ b/WoofWare.PawPrint/Exceptions.fs
@@ -5,7 +5,7 @@ open System.Collections.Immutable
/// Represents a location in the code where an exception occurred
type ExceptionStackFrame =
{
- Method : WoofWare.PawPrint.MethodInfo
+ Method : WoofWare.PawPrint.MethodInfo
/// The number of bytes into the IL of the method we were in
IlOffset : int
}
@@ -44,7 +44,7 @@ module ExceptionHandling =
let findExceptionHandler
(currentPC : int)
(exceptionTypeCrate : TypeInfoCrate)
- (method : WoofWare.PawPrint.MethodInfo)
+ (method : WoofWare.PawPrint.MethodInfo)
(assemblies : ImmutableDictionary)
: (WoofWare.PawPrint.ExceptionRegion * bool) option // handler, isFinally
=
@@ -92,7 +92,7 @@ module ExceptionHandling =
let findFinallyBlocksToRun
(currentPC : int)
(targetPC : int)
- (method : WoofWare.PawPrint.MethodInfo)
+ (method : WoofWare.PawPrint.MethodInfo)
: ExceptionOffset list
=
match method.Instructions with
@@ -122,7 +122,7 @@ module ExceptionHandling =
/// Get the active exception regions at a given offset
let getActiveRegionsAtOffset
(offset : int)
- (method : WoofWare.PawPrint.MethodInfo)
+ (method : WoofWare.PawPrint.MethodInfo)
: WoofWare.PawPrint.ExceptionRegion list
=
match method.Instructions with
diff --git a/WoofWare.PawPrint/IlMachineState.fs b/WoofWare.PawPrint/IlMachineState.fs
index 474ff77..a10b6c0 100644
--- a/WoofWare.PawPrint/IlMachineState.fs
+++ b/WoofWare.PawPrint/IlMachineState.fs
@@ -1,6 +1,5 @@
namespace WoofWare.PawPrint
-open System
open System.Collections.Immutable
open System.IO
open System.Reflection
@@ -246,11 +245,11 @@ module IlMachineState =
(loggerFactory : ILoggerFactory)
(referencedInAssembly : DumpedAssembly)
(target : TypeRef)
- (genericArgs : ImmutableArray option)
+ (typeGenericArgs : ImmutableArray option)
(state : IlMachineState)
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo
=
- match Assembly.resolveTypeRef state._LoadedAssemblies referencedInAssembly target genericArgs with
+ match Assembly.resolveTypeRef state._LoadedAssemblies referencedInAssembly target typeGenericArgs with
| TypeResolutionResult.Resolved (assy, typeDef) -> state, assy, typeDef
| TypeResolutionResult.FirstLoadAssy loadFirst ->
let state, _, _ =
@@ -260,7 +259,7 @@ module IlMachineState =
(fst loadFirst.Handle)
state
- resolveTypeFromRef loggerFactory referencedInAssembly target genericArgs state
+ resolveTypeFromRef loggerFactory referencedInAssembly target typeGenericArgs state
and resolveType
(loggerFactory : ILoggerFactory)
@@ -278,7 +277,8 @@ module IlMachineState =
(loggerFactory : ILoggerFactory)
(corelib : BaseClassTypes)
(ty : TypeDefn)
- (genericArgs : ImmutableArray option)
+ (typeGenericArgs : ImmutableArray option)
+ (methodGenericArgs : ImmutableArray)
(assy : DumpedAssembly)
(state : IlMachineState)
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo
@@ -291,7 +291,7 @@ module IlMachineState =
(state, args)
||> Seq.fold (fun state arg ->
let state, assy, arg =
- resolveTypeFromDefn loggerFactory corelib arg genericArgs assy state
+ resolveTypeFromDefn loggerFactory corelib arg typeGenericArgs methodGenericArgs assy state
let baseType =
arg.BaseType
@@ -316,21 +316,23 @@ module IlMachineState =
)
let args' = args'.ToImmutable ()
- resolveTypeFromDefn loggerFactory corelib generic (Some args') assy state
+ resolveTypeFromDefn loggerFactory corelib generic (Some args') methodGenericArgs assy state
| TypeDefn.FromDefinition (defn, assy, _typeKind) ->
let assy = state._LoadedAssemblies.[assy]
let defn =
assy.TypeDefs.[defn.Get]
|> TypeInfo.mapGeneric (fun _ param ->
- match genericArgs with
- | None -> failwith "somehow got a generic TypeDefn.FromDefinition without any generic args"
+ match typeGenericArgs with
+ | None -> failwith "somehow got a generic TypeDefn.FromDefinition without any type generic args"
| Some genericArgs -> genericArgs.[param.SequenceNumber]
)
state, assy, defn
| TypeDefn.FromReference (ref, _typeKind) ->
- let state, assy, ty = resolveTypeFromRef loggerFactory assy ref genericArgs state
+ let state, assy, ty =
+ resolveTypeFromRef loggerFactory assy ref typeGenericArgs state
+
state, assy, ty
| TypeDefn.PrimitiveType prim ->
let ty =
@@ -356,12 +358,16 @@ module IlMachineState =
state, corelib.Corelib, ty
| TypeDefn.GenericTypeParameter param ->
- match genericArgs with
+ match typeGenericArgs with
| None -> failwith "tried to resolve generic parameter without generic args"
| Some genericArgs ->
let arg = genericArgs.[param]
// TODO: this assembly is probably wrong?
- resolveTypeFromDefn loggerFactory corelib arg (Some genericArgs) assy state
+ resolveTypeFromDefn loggerFactory corelib arg (Some genericArgs) methodGenericArgs assy state
+ | TypeDefn.GenericMethodParameter param ->
+ let arg = methodGenericArgs.[param]
+ // TODO: this assembly is probably wrong?
+ resolveTypeFromDefn loggerFactory corelib arg typeGenericArgs methodGenericArgs assy state
| s -> failwith $"TODO: resolveTypeFromDefn unimplemented for {s}"
let resolveTypeFromSpec
@@ -370,10 +376,12 @@ module IlMachineState =
(ty : TypeSpecificationHandle)
(assy : DumpedAssembly)
(typeGenericArgs : TypeDefn ImmutableArray option)
+ (methodGenericArgs : TypeDefn ImmutableArray)
(state : IlMachineState)
: IlMachineState * DumpedAssembly * WoofWare.PawPrint.TypeInfo
=
- resolveTypeFromDefn loggerFactory corelib assy.TypeSpecs.[ty].Signature typeGenericArgs assy state
+ let sign = assy.TypeSpecs.[ty].Signature
+ resolveTypeFromDefn loggerFactory corelib sign typeGenericArgs methodGenericArgs assy state
let rec cliTypeZeroOf
(loggerFactory : ILoggerFactory)
@@ -400,7 +408,7 @@ module IlMachineState =
(wasConstructing : ManagedHeapAddress option)
(wasClassConstructor : bool)
(methodGenerics : ImmutableArray option)
- (methodToCall : WoofWare.PawPrint.MethodInfo)
+ (methodToCall : WoofWare.PawPrint.MethodInfo)
(thread : ThreadId)
(threadState : ThreadState)
(state : IlMachineState)
@@ -431,6 +439,10 @@ module IlMachineState =
let activeMethodState = threadState.MethodStates.[threadState.ActiveMethodState]
+ let methodToCall =
+ methodToCall
+ |> MethodInfo.mapMethodGenerics (fun _ param -> methodGenerics.Value.[param.SequenceNumber])
+
let state, newFrame, oldFrame =
if methodToCall.IsStatic then
let args = ImmutableArray.CreateBuilder methodToCall.Parameters.Length
@@ -672,7 +684,9 @@ module IlMachineState =
let currentThreadState = state.ThreadState.[currentThread]
let cctorMethod =
- cctorMethod |> MethodInfo.mapTypeGenerics (fun i _ -> ty.Generics.[i])
+ cctorMethod
+ |> MethodInfo.mapTypeGenerics (fun i _ -> ty.Generics.[i])
+ |> MethodInfo.mapMethodGenerics (fun _ -> failwith "cctor cannot be generic")
callMethod
loggerFactory
@@ -723,7 +737,7 @@ module IlMachineState =
(corelib : BaseClassTypes)
(thread : ThreadId)
(methodGenerics : TypeDefn ImmutableArray option)
- (methodToCall : WoofWare.PawPrint.MethodInfo)
+ (methodToCall : WoofWare.PawPrint.MethodInfo)
(weAreConstructingObj : ManagedHeapAddress option)
(state : IlMachineState)
: IlMachineState * WhatWeDid
@@ -977,7 +991,10 @@ module IlMachineState =
(state : IlMachineState)
: IlMachineState *
AssemblyName *
- Choice, WoofWare.PawPrint.FieldInfo>
+ Choice<
+ WoofWare.PawPrint.MethodInfo,
+ WoofWare.PawPrint.FieldInfo
+ >
=
// TODO: do we need to initialise the parent class here?
let mem = assy.Members.[m]
@@ -988,12 +1005,16 @@ module IlMachineState =
match mem.Parent with
| MetadataToken.TypeReference parent -> resolveType loggerFactory parent None assy state
| MetadataToken.TypeSpecification parent ->
+ let executing = state.ThreadState.[currentThread].MethodState.ExecutingMethod
+
let typeGenerics =
- match state.ThreadState.[currentThread].MethodState.ExecutingMethod.DeclaringType.Generics with
+ match executing.DeclaringType.Generics with
| [] -> None
| l -> Some (ImmutableArray.CreateRange l)
- resolveTypeFromSpec loggerFactory corelib parent assy typeGenerics state
+ let methodGenerics = executing.Generics
+
+ resolveTypeFromSpec loggerFactory corelib parent assy typeGenerics methodGenerics state
| parent -> failwith $"Unexpected: {parent}"
match mem.Signature with
diff --git a/WoofWare.PawPrint/MethodState.fs b/WoofWare.PawPrint/MethodState.fs
index 2a6128c..287f882 100644
--- a/WoofWare.PawPrint/MethodState.fs
+++ b/WoofWare.PawPrint/MethodState.fs
@@ -19,7 +19,7 @@ and MethodState =
_IlOpIndex : int
EvaluationStack : EvalStack
Arguments : CliType ImmutableArray
- ExecutingMethod : WoofWare.PawPrint.MethodInfo
+ ExecutingMethod : WoofWare.PawPrint.MethodInfo
/// We don't implement the local memory pool right now
LocalMemoryPool : unit
/// On return, we restore this state. This should be Some almost always; an exception is the entry point.
@@ -138,7 +138,7 @@ and MethodState =
(corelib : BaseClassTypes)
(loadedAssemblies : ImmutableDictionary)
(containingAssembly : DumpedAssembly)
- (method : WoofWare.PawPrint.MethodInfo)
+ (method : WoofWare.PawPrint.MethodInfo)
(methodGenerics : ImmutableArray option)
(args : ImmutableArray)
(returnState : MethodReturnState option)
diff --git a/WoofWare.PawPrint/Program.fs b/WoofWare.PawPrint/Program.fs
index 67445d2..c8bb311 100644
--- a/WoofWare.PawPrint/Program.fs
+++ b/WoofWare.PawPrint/Program.fs
@@ -68,6 +68,7 @@ module Program =
let mainMethod =
mainMethod
|> MethodInfo.mapTypeGenerics (fun _ -> failwith "Refusing to execute generic main method")
+ |> MethodInfo.mapMethodGenerics (fun _ -> failwith "Refusing to execute generic main method")
let rec computeState (baseClassTypes : BaseClassTypes option) (state : IlMachineState) =
// The thread's state is slightly fake: we will need to put arguments onto the stack before actually
diff --git a/WoofWare.PawPrint/UnaryMetadataIlOp.fs b/WoofWare.PawPrint/UnaryMetadataIlOp.fs
index a8908e9..fc2072c 100644
--- a/WoofWare.PawPrint/UnaryMetadataIlOp.fs
+++ b/WoofWare.PawPrint/UnaryMetadataIlOp.fs
@@ -681,6 +681,7 @@ module internal UnaryMetadataIlOp =
spec
assy
declaringTypeGenerics
+ currentMethod.Generics
state
| x -> failwith $"TODO: Stelem element type resolution unimplemented for {x}"
@@ -754,6 +755,7 @@ module internal UnaryMetadataIlOp =
spec
assy
declaringTypeGenerics
+ currentMethod.Generics
state
| x -> failwith $"TODO: Ldelem element type resolution unimplemented for {x}"