Track provenance of native ints (#14)

This commit is contained in:
Patrick Stevens
2025-05-25 20:58:10 +01:00
committed by GitHub
parent 658f54e2eb
commit 8dda1a0ceb
6 changed files with 113 additions and 43 deletions

View File

@@ -462,7 +462,12 @@ module IlMachineState =
else
let args = ImmutableArray.CreateBuilder (methodToCall.Parameters.Length + 1)
let poppedArg, afterPop = threadState.MethodState |> MethodState.popFromStack
args.Add (EvalStackValue.toCliTypeCoerced (CliType.ObjectRef None) poppedArg)
// 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
)
let mutable afterPop = afterPop
for i = 1 to methodToCall.Parameters.Length do
@@ -1106,8 +1111,7 @@ module AbstractMachine =
failwith "TODO: Clt NativeInt vs Int32 comparison unimplemented"
| other, EvalStackValue.Int32 var2 -> failwith $"invalid comparison, {other} vs int32 {var2}"
| EvalStackValue.NativeInt var1, EvalStackValue.NativeInt var2 -> if var1 < var2 then 1 else 0
| EvalStackValue.NativeInt var1, other ->
failwith $"invalid comparison, nativeint %i{var1} vs %O{other}"
| EvalStackValue.NativeInt var1, other -> failwith $"invalid comparison, nativeint {var1} vs %O{other}"
| EvalStackValue.ManagedPointer managedPointerSource, NativeInt int64 ->
failwith "TODO: Clt ManagedPointer vs NativeInt comparison unimplemented"
| EvalStackValue.ManagedPointer managedPointerSource, ManagedPointer pointerSource ->
@@ -1221,10 +1225,12 @@ module AbstractMachine =
| Some conv ->
// > If overflow occurs when converting one integer type to another, the high-order bits are silently truncated.
let conv =
if conv > uint64 System.Int64.MaxValue then
(conv % uint64 System.Int64.MaxValue) |> int64
else
int64 conv
match conv with
| UnsignedNativeIntSource.Verbatim conv ->
if conv > uint64 System.Int64.MaxValue then
(conv % uint64 System.Int64.MaxValue) |> int64 |> NativeIntSource.Verbatim
else
int64 conv |> NativeIntSource.Verbatim
state
|> IlMachineState.pushToEvalStack' (EvalStackValue.NativeInt conv) currentThread
@@ -1520,7 +1526,8 @@ module AbstractMachine =
match popped with
| EvalStackValue.ManagedPointer source ->
match source with
| ManagedPointerSource.LocalVariable -> failwith "TODO: Stsfld LocalVariable storage unimplemented"
| ManagedPointerSource.LocalVariable _ ->
failwith "TODO: Stsfld LocalVariable storage unimplemented"
| ManagedPointerSource.Heap addr -> CliType.ObjectRef (Some addr)
| ManagedPointerSource.Null -> CliType.ObjectRef None
| _ -> failwith "TODO: Stsfld non-managed pointer storage unimplemented"
@@ -1706,7 +1713,7 @@ module AbstractMachine =
match popped with
| EvalStackValue.Int32 i -> i <> 0
| EvalStackValue.Int64 i -> i <> 0L
| EvalStackValue.NativeInt i -> i <> 0L
| EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i)
| EvalStackValue.Float f -> failwith "TODO: Brtrue_s float semantics undocumented"
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false
| EvalStackValue.ManagedPointer _ -> true
@@ -1728,7 +1735,7 @@ module AbstractMachine =
match popped with
| EvalStackValue.Int32 i -> i = 0
| EvalStackValue.Int64 i -> i = 0L
| EvalStackValue.NativeInt i -> i = 0L
| EvalStackValue.NativeInt i -> NativeIntSource.isZero i
| EvalStackValue.Float f -> failwith "TODO: Brfalse float semantics undocumented"
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> true
| EvalStackValue.ManagedPointer _ -> false
@@ -1750,7 +1757,7 @@ module AbstractMachine =
match popped with
| EvalStackValue.Int32 i -> i <> 0
| EvalStackValue.Int64 i -> i <> 0L
| EvalStackValue.NativeInt i -> i <> 0L
| EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i)
| EvalStackValue.Float f -> failwith "TODO: Brtrue float semantics undocumented"
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false
| EvalStackValue.ManagedPointer _ -> true
@@ -1790,7 +1797,7 @@ module AbstractMachine =
let state =
state
|> IlMachineState.pushToEvalStack'
(EvalStackValue.ManagedPointer (ManagedPointerSource.LocalVariable))
(EvalStackValue.ManagedPointer (ManagedPointerSource.LocalVariable ((), uint16<uint8> b)))
currentThread
|> IlMachineState.advanceProgramCounter currentThread

View File

