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}"