@@ -325,6 +325,7 @@ module Assembly =
builder.Add (
c,
MemberReference.make<MetadataToken>
metadataReader.GetBlobReader
metadataReader.GetString
MetadataToken.ofEntityHandle
(metadataReader.GetMemberReference c)

View File

@@ -59,9 +59,12 @@ type CliValueType =
| Float32 of float32
| Float64 of float
[<RequireQualifiedAccess>]
type CliRuntimePointerSource = | LocalVariable of source : unit * whichVar : uint16
type CliRuntimePointer =
| Unmanaged of unit
| Managed of unit
| Managed of CliRuntimePointerSource
/// This is the kind of type that can be stored in arguments, local variables, statics, array elements, fields.
type CliType =

View File

@@ -3,15 +3,31 @@ namespace WoofWare.PawPrint
open Microsoft.FSharp.Core
type ManagedPointerSource =
| LocalVariable
| LocalVariable of source : unit * whichVar : uint16
| Heap of ManagedHeapAddress
| Null
[<RequireQualifiedAccess>]
type NativeIntSource = | Verbatim of int64
[<RequireQualifiedAccess>]
module NativeIntSource =
let isZero (n : NativeIntSource) : bool =
match n with
| NativeIntSource.Verbatim i -> i = 0L
let isNonnegative (n : NativeIntSource) : bool =
match n with
| NativeIntSource.Verbatim i -> i >= 0L
[<RequireQualifiedAccess>]
type UnsignedNativeIntSource = | Verbatim of uint64
/// See I.12.3.2.1 for definition
type EvalStackValue =
| Int32 of int32
| Int64 of int64
| NativeInt of int64
| NativeInt of NativeIntSource
| Float of float
| ManagedPointer of ManagedPointerSource
| ObjectRef of ManagedHeapAddress
@@ -22,17 +38,27 @@ type EvalStackValue =
[<RequireQualifiedAccess>]
module EvalStackValue =
/// The conversion performed by Conv_u.
let toUnsignedNativeInt (value : EvalStackValue) : uint64 option =
let toUnsignedNativeInt (value : EvalStackValue) : UnsignedNativeIntSource option =
// Table III.8
match value with
| EvalStackValue.Int32 i ->
if i >= 0 then
Some (uint64 i)
Some (uint64 i |> UnsignedNativeIntSource.Verbatim)
else
// Zero-extend.
failwith "todo"
| EvalStackValue.Int64 i -> if i >= 0L then Some (uint64 i) else failwith "todo"
| EvalStackValue.NativeInt i -> if i >= 0L then Some (uint64 i) else failwith "todo"
| EvalStackValue.Int64 i ->
if i >= 0L then
Some (uint64 i |> UnsignedNativeIntSource.Verbatim)
else
failwith "todo"
| EvalStackValue.NativeInt i ->
match i with
| NativeIntSource.Verbatim i ->
if i >= 0L then
uint64 i |> UnsignedNativeIntSource.Verbatim |> Some
else
failwith "todo"
| EvalStackValue.Float f -> failwith "todo"
| EvalStackValue.ManagedPointer managedPointerSource -> failwith "todo"
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
@@ -62,10 +88,14 @@ module EvalStackValue =
match popped with
| EvalStackValue.ManagedPointer ptrSource ->
match ptrSource with
| ManagedPointerSource.LocalVariable ->
| ManagedPointerSource.LocalVariable _ ->
failwith "TODO: trying to fit a local variable address into an ObjectRef"
| ManagedPointerSource.Heap managedHeapAddress -> CliType.ObjectRef (Some managedHeapAddress)
| ManagedPointerSource.Null -> CliType.ObjectRef None
| EvalStackValue.NativeInt nativeIntSource ->
match nativeIntSource with
| NativeIntSource.Verbatim 0L -> CliType.ObjectRef None
| NativeIntSource.Verbatim i -> failwith $"refusing to interpret verbatim native int {i} as a pointer"
| i -> failwith $"TODO: %O{i}"
| CliType.Bool _ ->
match popped with
@@ -75,7 +105,18 @@ module EvalStackValue =
| EvalStackValue.ManagedPointer src ->
failwith $"unexpectedly tried to convert a managed pointer (%O{src}) into a bool"
| i -> failwith $"TODO: %O{i}"
| i -> failwith $"TODO: %O{i}"
| CliType.RuntimePointer _ ->
match popped with
| EvalStackValue.ManagedPointer src ->
match src with
| ManagedPointerSource.Heap addr -> CliType.OfManagedObject addr
| ManagedPointerSource.Null -> CliType.ObjectRef None
| ManagedPointerSource.LocalVariable (source, var) ->
CliType.RuntimePointer (
CliRuntimePointer.Managed (CliRuntimePointerSource.LocalVariable (source, var))
)
| _ -> failwith $"TODO: %O{popped}"
| CliType.Char _ -> failwith "TODO: char"
type EvalStack =
{
@@ -129,6 +170,12 @@ type EvalStack =
// Zero-extend bool/char
| CliType.Bool b -> int32 b |> EvalStackValue.Int32
| CliType.Char (high, low) -> int32 high * 256 + int32 low |> EvalStackValue.Int32
| CliType.RuntimePointer cliRuntimePointer -> failwith "todo"
| CliType.RuntimePointer ptr ->
match ptr with
| CliRuntimePointer.Unmanaged () -> failwith "todo: unmanaged"
| CliRuntimePointer.Managed ptr ->
match ptr with
| CliRuntimePointerSource.LocalVariable (source, var) ->
EvalStackValue.ManagedPointer (ManagedPointerSource.LocalVariable (source, var))
EvalStack.Push' v stack

View File

@@ -47,6 +47,7 @@ type MemberRefSigSwitch =
[<RequireQualifiedAccess>]
module MemberReference =
let make<'parent>
(blobReader : BlobHandle -> BlobReader)
(getString : StringHandle -> string)
(makeParent : EntityHandle -> 'parent)
(mr : System.Reflection.Metadata.MemberReference)
@@ -54,11 +55,17 @@ module MemberReference =
=
let name = StringToken.String mr.Name
let br = blobReader mr.Signature
let header = br.ReadSignatureHeader ()
let signature =
try
mr.DecodeMethodSignature (TypeDefn.typeProvider, ()) |> Choice1Of2
with :? BadImageFormatException ->
mr.DecodeFieldSignature (TypeDefn.typeProvider, ()) |> Choice2Of2
match header.Kind with
| SignatureKind.Method -> mr.DecodeMethodSignature (TypeDefn.typeProvider, ()) |> Choice1Of2
| SignatureKind.Field -> mr.DecodeFieldSignature (TypeDefn.typeProvider, ()) |> Choice2Of2
| SignatureKind.LocalVariables -> failwith "TODO: LocalVariables"
| SignatureKind.Property -> failwith "TODO: Property"
| SignatureKind.MethodSpecification -> failwith "TODO: MethodSpec"
| i -> raise (ArgumentOutOfRangeException $"{i}")
let signature =
match signature with

View File

@@ -2,7 +2,6 @@ namespace WoofWare.PawPrint
#nowarn "9"
open System
open System.Collections.Immutable
open System.Reflection
open System.Reflection.Metadata
@@ -34,17 +33,22 @@ type Parameter =
[<RequireQualifiedAccess>]
module Parameter =
let readAll (metadata : MetadataReader) (param : ParameterHandleCollection) : Parameter ImmutableArray =
param
|> Seq.map (fun param ->
let result = ImmutableArray.CreateBuilder ()
for param in param do
let param = metadata.GetParameter param
{
Name = metadata.GetString param.Name
DefaultValue = metadata.GetConstant (param.GetDefaultValue ())
SequenceNumber = param.SequenceNumber
}
)
|> ImmutableArray.CreateRange
// The spec doesn't seem to mention this behaviour, but a sequence number of 0 (and an unnamed parameter)
// seems to correspond with a ref return.
if param.SequenceNumber <> 0 then
{
Name = metadata.GetString param.Name
DefaultValue = metadata.GetConstant (param.GetDefaultValue ())
SequenceNumber = param.SequenceNumber
}
|> result.Add
result.ToImmutable ()
/// <summary>
/// Represents a generic type or method parameter definition.
@@ -187,13 +191,12 @@ module MethodInfo =
let methodBody = peReader.GetMethodBody methodDef.RelativeVirtualAddress
let localSig =
let s = methodBody.LocalSignature |> metadataReader.GetStandaloneSignature
// :sob: why are all the useful methods internal
try
s.Signature |> ignore<BlobHandle> |> Some
with :? BadImageFormatException ->
if methodBody.LocalSignature.IsNil then
None
|> Option.map (fun () -> s.DecodeLocalSignature (TypeDefn.typeProvider, ()))
else
let s = methodBody.LocalSignature |> metadataReader.GetStandaloneSignature
s.DecodeLocalSignature (TypeDefn.typeProvider, ()) |> Some
let ilBytes = methodBody.GetILBytes ()
use bytes = fixed ilBytes
@@ -505,6 +508,8 @@ module MethodInfo =
None
| Some methodBody ->
let typeSig = TypeMethodSignature.make methodSig
let methodParams = Parameter.readAll metadataReader (methodDef.GetParameters ())
let methodGenericParams =
@@ -518,7 +523,7 @@ module MethodInfo =
Locations = methodBody.Instructions |> List.map (fun (a, b) -> b, a) |> Map.ofList
Parameters = methodParams
Generics = methodGenericParams
Signature = TypeMethodSignature.make methodSig
Signature = typeSig
IsPinvokeImpl = methodDef.Attributes.HasFlag MethodAttributes.PinvokeImpl
LocalsInit = methodBody.LocalInit
LocalVars = methodBody.LocalSig