29 Commits

Author SHA1 Message Date
Patrick Stevens
5e7bd969ba Convert correctness bug in overlapping fields into a failwith (#120) 2025-08-29 08:45:23 +00:00
Patrick Stevens
07fabfff65 Implement sizeof completely (#119) 2025-08-27 23:46:50 +00:00
Patrick Stevens
655ba4400a Centralise field handling in structs (#118) 2025-08-27 19:49:50 +01:00
Patrick Stevens
c58c8ce678 Progress towards the GenericEdgeCases test (#96) 2025-08-25 11:27:14 +00:00
Patrick Stevens
239ae0f0cd Implement dereferencing more (#117) 2025-08-24 19:44:18 +00:00
Patrick Stevens
4de0dbd816 Add another test and put real-version first (#116) 2025-08-24 19:38:43 +00:00
Patrick Stevens
91aff34d1e Fix a TODO (#115) 2025-08-24 19:27:38 +00:00
Patrick Stevens
f9e186ba8f Initobj (#114) 2025-08-24 19:23:50 +00:00
Patrick Stevens
622d0782ae Add field pointer handle (#113) 2025-08-24 09:44:57 +00:00
Patrick Stevens
3e4b0a7b7e Plumb through field offset info (#112) 2025-08-24 09:05:31 +00:00
Patrick Stevens
5f35c7a7cd ConcreteChar matcher (#111) 2025-08-24 08:13:06 +00:00
Patrick Stevens
9afc7efea1 Interface dispatch (#100) 2025-08-23 21:43:57 +00:00
Patrick Stevens
2190f148e1 Test for interface dispatch (#109) 2025-08-23 17:50:07 +00:00
Patrick Stevens
e2e3d5c3bf Tidy up a bit (#108) 2025-08-23 17:41:12 +00:00
Patrick Stevens
92f22cff42 Plumb generic metadata through (#107) 2025-08-23 15:18:05 +01:00
Patrick Stevens
3bdfeaf8a1 Delete spare Program.fs (#106) 2025-08-22 19:20:54 +00:00
Patrick Stevens
5c14baec9f Store interface implementations (#105) 2025-08-22 19:17:39 +00:00
Patrick Stevens
174e415c70 Add constraints to generics (#104) 2025-08-22 19:14:19 +00:00
Patrick Stevens
a531531aef More concrete active patterns (#103) 2025-08-22 19:11:02 +00:00
Patrick Stevens
4c64dd7eb5 Implement Bne_un_s (#102) 2025-08-22 13:37:56 +00:00
Patrick Stevens
cfd6716616 Switch back to NUnit (#101) 2025-08-22 13:34:36 +00:00
Patrick Stevens
d711d6fff5 Check types of JIT calls (#97) 2025-08-10 22:32:23 +00:00
Patrick Stevens
6dbe6614d5 Remove local variable checks in tests (#98) 2025-08-10 22:29:02 +00:00
Patrick Stevens
d8b7e84f6c Implement bgt_s (#95) 2025-07-05 23:32:12 +01:00
Patrick Stevens
3af1f2cc97 Implement Ldtoken for fields (#94) 2025-07-05 22:21:58 +01:00
Patrick Stevens
56d1cf63d6 Remove weird duplicate ConcreteType thing (#93) 2025-07-04 18:43:45 +00:00
Patrick Stevens
de1eefb436 Implement Sizeof (#92) 2025-07-04 18:08:56 +00:00
Patrick Stevens
30b8ec990f Split up IlMachineState (#91) 2025-07-03 08:51:48 +01:00
Patrick Stevens
2efe9803da Switch to Expecto (#89) 2025-07-02 23:14:29 +00:00
56 changed files with 6974 additions and 2233 deletions

View File

@@ -42,8 +42,13 @@ dotnet fantomas .
```
### Running the Application
A playground C# file is in CSharpExample/Class1.cs.
This environment is convenient for running WoofWare.PawPrint against a standalone DLL.
Interpolate the appropriate strings like `{Platform}` as necessary depending on the current environment and the output of the `dotnet publish`.
```bash
dotnet publish --self-contained --runtime-id osx-arm64 CSharpExample/ && dotnet run --project WoofWare.PawPrint.App/WoofWare.PawPrint.App.fsproj -- CSharpExample/bin/Debug/net9.0/osx-arm64/publish/CSharpExample.dll
dotnet publish --self-contained --runtime {Platform} CSharpExample/
dotnet run --project WoofWare.PawPrint.App/WoofWare.PawPrint.App.fsproj -- CSharpExample/bin/{Configuration}/{Framework}/{Platform}/publish/CSharpExample.dll
```
## Architecture
@@ -62,10 +67,13 @@ dotnet publish --self-contained --runtime-id osx-arm64 CSharpExample/ && dotnet
- `Corelib.fs`: Core library type definitions (String, Array, etc.)
**WoofWare.PawPrint.Test**
- Uses NUnit as the test framework
- Test cases are defined in `TestCases.fs`
- C# source files in `sources/` are compiled and executed by the runtime as test cases
- Uses Expecto as the test framework
- Test cases are defined in `TestPureCases.fs` and `TestImpureCases.fs`
- C# source files in `sources{Pure,Impure}/` are compiled and executed by the runtime as test cases
- `TestHarness.fs` provides infrastructure for running test assemblies through the interpreter
- Run all tests with `dotnet run --project WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj -- --no-spinner` (note the additional `--`)
- Run a specific test with `dotnet run --project WoofWare.PawPrint.Test/WoofWare.PawPrint.Test.fsproj -- --filter-test-case StringWithinTestName --no-spinner`
- Pending test definitions must be moved into the non-pending test case list before they can be run.
**WoofWare.PawPrint.App**
- Entry point application for running the interpreter
@@ -90,8 +98,8 @@ dotnet publish --self-contained --runtime-id osx-arm64 CSharpExample/ && dotnet
When adding new IL instruction support:
1. Add the instruction to `IlOp.fs`
2. Implement execution logic in `AbstractMachine.fs`
3. Add a test case in `sources/` (C# file) that exercises the instruction
4. Add the test case to `TestCases.fs`
3. Add a test case in `sourcesPure/` or `sourcesImpure/` (C# file) that exercises the instruction, remembering also to add the file as an EmbeddedResource in WoofWare.PawPrint.Test.fsproj
4. Add the test case to `TestPureCases.fs` or `TestImpureCases.fs`
5. Run tests to verify implementation
The project uses deterministic builds and treats warnings as errors to maintain code quality.
@@ -174,3 +182,47 @@ When encountering errors:
4. Add logging to see generic contexts: `failwithf "Failed to concretize: %A" typeDefn`
5. Check if you're in a generic method calling another generic method
6. Verify TypeRefs are being resolved in the correct assembly
## Common Type System Patterns
### Creating TypeDefn from Type Metadata
When you need to create a `TypeDefn` from type metadata (e.g., from a `TypeInfo`), there's a common pattern that involves:
1. Resolving the base type to determine `SignatureTypeKind`
2. Creating the base `TypeDefn.FromDefinition`
3. For generic types, creating a `GenericInstantiation` with type parameters
This pattern is implemented in `UnaryMetadataIlOp.lookupTypeDefn`. Example usage:
```fsharp
let state, typeDefn =
UnaryMetadataIlOp.lookupTypeDefn
baseClassTypes
state
activeAssembly
typeDefHandle
```
### Field Signature Comparison in Generic Contexts
When comparing field signatures in generic contexts (e.g., when resolving member references), signatures must be concretized before comparison. This ensures generic type parameters are properly substituted:
```fsharp
// Concretize both signatures before comparing
let state, concreteFieldSig = concretizeType ... fieldSig
let state, fieldSigConcrete = concretizeType ... fi.Signature
if fieldSigConcrete = concreteFieldSig then ...
```
### Static vs Instance Fields
When constructing objects with `Newobj`:
- Only instance fields should be included in the object
- Static fields belong to the type, not instances
- Filter using: `field.Attributes.HasFlag FieldAttributes.Static`
Example:
```fsharp
let instanceFields =
ctorType.Fields
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
```

View File

@@ -6,6 +6,11 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<WarningsAsErrors>false</WarningsAsErrors>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
<ItemGroup>
<Compile Include="Class1.cs" />
</ItemGroup>
</Project>

View File

@@ -45,10 +45,7 @@ type DumpedAssembly =
/// Dictionary of all type definitions in this assembly, keyed by their handle.
/// </summary>
TypeDefs :
IReadOnlyDictionary<
TypeDefinitionHandle,
WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
>
IReadOnlyDictionary<TypeDefinitionHandle, WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn>>
/// <summary>
/// Dictionary of all type references in this assembly, keyed by their handle.
@@ -67,7 +64,7 @@ type DumpedAssembly =
Methods :
IReadOnlyDictionary<
MethodDefinitionHandle,
WoofWare.PawPrint.MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter, TypeDefn>
WoofWare.PawPrint.MethodInfo<GenericParamFromMetadata, GenericParamFromMetadata, TypeDefn>
>
/// <summary>
@@ -78,7 +75,8 @@ type DumpedAssembly =
/// <summary>
/// Dictionary of all field definitions in this assembly, keyed by their handle.
/// </summary>
Fields : IReadOnlyDictionary<FieldDefinitionHandle, WoofWare.PawPrint.FieldInfo<FakeUnit, TypeDefn>>
Fields :
IReadOnlyDictionary<FieldDefinitionHandle, WoofWare.PawPrint.FieldInfo<GenericParamFromMetadata, TypeDefn>>
/// <summary>
/// The entry point method of the assembly, if one exists.
@@ -146,10 +144,7 @@ type DumpedAssembly =
/// Internal lookup for type definitions by namespace and name.
/// </summary>
_TypeDefsLookup :
ImmutableDictionary<
string * string,
WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
>
ImmutableDictionary<string * string, WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn>>
}
static member internal BuildExportedTypesLookup
@@ -205,7 +200,7 @@ type DumpedAssembly =
static member internal BuildTypeDefsLookup
(logger : ILogger)
(name : AssemblyName)
(typeDefs : WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> seq)
(typeDefs : WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn> seq)
=
let result = ImmutableDictionary.CreateBuilder ()
let keys = HashSet ()
@@ -236,7 +231,7 @@ type DumpedAssembly =
member this.TypeDef
(``namespace`` : string)
(name : string)
: WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn> option
: WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn> option
=
match this._TypeDefsLookup.TryGetValue ((``namespace``, name)) with
| false, _ -> None
@@ -428,8 +423,8 @@ module Assembly =
let rec resolveTypeRef
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
(referencedInAssembly : DumpedAssembly)
(target : TypeRef)
(genericArgs : ImmutableArray<TypeDefn>)
(target : TypeRef)
: TypeResolutionResult
=
match target.ResolutionScope with
@@ -467,7 +462,8 @@ module Assembly =
match targetType with
| [ t ] ->
let t = t |> TypeInfo.mapGeneric (fun _ param -> genericArgs.[param.SequenceNumber])
let t =
t |> TypeInfo.mapGeneric (fun (param, md) -> genericArgs.[param.SequenceNumber])
TypeResolutionResult.Resolved (assy, t)
| _ :: _ :: _ -> failwith $"Multiple matching type definitions! {nsPath} {target.Name}"
@@ -493,13 +489,13 @@ module Assembly =
| Some typeDef ->
let typeDef =
typeDef
|> TypeInfo.mapGeneric (fun _ param -> genericArgs.[param.SequenceNumber])
|> TypeInfo.mapGeneric (fun (param, md) -> genericArgs.[param.SequenceNumber])
TypeResolutionResult.Resolved (assy, typeDef)
| None ->
match assy.TypeRef ns name with
| Some typeRef -> resolveTypeRef assemblies assy typeRef genericArgs
| Some typeRef -> resolveTypeRef assemblies assy genericArgs typeRef
| None ->
match assy.ExportedType (Some ns) name with
@@ -536,7 +532,7 @@ module DumpedAssembly =
| Some (BaseTypeInfo.TypeRef r) ->
let assy = loadedAssemblies.[source.FullName]
// TODO: generics
match Assembly.resolveTypeRef loadedAssemblies assy assy.TypeRefs.[r] ImmutableArray.Empty with
match Assembly.resolveTypeRef loadedAssemblies assy ImmutableArray.Empty assy.TypeRefs.[r] with
| TypeResolutionResult.FirstLoadAssy _ ->
failwith
"seems pretty unlikely that we could have constructed this object without loading its base type"
@@ -564,3 +560,36 @@ module DumpedAssembly =
| None -> ResolvedBaseType.Object
go source baseTypeInfo
let typeInfoToTypeDefn
(bct : BaseClassTypes<DumpedAssembly>)
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
(ti : TypeInfo<TypeDefn, TypeDefn>)
: TypeDefn
=
ti
|> TypeInfo.toTypeDefn
bct
(fun n -> assemblies.[n.FullName])
_.Name
(fun x y -> x.TypeDefs.[y])
(fun x y ->
let r = x.TypeRefs.[y] |> Assembly.resolveTypeRef assemblies x ImmutableArray.Empty
match r with
| TypeResolutionResult.FirstLoadAssy assemblyReference -> failwith "todo"
| TypeResolutionResult.Resolved (dumpedAssembly, typeInfo) ->
let result =
typeInfo |> TypeInfo.mapGeneric (fun typeDef -> failwith "TODO: generics")
dumpedAssembly, result
)
let typeInfoToTypeDefn'
(bct : BaseClassTypes<DumpedAssembly>)
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
(ti : TypeInfo<GenericParamFromMetadata, TypeDefn>)
=
ti
|> TypeInfo.mapGeneric (fun (par, _) -> TypeDefn.GenericTypeParameter par.SequenceNumber)
|> typeInfoToTypeDefn bct assemblies

View File

@@ -0,0 +1,37 @@
namespace WoofWare.PawPrint
open System
open System.Reflection.Metadata
[<CustomEquality>]
[<CustomComparison>]
type ComparableFieldDefinitionHandle =
private
{
_Inner : FieldDefinitionHandle
}
override this.Equals other =
match other with
| :? ComparableFieldDefinitionHandle as other -> this._Inner.GetHashCode () = other._Inner.GetHashCode ()
| _ -> false
override this.GetHashCode () : int = this._Inner.GetHashCode ()
interface IComparable<ComparableFieldDefinitionHandle> with
member this.CompareTo (other : ComparableFieldDefinitionHandle) : int =
this._Inner.GetHashCode().CompareTo (other._Inner.GetHashCode ())
interface IComparable with
member this.CompareTo (other : obj) : int =
match other with
| :? ComparableFieldDefinitionHandle as other ->
(this :> IComparable<ComparableFieldDefinitionHandle>).CompareTo other
| _ -> failwith "invalid comparison"
static member Make (h : FieldDefinitionHandle) =
{
_Inner = h
}
member this.Get = this._Inner

View File

@@ -11,7 +11,7 @@ type ComparableTypeDefinitionHandle =
_Inner : TypeDefinitionHandle
}
override this.Equals (other) =
override this.Equals other =
match other with
| :? ComparableTypeDefinitionHandle as other -> this._Inner.GetHashCode () = other._Inner.GetHashCode ()
| _ -> false

View File

@@ -1,6 +1,6 @@
namespace WoofWare.PawPrint
open System
open System.Collections.Immutable
open System.Reflection
open System.Reflection.Metadata
@@ -16,8 +16,8 @@ module FakeUnit =
/// A type which has been concretised, runtime-representable, etc.
[<CustomEquality>]
[<CustomComparison>]
type ConcreteType<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :> IComparable<'typeGeneric>> =
[<NoComparison>]
type ConcreteType<'typeGeneric> =
private
{
/// Do not use this, because it's intended to be private; use the accessor `.Assembly : AssemblyName`
@@ -30,12 +30,26 @@ type ConcreteType<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :
/// Do not use this, because it's intended to be private; use the accessor `.Namespace` instead.
_Namespace : string
/// Do not use this, because it's intended to be private; use the accessor `.Generics` instead.
_Generics : 'typeGeneric list
_Generics : ImmutableArray<'typeGeneric>
}
override this.ToString () : string =
let basic = $"%s{this.Assembly.Name}.%s{this.Namespace}.%s{this.Name}"
let generics =
if this.Generics.IsEmpty then
""
else
this.Generics
|> Seq.map string
|> String.concat ", "
|> fun x -> "<" + x + ">"
basic + generics
member this.Assembly : AssemblyName = this._AssemblyName
member this.Definition : ComparableTypeDefinitionHandle = this._Definition
member this.Generics : 'typeGeneric list = this._Generics
member this.Generics : ImmutableArray<'typeGeneric> = this._Generics
member this.Name = this._Name
member this.Namespace = this._Namespace
@@ -50,72 +64,26 @@ type ConcreteType<'typeGeneric when 'typeGeneric : comparison and 'typeGeneric :
override this.GetHashCode () : int =
hash (this._AssemblyName.FullName, this._Definition, this._Generics)
interface IComparable<ConcreteType<'typeGeneric>> with
member this.CompareTo (other : ConcreteType<'typeGeneric>) : int =
let comp = this._AssemblyName.FullName.CompareTo other._AssemblyName.FullName
if comp <> 0 then
comp
else
let comp =
(this._Definition :> IComparable<ComparableTypeDefinitionHandle>).CompareTo other._Definition
if comp <> 0 then
comp
else
let thisGen = (this._Generics : 'typeGeneric list) :> IComparable<'typeGeneric list>
thisGen.CompareTo other._Generics
interface IComparable with
member this.CompareTo other =
match other with
| :? ConcreteType<'typeGeneric> as other ->
(this :> IComparable<ConcreteType<'typeGeneric>>).CompareTo other
| _ -> failwith "bad comparison"
[<RequireQualifiedAccess>]
module ConcreteType =
let make
(assemblyName : AssemblyName)
(defn : TypeDefinitionHandle)
(ns : string)
(name : string)
(defn : TypeDefinitionHandle)
(generics : TypeDefn list)
: ConcreteType<TypeDefn>
(genericParam : ImmutableArray<GenericParamFromMetadata>)
: ConcreteType<GenericParamFromMetadata>
=
{
_AssemblyName = assemblyName
_Definition = ComparableTypeDefinitionHandle.Make defn
_Name = name
_Namespace = ns
_Generics = generics
_Generics = genericParam
}
let make'
(assemblyName : AssemblyName)
(defn : TypeDefinitionHandle)
(ns : string)
(name : string)
(genericParamCount : int)
: ConcreteType<FakeUnit>
=
{
_AssemblyName = assemblyName
_Definition = ComparableTypeDefinitionHandle.Make defn
_Name = name
_Namespace = ns
_Generics = List.replicate genericParamCount FakeUnit.FakeUnit
}
let mapGeneric<'a, 'b
when 'a : comparison and 'a :> IComparable<'a> and 'b : equality and 'b : comparison and 'b :> IComparable<'b>>
(f : int -> 'a -> 'b)
(x : ConcreteType<'a>)
: ConcreteType<'b>
=
let generics = x._Generics |> List.mapi f
let mapGeneric<'a, 'b> (f : int -> 'a -> 'b) (x : ConcreteType<'a>) : ConcreteType<'b> =
let generics = x._Generics |> Seq.mapi f |> ImmutableArray.CreateRange
{
_AssemblyName = x._AssemblyName

View File

@@ -1,6 +1,5 @@
namespace WoofWare.PawPrint
open System
open System.Reflection
open System.Reflection.Metadata
@@ -8,8 +7,7 @@ open System.Reflection.Metadata
/// Represents detailed information about a field in a .NET assembly.
/// This is a strongly-typed representation of FieldDefinition from System.Reflection.Metadata.
/// </summary>
type FieldInfo<'typeGeneric, 'fieldGeneric when 'typeGeneric : comparison and 'typeGeneric :> IComparable<'typeGeneric>>
=
type FieldInfo<'typeGeneric, 'fieldGeneric> =
{
/// <summary>
/// The metadata token handle that uniquely identifies this field in the assembly.
@@ -34,8 +32,15 @@ type FieldInfo<'typeGeneric, 'fieldGeneric when 'typeGeneric : comparison and 't
/// literal, and other characteristics.
/// </summary>
Attributes : FieldAttributes
/// Static fields don't have an offset at all; also, instance fields which don't have an explicit offset (but
/// which of course do have one implicitly, which is most fields) are None here.
Offset : int option
}
member this.HasFieldRVA = this.Attributes.HasFlag FieldAttributes.HasFieldRVA
member this.IsStatic = this.Attributes.HasFlag FieldAttributes.Static
override this.ToString () : string =
$"%s{this.DeclaringType.Assembly.Name}.{this.DeclaringType.Name}.%s{this.Name}"
@@ -46,18 +51,26 @@ module FieldInfo =
(assembly : AssemblyName)
(handle : FieldDefinitionHandle)
(def : FieldDefinition)
: FieldInfo<FakeUnit, TypeDefn>
: FieldInfo<GenericParamFromMetadata, TypeDefn>
=
let name = mr.GetString def.Name
let fieldSig = def.DecodeSignature (TypeDefn.typeProvider assembly, ())
let declaringType = def.GetDeclaringType ()
let typeGenerics = mr.GetTypeDefinition(declaringType).GetGenericParameters().Count
let decType = mr.GetTypeDefinition (declaringType)
let decType = mr.GetTypeDefinition declaringType
let typeGenerics = decType.GetGenericParameters () |> GenericParameter.readAll mr
let declaringTypeNamespace = mr.GetString decType.Namespace
let declaringTypeName = mr.GetString decType.Name
let declaringType =
ConcreteType.make' assembly declaringType declaringTypeNamespace declaringTypeName typeGenerics
ConcreteType.make assembly declaringType declaringTypeNamespace declaringTypeName typeGenerics
let offset =
match def.GetOffset () with
| -1 -> None
| s -> Some s
{
Name = name
@@ -65,14 +78,10 @@ module FieldInfo =
DeclaringType = declaringType
Handle = handle
Attributes = def.Attributes
Offset = offset
}
let mapTypeGenerics<'a, 'b, 'field
when 'a :> IComparable<'a> and 'a : comparison and 'b :> IComparable<'b> and 'b : comparison>
(f : int -> 'a -> 'b)
(input : FieldInfo<'a, 'field>)
: FieldInfo<'b, 'field>
=
let mapTypeGenerics<'a, 'b, 'field> (f : int -> 'a -> 'b) (input : FieldInfo<'a, 'field>) : FieldInfo<'b, 'field> =
let declaringType = input.DeclaringType |> ConcreteType.mapGeneric f
{
@@ -81,5 +90,5 @@ module FieldInfo =
DeclaringType = declaringType
Signature = input.Signature
Attributes = input.Attributes
Offset = input.Offset
}

View File

@@ -0,0 +1,85 @@
namespace WoofWare.PawPrint
open System.Collections.Immutable
open System.Reflection
open System.Reflection.Metadata
type GenericVariance =
| Covariant
| Contravariant
type GenericConstraint =
| Reference
| NonNullableValue
type GenericParamMetadata =
{
Variance : GenericVariance option
Constraint : GenericConstraint option
RequiresParameterlessConstructor : bool
}
/// <summary>
/// Represents a generic type or method parameter definition.
/// Corresponds to GenericParameter in System.Reflection.Metadata.
/// </summary>
type GenericParameter =
{
/// <summary>The name of the generic parameter (e.g., 'T', 'TKey', etc.).</summary>
Name : string
/// <summary>
/// The zero-based index of the generic parameter in the generic parameter list.
/// For example, in Dictionary&lt;TKey, TValue&rt;, TKey has index 0 and TValue has index 1.
/// </summary>
SequenceNumber : int
}
type GenericParamFromMetadata = GenericParameter * GenericParamMetadata
[<RequireQualifiedAccess>]
module GenericParameter =
let readAll
(metadata : MetadataReader)
(param : GenericParameterHandleCollection)
: GenericParamFromMetadata ImmutableArray
=
param
|> Seq.map (fun param ->
let param = metadata.GetGenericParameter param
let requiresParamlessCons =
param.Attributes.HasFlag GenericParameterAttributes.DefaultConstructorConstraint
let constr =
if param.Attributes.HasFlag GenericParameterAttributes.NotNullableValueTypeConstraint then
Some GenericConstraint.NonNullableValue
elif param.Attributes.HasFlag GenericParameterAttributes.ReferenceTypeConstraint then
Some GenericConstraint.Reference
else
None
let variance =
if param.Attributes.HasFlag GenericParameterAttributes.Contravariant then
Some GenericVariance.Contravariant
elif param.Attributes.HasFlag GenericParameterAttributes.Covariant then
Some GenericVariance.Covariant
else
None
let md =
{
Variance = variance
Constraint = constr
RequiresParameterlessConstructor = requiresParamlessCons
}
let p =
{
Name = metadata.GetString param.Name
SequenceNumber = param.Index
}
p, md
)
|> ImmutableArray.CreateRange

View File

@@ -0,0 +1,22 @@
namespace WoofWare.PawPrint
open System.Collections.Immutable
[<RequireQualifiedAccess>]
module internal ImmutableArray =
let inline map ([<InlineIfLambda>] f : 'a -> 'b) (arr : ImmutableArray<'a>) : ImmutableArray<'b> =
let b = ImmutableArray.CreateBuilder ()
for i in arr do
b.Add (f i)
b.ToImmutable ()
let inline mapi ([<InlineIfLambda>] f : int -> 'a -> 'b) (arr : ImmutableArray<'a>) : ImmutableArray<'b> =
let b = ImmutableArray.CreateBuilder ()
for i = 0 to arr.Length - 1 do
b.Add (f i arr.[i])
b.ToImmutable ()

View File

@@ -52,40 +52,6 @@ module Parameter =
result.ToImmutable ()
/// <summary>
/// Represents a generic type or method parameter definition.
/// Corresponds to GenericParameter in System.Reflection.Metadata.
/// </summary>
type GenericParameter =
{
/// <summary>The name of the generic parameter (e.g., 'T', 'TKey', etc.).</summary>
Name : string
/// <summary>
/// The zero-based index of the generic parameter in the generic parameter list.
/// For example, in Dictionary&lt;TKey, TValue&rt;, TKey has index 0 and TValue has index 1.
/// </summary>
SequenceNumber : int
}
[<RequireQualifiedAccess>]
module GenericParameter =
let readAll
(metadata : MetadataReader)
(param : GenericParameterHandleCollection)
: GenericParameter ImmutableArray
=
param
|> Seq.map (fun param ->
let param = metadata.GetGenericParameter param
{
Name = metadata.GetString param.Name
SequenceNumber = param.Index
}
)
|> ImmutableArray.CreateRange
type ExceptionOffset =
{
TryLength : int
@@ -168,8 +134,7 @@ module MethodInstructions =
/// Represents detailed information about a method in a .NET assembly.
/// This is a strongly-typed representation of MethodDefinition from System.Reflection.Metadata.
/// </summary>
type MethodInfo<'typeGenerics, 'methodGenerics, 'methodVars
when 'typeGenerics :> IComparable<'typeGenerics> and 'typeGenerics : comparison> =
type MethodInfo<'typeGenerics, 'methodGenerics, 'methodVars> =
{
/// <summary>
/// The type that declares this method, along with its assembly information.
@@ -267,14 +232,13 @@ module MethodInfo =
| con -> failwith $"TODO: {con}"
)
let mapTypeGenerics<'a, 'b, 'methodGen, 'vars
when 'a :> IComparable<'a> and 'a : comparison and 'b : comparison and 'b :> IComparable<'b>>
(f : int -> 'a -> 'b)
let mapTypeGenerics<'a, 'b, 'methodGen, 'vars>
(f : 'a -> 'b)
(m : MethodInfo<'a, 'methodGen, 'vars>)
: MethodInfo<'b, 'methodGen, 'vars>
=
{
DeclaringType = m.DeclaringType |> ConcreteType.mapGeneric f
DeclaringType = m.DeclaringType |> ConcreteType.mapGeneric (fun _ -> f)
Handle = m.Handle
Name = m.Name
Instructions = m.Instructions
@@ -288,18 +252,20 @@ module MethodInfo =
IsStatic = m.IsStatic
}
let mapMethodGenerics<'a, 'b, 'vars, 'typeGen when 'typeGen :> IComparable<'typeGen> and 'typeGen : comparison>
let mapMethodGenerics<'a, 'b, 'vars, 'typeGen>
(f : int -> 'a -> 'b)
(m : MethodInfo<'typeGen, 'a, 'vars>)
: MethodInfo<'typeGen, 'b, 'vars>
=
let generics = m.Generics |> Seq.mapi f |> ImmutableArray.CreateRange
{
DeclaringType = m.DeclaringType
Handle = m.Handle
Name = m.Name
Instructions = m.Instructions
Parameters = m.Parameters
Generics = m.Generics |> Seq.mapi f |> ImmutableArray.CreateRange
Generics = generics
Signature = m.Signature
RawSignature = m.RawSignature
CustomAttributes = m.CustomAttributes
@@ -676,7 +642,7 @@ module MethodInfo =
(peReader : PEReader)
(metadataReader : MetadataReader)
(methodHandle : MethodDefinitionHandle)
: MethodInfo<FakeUnit, GenericParameter, TypeDefn> option
: MethodInfo<GenericParamFromMetadata, GenericParamFromMetadata, TypeDefn> option
=
let logger = loggerFactory.CreateLogger "MethodInfo"
let assemblyName = metadataReader.GetAssemblyDefinition().GetAssemblyName ()
@@ -717,7 +683,8 @@ module MethodInfo =
let declaringTypeName = metadataReader.GetString declaringDefn.Name
let declaringTypeGenericParams =
metadataReader.GetTypeDefinition(declaringType).GetGenericParameters().Count
metadataReader.GetTypeDefinition(declaringType).GetGenericParameters ()
|> GenericParameter.readAll metadataReader
let attrs =
let result = ImmutableArray.CreateBuilder ()
@@ -738,7 +705,7 @@ module MethodInfo =
GenericParameter.readAll metadataReader (methodDef.GetGenericParameters ())
let declaringType =
ConcreteType.make'
ConcreteType.make
assemblyName
declaringType
declaringTypeNamespace

View File

@@ -1,5 +1,6 @@
namespace WoofWare.PawPrint
open System
open System.Collections.Immutable
open System.Reflection
open System.Reflection.Metadata
@@ -9,6 +10,12 @@ type ConcreteTypeHandle =
| Byref of ConcreteTypeHandle
| Pointer of ConcreteTypeHandle
override this.ToString () =
match this with
| ConcreteTypeHandle.Byref b -> "&" + b.ToString ()
| ConcreteTypeHandle.Concrete i -> i.ToString ()
| ConcreteTypeHandle.Pointer i -> "*" + i.ToString ()
type AllConcreteTypes =
{
Mapping : Map<int, ConcreteType<ConcreteTypeHandle>>
@@ -46,7 +53,7 @@ module AllConcreteTypes =
let findExistingConcreteType
(concreteTypes : AllConcreteTypes)
(asm : AssemblyName, ns : string, name : string, generics : ConcreteTypeHandle list as key)
(asm : AssemblyName, ns : string, name : string, generics : ConcreteTypeHandle ImmutableArray)
: ConcreteTypeHandle option
=
concreteTypes.Mapping
@@ -75,10 +82,301 @@ module AllConcreteTypes =
toRet, newState
// Active patterns for matching concrete types
[<AutoOpen>]
module ConcreteActivePatterns =
/// Active pattern to match primitive types from concrete type handles
let (|ConcretePrimitive|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct when ct.Namespace = "System" && ct.Generics.IsEmpty ->
match ct.Name with
| "Int32" -> Some PrimitiveType.Int32
| "Int64" -> Some PrimitiveType.Int64
| "Int16" -> Some PrimitiveType.Int16
| "UInt32" -> Some PrimitiveType.UInt32
| "UInt64" -> Some PrimitiveType.UInt64
| "UInt16" -> Some PrimitiveType.UInt16
| "Byte" -> Some PrimitiveType.Byte
| "SByte" -> Some PrimitiveType.SByte
| "Single" -> Some PrimitiveType.Single
| "Double" -> Some PrimitiveType.Double
| "String" -> Some PrimitiveType.String
| "Boolean" -> Some PrimitiveType.Boolean
| "Char" -> Some PrimitiveType.Char
| "Object" -> Some PrimitiveType.Object
| "IntPtr" -> Some PrimitiveType.IntPtr
| "UIntPtr" -> Some PrimitiveType.UIntPtr
| "TypedReference" -> Some PrimitiveType.TypedReference
| _ -> None
| _ -> None
| _ -> None
/// Active pattern to match void type
let (|ConcreteVoid|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct when
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "Void"
&& ct.Generics.IsEmpty
->
Some ()
| _ -> None
| _ -> None
/// Active pattern to match any concrete type by assembly/namespace/name and generics
let (|ConcreteType|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct -> Some (ct.Assembly.Name, ct.Namespace, ct.Name, ct.Generics)
| None -> None
| _ -> None
let (|ConcreteChar|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct ->
if
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "Char"
&& ct.Generics.IsEmpty
then
Some ()
else
None
| None -> None
| _ -> None
let (|ConcreteRuntimeFieldHandle|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct when
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "RuntimeFieldHandle"
&& ct.Generics.IsEmpty
->
Some ()
| _ -> None
| _ -> None
let (|ConcreteNonGenericArray|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct when
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "Array"
&& ct.Generics.IsEmpty
->
Some ()
| _ -> None
| _ -> None
let (|ConcreteGenericArray|_|)
(concreteTypes : AllConcreteTypes)
(eltType : ConcreteTypeHandle)
(handle : ConcreteTypeHandle)
=
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct when
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "Array"
&& Seq.tryExactlyOne ct.Generics = Some eltType
->
Some ()
| _ -> None
| _ -> None
let (|ConcreteObj|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct ->
if
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "Object"
&& ct.Generics.IsEmpty
then
Some ()
else
None
| None -> None
| _ -> None
let (|ConcreteValueType|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct ->
if
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "ValueType"
&& ct.Generics.IsEmpty
then
Some ()
else
None
| None -> None
| _ -> None
let (|ConcreteBool|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct ->
if
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "Boolean"
&& ct.Generics.IsEmpty
then
Some ()
else
None
| None -> None
| _ -> None
let (|ConcreteString|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct ->
if
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "String"
&& ct.Generics.IsEmpty
then
Some ()
else
None
| None -> None
| _ -> None
let (|ConcreteDouble|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct ->
if
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "Double"
&& ct.Generics.IsEmpty
then
Some ()
else
None
| None -> None
| _ -> None
let (|ConcreteInt64|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct ->
if
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "Int64"
&& ct.Generics.IsEmpty
then
Some ()
else
None
| None -> None
| _ -> None
let (|ConcreteInt32|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct ->
if
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "Int32"
&& ct.Generics.IsEmpty
then
Some ()
else
None
| None -> None
| _ -> None
let (|ConcreteUInt32|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct ->
if
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "UInt32"
&& ct.Generics.IsEmpty
then
Some ()
else
None
| None -> None
| _ -> None
let (|ConcreteSingle|_|) (concreteTypes : AllConcreteTypes) (handle : ConcreteTypeHandle) : unit option =
match handle with
| ConcreteTypeHandle.Concrete id ->
match concreteTypes.Mapping |> Map.tryFind id with
| Some ct ->
if
ct.Assembly.Name = "System.Private.CoreLib"
&& ct.Namespace = "System"
&& ct.Name = "Single"
&& ct.Generics.IsEmpty
then
Some ()
else
None
| None -> None
| _ -> None
/// Active pattern to match byref types
let (|ConcreteByref|_|) (handle : ConcreteTypeHandle) =
match handle with
| ConcreteTypeHandle.Byref inner -> Some inner
| _ -> None
/// Active pattern to match pointer types
let (|ConcretePointer|_|) (handle : ConcreteTypeHandle) =
match handle with
| ConcreteTypeHandle.Pointer inner -> Some inner
| _ -> None
type IAssemblyLoad =
abstract LoadAssembly :
loadedAssemblies : ImmutableDictionary<string, DumpedAssembly> ->
referencedIn : AssemblyName ->
handle : AssemblyReferenceHandle ->
ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly
[<RequireQualifiedAccess>]
module TypeConcretization =
type ConcretizationContext =
type ConcretizationContext<'corelib> =
{
/// Types currently being processed (to detect cycles)
InProgress : ImmutableDictionary<AssemblyName * TypeDefn, ConcreteTypeHandle>
@@ -86,7 +384,7 @@ module TypeConcretization =
ConcreteTypes : AllConcreteTypes
/// For resolving type references
LoadedAssemblies : ImmutableDictionary<string, DumpedAssembly>
BaseTypes : BaseClassTypes<DumpedAssembly>
BaseTypes : BaseClassTypes<'corelib>
}
// Helper function to find existing types by assembly, namespace, name, and generics
@@ -95,7 +393,7 @@ module TypeConcretization =
(assembly : AssemblyName)
(ns : string)
(name : string)
(generics : ConcreteTypeHandle list)
(generics : ConcreteTypeHandle ImmutableArray)
: ConcreteTypeHandle option
=
concreteTypes.Mapping
@@ -117,18 +415,18 @@ module TypeConcretization =
(key : AssemblyName * string * string)
: ConcreteTypeHandle option
=
let (asm, ns, name) = key
findExistingType concreteTypes asm ns name []
let asm, ns, name = key
findExistingType concreteTypes asm ns name ImmutableArray.Empty
// Helper function to create and add a ConcreteType to the context
let private createAndAddConcreteType
(ctx : ConcretizationContext)
(ctx : ConcretizationContext<'corelib>)
(assembly : AssemblyName)
(definition : ComparableTypeDefinitionHandle)
(ns : string)
(name : string)
(generics : ConcreteTypeHandle list)
: ConcreteTypeHandle * ConcretizationContext
(generics : ConcreteTypeHandle ImmutableArray)
: ConcreteTypeHandle * ConcretizationContext<'corelib>
=
let concreteType =
{
@@ -150,12 +448,11 @@ module TypeConcretization =
// Helper function for assembly loading with retry pattern
let private loadAssemblyAndResolveTypeRef
(loadAssembly :
AssemblyName -> AssemblyReferenceHandle -> ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly)
(ctx : ConcretizationContext)
(loadAssembly : IAssemblyLoad)
(ctx : ConcretizationContext<'corelib>)
(currentAssembly : AssemblyName)
(typeRef : TypeRef)
: (DumpedAssembly * WoofWare.PawPrint.TypeInfo<_, _>) * ConcretizationContext
: (DumpedAssembly * WoofWare.PawPrint.TypeInfo<_, _>) * ConcretizationContext<'corelib>
=
let currentAssy =
match ctx.LoadedAssemblies.TryGetValue currentAssembly.FullName with
@@ -164,7 +461,7 @@ module TypeConcretization =
// First try to resolve without loading new assemblies
let resolutionResult =
Assembly.resolveTypeRef ctx.LoadedAssemblies currentAssy typeRef ImmutableArray.Empty
Assembly.resolveTypeRef ctx.LoadedAssemblies currentAssy ImmutableArray.Empty typeRef
match resolutionResult with
| TypeResolutionResult.Resolved (targetAssy, typeInfo) -> (targetAssy, typeInfo), ctx
@@ -172,7 +469,8 @@ module TypeConcretization =
// Need to load the assembly
match typeRef.ResolutionScope with
| TypeRefResolutionScope.Assembly assyRef ->
let newAssemblies, loadedAssy = loadAssembly currentAssembly assyRef
let newAssemblies, _ =
loadAssembly.LoadAssembly ctx.LoadedAssemblies currentAssembly assyRef
let newCtx =
{ ctx with
@@ -181,7 +479,7 @@ module TypeConcretization =
// Now try to resolve again with the loaded assembly
let resolutionResult2 =
Assembly.resolveTypeRef newCtx.LoadedAssemblies currentAssy typeRef ImmutableArray.Empty
Assembly.resolveTypeRef newCtx.LoadedAssemblies currentAssy ImmutableArray.Empty typeRef
match resolutionResult2 with
| TypeResolutionResult.Resolved (targetAssy, typeInfo) -> (targetAssy, typeInfo), newCtx
@@ -190,9 +488,9 @@ module TypeConcretization =
| _ -> failwith "Unexpected resolution scope"
let private concretizePrimitive
(ctx : ConcretizationContext)
(ctx : ConcretizationContext<'corelib>)
(prim : PrimitiveType)
: ConcreteTypeHandle * ConcretizationContext
: ConcreteTypeHandle * ConcretizationContext<'corelib>
=
// Get the TypeInfo for this primitive from BaseClassTypes
@@ -229,13 +527,13 @@ module TypeConcretization =
(ComparableTypeDefinitionHandle.Make typeInfo.TypeDefHandle)
typeInfo.Namespace
typeInfo.Name
[] // Primitives have no generic parameters
ImmutableArray.Empty // Primitives have no generic parameters
let private concretizeArray
(ctx : ConcretizationContext)
(ctx : ConcretizationContext<'corelib>)
(elementHandle : ConcreteTypeHandle)
(shape : 'a)
: ConcreteTypeHandle * ConcretizationContext
: ConcreteTypeHandle * ConcretizationContext<'corelib>
=
// Arrays are System.Array<T> where T is the element type
@@ -248,7 +546,7 @@ module TypeConcretization =
arrayTypeInfo.Assembly
arrayTypeInfo.Namespace
arrayTypeInfo.Name
[ elementHandle ]
(ImmutableArray.Create elementHandle)
with
| Some handle -> handle, ctx
| None ->
@@ -259,12 +557,12 @@ module TypeConcretization =
(ComparableTypeDefinitionHandle.Make arrayTypeInfo.TypeDefHandle)
arrayTypeInfo.Namespace
arrayTypeInfo.Name
[ elementHandle ] // Array<T> has one generic parameter
(ImmutableArray.Create elementHandle) // Array<T> has one generic parameter
let private concretizeOneDimArray
(ctx : ConcretizationContext)
(ctx : ConcretizationContext<'corelib>)
(elementHandle : ConcreteTypeHandle)
: ConcreteTypeHandle * ConcretizationContext
: ConcreteTypeHandle * ConcretizationContext<'corelib>
=
// One-dimensional arrays with lower bound 0 are also System.Array<T>
@@ -278,7 +576,7 @@ module TypeConcretization =
arrayTypeInfo.Assembly
arrayTypeInfo.Namespace
arrayTypeInfo.Name
[ elementHandle ]
(ImmutableArray.Create elementHandle)
with
| Some handle -> handle, ctx
| None ->
@@ -289,13 +587,13 @@ module TypeConcretization =
(ComparableTypeDefinitionHandle.Make arrayTypeInfo.TypeDefHandle)
arrayTypeInfo.Namespace
arrayTypeInfo.Name
[ elementHandle ] // Array<T> has one generic parameter
(ImmutableArray.Create elementHandle) // Array<T> has one generic parameter
let concretizeTypeDefinition
(ctx : ConcretizationContext)
(ctx : ConcretizationContext<'corelib>)
(assemblyName : AssemblyName)
(typeDefHandle : ComparableTypeDefinitionHandle)
: ConcreteTypeHandle * ConcretizationContext
: ConcreteTypeHandle * ConcretizationContext<'corelib>
=
// Look up the type definition in the assembly
@@ -315,19 +613,24 @@ module TypeConcretization =
typeInfo.Generics.Length
// Check if we've already concretized this type
match findExistingType ctx.ConcreteTypes assemblyName typeInfo.Namespace typeInfo.Name [] with
match findExistingType ctx.ConcreteTypes assemblyName typeInfo.Namespace typeInfo.Name ImmutableArray.Empty with
| Some handle -> handle, ctx
| None ->
// Create and add the concrete type (no generic arguments since it's not generic)
createAndAddConcreteType ctx assemblyName typeDefHandle typeInfo.Namespace typeInfo.Name [] // No generic parameters
createAndAddConcreteType
ctx
assemblyName
typeDefHandle
typeInfo.Namespace
typeInfo.Name
ImmutableArray.Empty // No generic parameters
let private concretizeTypeReference
(loadAssembly :
AssemblyName -> AssemblyReferenceHandle -> ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly)
(ctx : ConcretizationContext)
(loadAssembly : IAssemblyLoad)
(ctx : ConcretizationContext<'corelib>)
(currentAssembly : AssemblyName)
(typeRef : TypeRef)
: ConcreteTypeHandle * ConcretizationContext
: ConcreteTypeHandle * ConcretizationContext<'corelib>
=
// Use the helper to load assembly and resolve the type reference
let (targetAssy, typeInfo), ctx =
@@ -346,14 +649,13 @@ module TypeConcretization =
/// Concretize a type in a specific generic context
let rec concretizeType
(ctx : ConcretizationContext)
(loadAssembly :
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
(ctx : ConcretizationContext<DumpedAssembly>)
(loadAssembly : IAssemblyLoad)
(assembly : AssemblyName)
(typeGenerics : ConcreteTypeHandle ImmutableArray)
(methodGenerics : ConcreteTypeHandle ImmutableArray)
(typeGenerics : ImmutableArray<ConcreteTypeHandle>)
(methodGenerics : ImmutableArray<ConcreteTypeHandle>)
(typeDefn : TypeDefn)
: ConcreteTypeHandle * ConcretizationContext
: ConcreteTypeHandle * ConcretizationContext<DumpedAssembly>
=
let key = (assembly, typeDefn)
@@ -382,13 +684,13 @@ module TypeConcretization =
if index < typeGenerics.Length then
typeGenerics.[index], ctx
else
failwithf "Generic type parameter %d out of range" index
raise (IndexOutOfRangeException $"Generic type parameter %i{index}")
| TypeDefn.GenericMethodParameter index ->
if index < methodGenerics.Length then
methodGenerics.[index], ctx
else
failwithf "Generic method parameter %d out of range" index
raise (IndexOutOfRangeException $"Generic method parameter %i{index}")
| TypeDefn.GenericInstantiation (genericDef, args) ->
concretizeGenericInstantiation ctx loadAssembly assembly typeGenerics methodGenerics genericDef args
@@ -422,7 +724,12 @@ module TypeConcretization =
let voidTypeInfo = ctx.BaseTypes.Void
match
findExistingType ctx.ConcreteTypes voidTypeInfo.Assembly voidTypeInfo.Namespace voidTypeInfo.Name []
findExistingType
ctx.ConcreteTypes
voidTypeInfo.Assembly
voidTypeInfo.Namespace
voidTypeInfo.Name
ImmutableArray.Empty
with
| Some handle -> handle, ctx
| None ->
@@ -433,20 +740,19 @@ module TypeConcretization =
(ComparableTypeDefinitionHandle.Make voidTypeInfo.TypeDefHandle)
voidTypeInfo.Namespace
voidTypeInfo.Name
[] // Void has no generic parameters
ImmutableArray.Empty // Void has no generic parameters
| _ -> failwithf "TODO: Concretization of %A not implemented" typeDefn
and private concretizeGenericInstantiation
(ctx : ConcretizationContext)
(loadAssembly :
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
(ctx : ConcretizationContext<DumpedAssembly>)
(loadAssembly : IAssemblyLoad)
(assembly : AssemblyName)
(typeGenerics : ConcreteTypeHandle ImmutableArray)
(methodGenerics : ConcreteTypeHandle ImmutableArray)
(typeGenerics : ImmutableArray<ConcreteTypeHandle>)
(methodGenerics : ImmutableArray<ConcreteTypeHandle>)
(genericDef : TypeDefn)
(args : ImmutableArray<TypeDefn>)
: ConcreteTypeHandle * ConcretizationContext
: ConcreteTypeHandle * ConcretizationContext<DumpedAssembly>
=
// First, concretize all type arguments
let argHandles, ctxAfterArgs =
@@ -460,7 +766,7 @@ module TypeConcretization =
)
([], ctx)
let argHandles = argHandles |> List.rev
let argHandles = argHandles |> Seq.rev |> ImmutableArray.CreateRange
// Get the base type definition
let baseAssembly, baseTypeDefHandle, baseNamespace, baseName, ctxAfterArgs =
@@ -519,7 +825,8 @@ module TypeConcretization =
| false, _ ->
// Need to load the assembly
let newAssemblies, loadedAssy = loadAssembly assembly assyRef
let newAssemblies, loadedAssy =
loadAssembly.LoadAssembly ctx.LoadedAssemblies assembly assyRef
let ctxWithNewAssy =
{ ctxAfterArgs with
@@ -597,17 +904,16 @@ module Concretization =
/// Helper to concretize an array of types
let private concretizeTypeArray
(ctx : TypeConcretization.ConcretizationContext)
(loadAssembly :
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
(ctx : TypeConcretization.ConcretizationContext<DumpedAssembly>)
(loadAssembly : IAssemblyLoad)
(assembly : AssemblyName)
(typeArgs : ConcreteTypeHandle ImmutableArray)
(methodArgs : ConcreteTypeHandle ImmutableArray)
(typeArgs : ImmutableArray<ConcreteTypeHandle>)
(methodArgs : ImmutableArray<ConcreteTypeHandle>)
(types : ImmutableArray<TypeDefn>)
: ImmutableArray<ConcreteTypeHandle> * TypeConcretization.ConcretizationContext
: ImmutableArray<ConcreteTypeHandle> * TypeConcretization.ConcretizationContext<DumpedAssembly>
=
let handles = ImmutableArray.CreateBuilder (types.Length)
let handles = ImmutableArray.CreateBuilder types.Length
let mutable ctx = ctx
for i = 0 to types.Length - 1 do
@@ -621,14 +927,13 @@ module Concretization =
/// Helper to concretize a method signature
let private concretizeMethodSignature
(ctx : TypeConcretization.ConcretizationContext)
(loadAssembly :
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
(ctx : TypeConcretization.ConcretizationContext<DumpedAssembly>)
(loadAssembly : IAssemblyLoad)
(assembly : AssemblyName)
(typeArgs : ConcreteTypeHandle ImmutableArray)
(methodArgs : ConcreteTypeHandle ImmutableArray)
(typeArgs : ImmutableArray<ConcreteTypeHandle>)
(methodArgs : ImmutableArray<ConcreteTypeHandle>)
(signature : TypeMethodSignature<TypeDefn>)
: TypeMethodSignature<ConcreteTypeHandle> * TypeConcretization.ConcretizationContext
: TypeMethodSignature<ConcreteTypeHandle> * TypeConcretization.ConcretizationContext<DumpedAssembly>
=
// Concretize return type
@@ -643,7 +948,7 @@ module Concretization =
let handle, newCtx =
TypeConcretization.concretizeType ctx loadAssembly assembly typeArgs methodArgs paramType
paramHandles.Add (handle)
paramHandles.Add handle
ctx <- newCtx
let newSignature =
@@ -659,8 +964,7 @@ module Concretization =
/// Helper to ensure base type assembly is loaded
let rec private ensureBaseTypeAssembliesLoaded
(loadAssembly :
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
(loadAssembly : IAssemblyLoad)
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
(assyName : AssemblyName)
(baseTypeInfo : BaseTypeInfo option)
@@ -680,7 +984,7 @@ module Concretization =
| true, _ -> assemblies
| false, _ ->
// Need to load the assembly - pass the assembly that contains the reference
let newAssemblies, _ = loadAssembly assy.Name assyRef
let newAssemblies, _ = loadAssembly.LoadAssembly assemblies assy.Name assyRef
newAssemblies
| _ -> assemblies
| Some (BaseTypeInfo.TypeDef _)
@@ -690,13 +994,12 @@ module Concretization =
/// Concretize a method's signature and body
let concretizeMethod
(ctx : AllConcreteTypes)
(loadAssembly :
AssemblyName -> AssemblyReferenceHandle -> (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly))
(loadAssembly : IAssemblyLoad)
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
(baseTypes : BaseClassTypes<DumpedAssembly>)
(method : WoofWare.PawPrint.MethodInfo<TypeDefn, WoofWare.PawPrint.GenericParameter, TypeDefn>)
(typeArgs : ConcreteTypeHandle ImmutableArray)
(methodArgs : ConcreteTypeHandle ImmutableArray)
(method : WoofWare.PawPrint.MethodInfo<'ty, GenericParamFromMetadata, TypeDefn>)
(typeArgs : ImmutableArray<ConcreteTypeHandle>)
(methodArgs : ImmutableArray<ConcreteTypeHandle>)
: WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle> *
AllConcreteTypes *
ImmutableDictionary<string, DumpedAssembly>
@@ -826,10 +1129,9 @@ module Concretization =
// Map generics to handles
let genericHandles =
method.Generics
|> Seq.mapi (fun i _ -> methodArgs.[i])
|> ImmutableArray.CreateRange
|> ImmutableArray.map (fun (gp, md) -> methodArgs.[gp.SequenceNumber])
let concretizedMethod : MethodInfo<_, _, ConcreteTypeHandle> =
let concretizedMethod : MethodInfo<_, _, _> =
{
DeclaringType = concretizedDeclaringType
Handle = method.Handle
@@ -891,8 +1193,7 @@ module Concretization =
// Recursively convert generic arguments
let genericArgs =
concreteType.Generics
|> List.map (fun h -> concreteHandleToTypeDefn baseClassTypes h concreteTypes assemblies)
|> ImmutableArray.CreateRange
|> ImmutableArray.map (fun h -> concreteHandleToTypeDefn baseClassTypes h concreteTypes assemblies)
let baseDef =
TypeDefn.FromDefinition (concreteType.Definition, concreteType.Assembly.FullName, signatureTypeKind)

View File

@@ -149,6 +149,28 @@ type PrimitiveType =
| PrimitiveType.UIntPtr -> "uintptr"
| PrimitiveType.Object -> "obj"
[<RequireQualifiedAccess>]
module PrimitiveType =
let sizeOf (pt : PrimitiveType) : int =
match pt with
| PrimitiveType.Boolean -> 1
| PrimitiveType.Char -> 2
| PrimitiveType.SByte -> 1
| PrimitiveType.Byte -> 1
| PrimitiveType.Int16 -> 2
| PrimitiveType.UInt16 -> 2
| PrimitiveType.Int32 -> 4
| PrimitiveType.UInt32 -> 4
| PrimitiveType.Int64 -> 8
| PrimitiveType.UInt64 -> 8
| PrimitiveType.Single -> 4
| PrimitiveType.Double -> 8
| PrimitiveType.String -> 8
| PrimitiveType.TypedReference -> failwith "todo"
| PrimitiveType.IntPtr -> 8
| PrimitiveType.UIntPtr -> 8
| PrimitiveType.Object -> 8
type TypeDefn =
| PrimitiveType of PrimitiveType
// TODO: array shapes

View File

@@ -1,5 +1,6 @@
namespace WoofWare.PawPrint
open System
open System.Collections.Generic
open System.Collections.Immutable
open System.Reflection
@@ -19,6 +20,19 @@ type MethodImplParsed =
| MethodImplementation of MethodImplementationHandle
| MethodDefinition of MethodDefinitionHandle
type InterfaceImplementation =
{
/// TypeDefinition, TypeReference, or TypeSpecification
InterfaceHandle : MetadataToken
/// The assembly which InterfaceHandle is relative to
RelativeToAssembly : AssemblyName
}
type Layout =
| Default
| Custom of size : int * packingSize : int
/// <summary>
/// Represents detailed information about a type definition in a .NET assembly.
/// This is a strongly-typed representation of TypeDefinition from System.Reflection.Metadata.
@@ -34,7 +48,7 @@ type TypeInfo<'generic, 'fieldGeneric> =
/// <summary>
/// All methods defined within this type.
/// </summary>
Methods : WoofWare.PawPrint.MethodInfo<FakeUnit, WoofWare.PawPrint.GenericParameter, TypeDefn> list
Methods : WoofWare.PawPrint.MethodInfo<GenericParamFromMetadata, GenericParamFromMetadata, TypeDefn> list
/// <summary>
/// Method implementation mappings for this type, often used for interface implementations
@@ -45,7 +59,7 @@ type TypeInfo<'generic, 'fieldGeneric> =
/// <summary>
/// Fields defined in this type.
/// </summary>
Fields : WoofWare.PawPrint.FieldInfo<FakeUnit, 'fieldGeneric> list
Fields : WoofWare.PawPrint.FieldInfo<GenericParamFromMetadata, 'fieldGeneric> list
/// <summary>
/// The base type that this type inherits from, or None for types that don't have a base type
@@ -71,6 +85,8 @@ type TypeInfo<'generic, 'fieldGeneric> =
/// </summary>
TypeDefHandle : TypeDefinitionHandle
DeclaringType : TypeDefinitionHandle
/// <summary>
/// The assembly in which this type is defined.
/// </summary>
@@ -79,8 +95,25 @@ type TypeInfo<'generic, 'fieldGeneric> =
Generics : 'generic ImmutableArray
Events : EventDefn ImmutableArray
ImplementedInterfaces : InterfaceImplementation ImmutableArray
Layout : Layout
}
member this.IsInterface = this.TypeAttributes.HasFlag TypeAttributes.Interface
member this.IsNested =
[
TypeAttributes.NestedPublic
TypeAttributes.NestedPrivate
TypeAttributes.NestedFamily
TypeAttributes.NestedAssembly
TypeAttributes.NestedFamANDAssem
TypeAttributes.NestedFamORAssem
]
|> List.exists this.TypeAttributes.HasFlag
override this.ToString () =
$"%s{this.Assembly.Name}.%s{this.Namespace}.%s{this.Name}"
@@ -129,36 +162,47 @@ module TypeInfoCrate =
type BaseClassTypes<'corelib> =
{
Corelib : 'corelib
String : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
Boolean : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
Char : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
SByte : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
Byte : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
Int16 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
UInt16 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
Int32 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
UInt32 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
Int64 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
UInt64 : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
Single : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
Double : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
Array : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
Enum : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
ValueType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
DelegateType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
Object : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
RuntimeMethodHandle : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
RuntimeFieldHandle : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
RuntimeTypeHandle : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
RuntimeType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
Void : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
TypedReference : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
IntPtr : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
UIntPtr : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
String : TypeInfo<GenericParamFromMetadata, TypeDefn>
Boolean : TypeInfo<GenericParamFromMetadata, TypeDefn>
Char : TypeInfo<GenericParamFromMetadata, TypeDefn>
SByte : TypeInfo<GenericParamFromMetadata, TypeDefn>
Byte : TypeInfo<GenericParamFromMetadata, TypeDefn>
Int16 : TypeInfo<GenericParamFromMetadata, TypeDefn>
UInt16 : TypeInfo<GenericParamFromMetadata, TypeDefn>
Int32 : TypeInfo<GenericParamFromMetadata, TypeDefn>
UInt32 : TypeInfo<GenericParamFromMetadata, TypeDefn>
Int64 : TypeInfo<GenericParamFromMetadata, TypeDefn>
UInt64 : TypeInfo<GenericParamFromMetadata, TypeDefn>
Single : TypeInfo<GenericParamFromMetadata, TypeDefn>
Double : TypeInfo<GenericParamFromMetadata, TypeDefn>
Array : TypeInfo<GenericParamFromMetadata, TypeDefn>
Enum : TypeInfo<GenericParamFromMetadata, TypeDefn>
ValueType : TypeInfo<GenericParamFromMetadata, TypeDefn>
DelegateType : TypeInfo<GenericParamFromMetadata, TypeDefn>
Object : TypeInfo<GenericParamFromMetadata, TypeDefn>
RuntimeMethodHandle : TypeInfo<GenericParamFromMetadata, TypeDefn>
RuntimeFieldHandle : TypeInfo<GenericParamFromMetadata, TypeDefn>
RuntimeTypeHandle : TypeInfo<GenericParamFromMetadata, TypeDefn>
RuntimeFieldInfoStub : TypeInfo<GenericParamFromMetadata, TypeDefn>
RuntimeFieldHandleInternal : TypeInfo<GenericParamFromMetadata, TypeDefn>
RuntimeType : TypeInfo<GenericParamFromMetadata, TypeDefn>
Void : TypeInfo<GenericParamFromMetadata, TypeDefn>
TypedReference : TypeInfo<GenericParamFromMetadata, TypeDefn>
IntPtr : TypeInfo<GenericParamFromMetadata, TypeDefn>
UIntPtr : TypeInfo<GenericParamFromMetadata, TypeDefn>
}
[<RequireQualifiedAccess>]
module TypeInfo =
let rec fullName (get : TypeDefinitionHandle -> TypeInfo<_, _>) (ty : TypeInfo<'a, 'b>) =
if ty.IsNested then
let parent = get ty.DeclaringType |> fullName get
$"%s{parent}.{ty.Name}"
else if not (String.IsNullOrEmpty ty.Namespace) then
$"{ty.Namespace}.{ty.Name}"
else
ty.Name
let withGenerics<'a, 'b, 'field> (gen : 'b ImmutableArray) (t : TypeInfo<'a, 'field>) : TypeInfo<'b, 'field> =
{
Namespace = t.Namespace
@@ -170,13 +214,16 @@ module TypeInfo =
TypeAttributes = t.TypeAttributes
Attributes = t.Attributes
TypeDefHandle = t.TypeDefHandle
DeclaringType = t.DeclaringType
Assembly = t.Assembly
Generics = gen
Events = t.Events
ImplementedInterfaces = t.ImplementedInterfaces
Layout = t.Layout
}
let mapGeneric<'a, 'b, 'field> (f : int -> 'a -> 'b) (t : TypeInfo<'a, 'field>) : TypeInfo<'b, 'field> =
withGenerics (t.Generics |> Seq.mapi f |> ImmutableArray.CreateRange) t
let mapGeneric<'a, 'b, 'field> (f : 'a -> 'b) (t : TypeInfo<'a, 'field>) : TypeInfo<'b, 'field> =
withGenerics (t.Generics |> ImmutableArray.map f) t
let internal read
(loggerFactory : ILoggerFactory)
@@ -184,9 +231,10 @@ module TypeInfo =
(thisAssembly : AssemblyName)
(metadataReader : MetadataReader)
(typeHandle : TypeDefinitionHandle)
: TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
: TypeInfo<GenericParamFromMetadata, TypeDefn>
=
let typeDef = metadataReader.GetTypeDefinition typeHandle
let declaringType = typeDef.GetDeclaringType ()
let methods = typeDef.GetMethods ()
let methodImpls =
@@ -253,6 +301,28 @@ module TypeInfo =
result.ToImmutable ()
let interfaces =
let result = ImmutableArray.CreateBuilder ()
for i in typeDef.GetInterfaceImplementations () do
let impl = metadataReader.GetInterfaceImplementation i
{
InterfaceHandle = MetadataToken.ofEntityHandle impl.Interface
RelativeToAssembly = thisAssembly
}
|> result.Add
result.ToImmutable ()
let layout =
let l = typeDef.GetLayout ()
if l.IsDefault then
Layout.Default
else
Layout.Custom (size = l.Size, packingSize = l.PackingSize)
{
Namespace = ns
Name = name
@@ -266,6 +336,9 @@ module TypeInfo =
Assembly = thisAssembly
Generics = genericParams
Events = events
ImplementedInterfaces = interfaces
DeclaringType = declaringType
Layout = layout
}
let isBaseType<'corelib>
@@ -291,10 +364,10 @@ module TypeInfo =
let rec resolveBaseType<'corelib, 'generic, 'field>
(baseClassTypes : BaseClassTypes<'corelib>)
(sourceAssy : 'corelib)
(getName : 'corelib -> AssemblyName)
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic, 'field>)
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic, 'field>)
(sourceAssembly : AssemblyName)
(getTypeRef : 'corelib -> TypeReferenceHandle -> 'corelib * TypeInfo<'generic, 'field>)
(value : BaseTypeInfo option)
: ResolvedBaseType
=
@@ -304,40 +377,48 @@ module TypeInfo =
match value with
| BaseTypeInfo.TypeDef typeDefinitionHandle ->
match isBaseType baseClassTypes getName sourceAssembly typeDefinitionHandle with
match isBaseType baseClassTypes getName (getName sourceAssy) typeDefinitionHandle with
| Some x -> x
| None ->
let baseType = getTypeDef baseClassTypes.Corelib typeDefinitionHandle
resolveBaseType baseClassTypes getName getTypeDef getTypeRef sourceAssembly baseType.BaseType
resolveBaseType baseClassTypes sourceAssy getName getTypeDef getTypeRef baseType.BaseType
| BaseTypeInfo.TypeRef typeReferenceHandle ->
let typeRef = getTypeRef baseClassTypes.Corelib typeReferenceHandle
failwith $"{typeRef}"
let targetAssy, typeRef = getTypeRef sourceAssy typeReferenceHandle
match isBaseType baseClassTypes getName (getName targetAssy) typeRef.TypeDefHandle with
| Some x -> x
| None ->
let baseType = getTypeDef baseClassTypes.Corelib typeRef.TypeDefHandle
resolveBaseType baseClassTypes sourceAssy getName getTypeDef getTypeRef baseType.BaseType
| BaseTypeInfo.TypeSpec typeSpecificationHandle -> failwith "todo"
| BaseTypeInfo.ForeignAssemblyType (assemblyName, typeDefinitionHandle) ->
resolveBaseType
baseClassTypes
sourceAssy
getName
getTypeDef
getTypeRef
assemblyName
(Some (BaseTypeInfo.TypeDef typeDefinitionHandle))
let toTypeDefn
(corelib : BaseClassTypes<'corelib>)
(baseClassTypes : BaseClassTypes<'corelib>)
(assemblies : AssemblyName -> 'corelib)
(getName : 'corelib -> AssemblyName)
(getTypeDef : 'corelib -> TypeDefinitionHandle -> TypeInfo<'generic, 'field>)
(getTypeRef : 'corelib -> TypeReferenceHandle -> TypeInfo<'generic, 'field>)
(getTypeRef : 'corelib -> TypeReferenceHandle -> 'corelib * TypeInfo<'generic, 'field>)
(ty : TypeInfo<TypeDefn, TypeDefn>)
: TypeDefn
=
let stk =
match resolveBaseType corelib getName getTypeDef getTypeRef ty.Assembly ty.BaseType with
match resolveBaseType baseClassTypes (assemblies ty.Assembly) getName getTypeDef getTypeRef ty.BaseType with
| ResolvedBaseType.Enum
| ResolvedBaseType.ValueType -> SignatureTypeKind.ValueType
| ResolvedBaseType.Object
| ResolvedBaseType.Delegate -> SignatureTypeKind.Class
let defn =
// The only allowed construction of FromDefinition!
// All other constructions should use DumpedAssembly.typeInfoToTypeDefn.
TypeDefn.FromDefinition (ComparableTypeDefinitionHandle.Make ty.TypeDefHandle, ty.Assembly.FullName, stk)
if ty.Generics.IsEmpty then

View File

@@ -7,13 +7,16 @@
<ItemGroup>
<Compile Include="StringToken.fs" />
<Compile Include="ImmutableArray.fs" />
<Compile Include="Tokens.fs" />
<Compile Include="TypeRef.fs" />
<Compile Include="IlOp.fs" />
<Compile Include="CustomAttribute.fs" />
<Compile Include="GenericParameter.fs" />
<Compile Include="AssemblyReference.fs" />
<Compile Include="EventDefn.fs" />
<Compile Include="ComparableTypeDefinitionHandle.fs" />
<Compile Include="ComparableFieldDefinitionHandle.fs" />
<Compile Include="ComparableSignatureHeader.fs" />
<Compile Include="TypeDefn.fs" />
<Compile Include="ConcreteType.fs" />

View File

@@ -23,7 +23,7 @@ module LoggerFactory =
let makeTest () : (unit -> LogLine list) * ILoggerFactory =
// Shared sink for all loggers created by the factory.
let sink = ResizeArray ()
let isEnabled (logLevel : LogLevel) : bool = logLevel >= LogLevel.Information
let isEnabled (logLevel : LogLevel) : bool = logLevel >= LogLevel.Debug
let createLogger (category : string) : ILogger =
{ new ILogger with

View File

@@ -25,10 +25,9 @@ module MockEnv =
System_Threading_Monitor = System_Threading_MonitorMock.Empty
}
type TestCase =
type EndToEndTestCase =
{
FileName : string
ExpectedReturnCode : int
NativeImpls : NativeImpls
LocalVariablesOfMain : CliType list option
}

View File

@@ -20,11 +20,10 @@ module TestImpureCases =
FileName = "WriteLine.cs"
ExpectedReturnCode = 1
NativeImpls = NativeImpls.PassThru ()
LocalVariablesOfMain = [] |> Some
}
]
let cases : TestCase list =
let cases : EndToEndTestCase list =
[
{
FileName = "InstaQuit.cs"
@@ -47,12 +46,10 @@ module TestImpureCases =
ExecutionResult.Terminated (state, thread)
}
}
LocalVariablesOfMain = [] |> Some
}
]
[<TestCaseSource(nameof cases)>]
let ``Can evaluate C# files`` (case : TestCase) : unit =
let runTest (case : EndToEndTestCase) : unit =
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
let image = Roslyn.compile [ source ]
let messages, loggerFactory = LoggerFactory.makeTest ()
@@ -75,15 +72,6 @@ module TestImpureCases =
| ret -> failwith $"expected program to return an int, but it returned %O{ret}"
exitCode |> shouldEqual case.ExpectedReturnCode
let finalVariables =
terminalState.ThreadState.[terminatingThread].MethodState.LocalVariables
|> Seq.toList
match case.LocalVariablesOfMain with
| None -> ()
| Some expected -> finalVariables |> shouldEqual expected
with _ ->
for message in messages () do
System.Console.Error.WriteLine $"{message}"
@@ -91,33 +79,8 @@ module TestImpureCases =
reraise ()
[<TestCaseSource(nameof unimplemented)>]
[<Explicit "not yet implemented">]
let ``Can evaluate C# files, unimplemented`` (case : TestCase) : unit =
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
let image = Roslyn.compile [ source ]
let messages, loggerFactory = LoggerFactory.makeTest ()
[<Explicit>]
let ``Can evaluate C# files, unimplemented`` (case : EndToEndTestCase) = runTest case
let dotnetRuntimes =
DotnetRuntime.SelectForDll assy.Location |> ImmutableArray.CreateRange
use peImage = new MemoryStream (image)
try
let terminalState, terminatingThread =
Program.run loggerFactory (Some case.FileName) peImage dotnetRuntimes case.NativeImpls []
let exitCode =
match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with
| [] -> failwith "expected program to return a value, but it returned void"
| head :: _ ->
match head with
| EvalStackValue.Int32 i -> i
| ret -> failwith $"expected program to return an int, but it returned %O{ret}"
exitCode |> shouldEqual case.ExpectedReturnCode
with _ ->
for message in messages () do
System.Console.Error.WriteLine $"{message}"
reraise ()
[<TestCaseSource(nameof cases)>]
let ``Can evaluate C# files`` (case : EndToEndTestCase) = runTest case

View File

@@ -20,119 +20,105 @@ module TestPureCases =
FileName = "CrossAssemblyTypes.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "GenericEdgeCases.cs"
FileName = "OverlappingStructs.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "TestShl.cs"
FileName = "AdvancedStructLayout.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "TestShr.cs"
FileName = "InitializeArray.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "Threads.cs"
ExpectedReturnCode = 3
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = [] |> Some
}
{
FileName = "ComplexTryCatch.cs"
ExpectedReturnCode = 14
NativeImpls = NativeImpls.PassThru ()
LocalVariablesOfMain =
[
4
20
115
12
1
10
2
112
12
1111
42
99
25
50
123
20
35
5
11111
100001
]
|> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i))
|> Some
}
{
FileName = "ResizeArray.cs"
ExpectedReturnCode = 109
ExpectedReturnCode = 114
NativeImpls = MockEnv.make ()
}
{
FileName = "LdtokenField.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
}
{
FileName = "GenericEdgeCases.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
}
{
FileName = "UnsafeAs.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ] |> Some
}
]
let cases : TestCase list =
let cases : EndToEndTestCase list =
[
{
FileName = "NoOp.cs"
ExpectedReturnCode = 1
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 1) ] |> Some
}
{
FileName = "Sizeof.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
}
{
FileName = "Sizeof2.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
}
{
FileName = "Initobj.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
}
{
FileName = "TestShl.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
}
{
FileName = "TestShr.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
}
{
FileName = "StaticVariables.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "Ldind.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain =
[
// `failures`
CliType.Numeric (CliNumericType.Int32 0)
// Return value
CliType.Numeric (CliNumericType.Int32 0)
]
|> Some
}
{
FileName = "CustomDelegate.cs"
ExpectedReturnCode = 8
NativeImpls = MockEnv.make ()
LocalVariablesOfMain =
[
// filter
CliType.ObjectRef (Some (ManagedHeapAddress 2))
// result
CliType.OfBool true
// result, cloned for "if(result)" check
CliType.OfBool true
// ret
CliType.Numeric (CliNumericType.Int32 8)
]
|> Some
}
{
FileName = "ArgumentOrdering.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "BasicLock.cs"
@@ -143,97 +129,55 @@ module TestPureCases =
{ mock with
System_Threading_Monitor = System_Threading_Monitor.passThru
}
LocalVariablesOfMain =
[
// Four variables:
// locker
CliType.ObjectRef (Some (ManagedHeapAddress 2))
// a copy of locker, taken so that the contents of the implicit `finally` have a stable copy
CliType.ObjectRef (Some (ManagedHeapAddress 2))
// out param of `ReliableEnter`
CliType.OfBool true
// return value
CliType.Numeric (CliNumericType.Int32 1)
]
|> Some
}
{
FileName = "TriangleNumber.cs"
ExpectedReturnCode = 10
NativeImpls = MockEnv.make ()
LocalVariablesOfMain =
[
// answer
CliType.Numeric (CliNumericType.Int32 10)
// i
CliType.Numeric (CliNumericType.Int32 5)
// End-loop condition
CliType.OfBool false
// Ret
CliType.Numeric (CliNumericType.Int32 10)
]
|> Some
}
{
FileName = "ExceptionWithNoOpFinally.cs"
ExpectedReturnCode = 3
NativeImpls = MockEnv.make ()
LocalVariablesOfMain =
[
// Variable 1 is `x`, variable 2 is the implicit return value
4
3
]
|> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i))
|> Some
}
{
FileName = "ExceptionWithNoOpCatch.cs"
ExpectedReturnCode = 10
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = [ CliType.Numeric (CliNumericType.Int32 10) ] |> Some
}
{
FileName = "Floats.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "TryCatchWithThrowInBody.cs"
ExpectedReturnCode = 4
NativeImpls = MockEnv.make ()
LocalVariablesOfMain =
[
// one variable is x, one variable is the return value which also happens to have the same value
4
4
]
|> List.map (fun i -> CliType.Numeric (CliNumericType.Int32 i))
|> Some
}
{
FileName = "Ldelema.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "TypeConcretization.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "TestOr.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
LocalVariablesOfMain = None
}
{
FileName = "InterfaceDispatch.cs"
ExpectedReturnCode = 0
NativeImpls = MockEnv.make ()
}
]
[<TestCaseSource(nameof cases)>]
let ``Can evaluate C# files`` (case : TestCase) : unit =
let runTest (case : EndToEndTestCase) : unit =
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
let image = Roslyn.compile [ source ]
let messages, loggerFactory = LoggerFactory.makeTest ()
@@ -244,11 +188,12 @@ module TestPureCases =
use peImage = new MemoryStream (image)
try
let realResult = RealRuntime.executeWithRealRuntime [||] image
realResult.ExitCode |> shouldEqual case.ExpectedReturnCode
let terminalState, terminatingThread =
Program.run loggerFactory (Some case.FileName) peImage dotnetRuntimes case.NativeImpls []
let realResult = RealRuntime.executeWithRealRuntime [||] image
let exitCode =
match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with
| [] -> failwith "expected program to return a value, but it returned void"
@@ -259,15 +204,6 @@ module TestPureCases =
exitCode |> shouldEqual realResult.ExitCode
exitCode |> shouldEqual case.ExpectedReturnCode
let finalVariables =
terminalState.ThreadState.[terminatingThread].MethodState.LocalVariables
|> Seq.toList
match case.LocalVariablesOfMain with
| None -> ()
| Some expected -> finalVariables |> shouldEqual expected
with _ ->
for message in messages () do
System.Console.Error.WriteLine $"{message}"
@@ -275,33 +211,8 @@ module TestPureCases =
reraise ()
[<TestCaseSource(nameof unimplemented)>]
[<Explicit "not yet implemented">]
let ``Can evaluate C# files, unimplemented`` (case : TestCase) : unit =
let source = Assembly.getEmbeddedResourceAsString case.FileName assy
let image = Roslyn.compile [ source ]
let messages, loggerFactory = LoggerFactory.makeTest ()
[<Explicit>]
let ``Can evaluate C# files, unimplemented`` (case : EndToEndTestCase) = runTest case
let dotnetRuntimes =
DotnetRuntime.SelectForDll assy.Location |> ImmutableArray.CreateRange
use peImage = new MemoryStream (image)
try
let terminalState, terminatingThread =
Program.run loggerFactory (Some case.FileName) peImage dotnetRuntimes case.NativeImpls []
let exitCode =
match terminalState.ThreadState.[terminatingThread].MethodState.EvaluationStack.Values with
| [] -> failwith "expected program to return a value, but it returned void"
| head :: _ ->
match head with
| EvalStackValue.Int32 i -> i
| ret -> failwith $"expected program to return an int, but it returned %O{ret}"
exitCode |> shouldEqual case.ExpectedReturnCode
with _ ->
for message in messages () do
System.Console.Error.WriteLine $"{message}"
reraise ()
[<TestCaseSource(nameof cases)>]
let ``Can evaluate C# files`` (case : EndToEndTestCase) = runTest case

View File

@@ -4,9 +4,6 @@
<TargetFramework>net9.0</TargetFramework>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<IsTestProject>true</IsTestProject>
<TestingPlatformDotnetTestSupport>true</TestingPlatformDotnetTestSupport>
<EnableNUnitRunner>true</EnableNUnitRunner>
</PropertyGroup>
<ItemGroup>
@@ -19,31 +16,8 @@
<Compile Include="TestImpureCases.fs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="sourcesPure\BasicLock.cs" />
<EmbeddedResource Include="sourcesPure\Floats.cs" />
<EmbeddedResource Include="sourcesPure\NoOp.cs" />
<EmbeddedResource Include="sourcesPure\StaticVariables.cs" />
<EmbeddedResource Include="sourcesPure\Ldelema.cs" />
<EmbeddedResource Include="sourcesPure\ExceptionWithNoOpCatch.cs" />
<EmbeddedResource Include="sourcesPure\ExceptionWithNoOpFinally.cs" />
<EmbeddedResource Include="sourcesPure\TryCatchWithThrowInBody.cs" />
<EmbeddedResource Include="sourcesPure\ComplexTryCatch.cs" />
<EmbeddedResource Include="sourcesPure\TriangleNumber.cs" />
<EmbeddedResource Include="sourcesPure\Threads.cs" />
<EmbeddedResource Include="sourcesPure\ResizeArray.cs" />
<EmbeddedResource Include="sourcesPure\ArgumentOrdering.cs" />
<EmbeddedResource Include="sourcesPure\TestShl.cs" />
<EmbeddedResource Include="sourcesPure\TestShr.cs" />
<EmbeddedResource Include="sourcesPure\TestOr.cs" />
<EmbeddedResource Include="sourcesPure\CustomDelegate.cs" />
<EmbeddedResource Include="sourcesPure\Ldind.cs" />
<EmbeddedResource Include="sourcesPure\TypeConcretization.cs" />
<EmbeddedResource Include="sourcesPure\CrossAssemblyTypes.cs" />
<EmbeddedResource Include="sourcesPure\GenericEdgeCases.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="sourcesImpure\WriteLine.cs" />
<EmbeddedResource Include="sourcesImpure\InstaQuit.cs" />
<EmbeddedResource Include="sourcesPure\*.cs" />
<EmbeddedResource Include="sourcesImpure\*.cs" />
</ItemGroup>
<ItemGroup>
@@ -51,10 +25,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="NUnit" Version="4.4.0"/>
<PackageReference Include="NUnit3TestAdapter" Version="5.1.0"/>
<PackageReference Include="FsUnit" Version="7.1.1"/>
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageReference Include="NUnit" Version="4.3.2"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0"/>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.6" />
<PackageReference Include="WoofWare.DotnetRuntimeLocator" Version="0.3.2"/>

View File

@@ -0,0 +1,480 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
public class StructLayoutTestsAdvanced
{
// Test structs
[StructLayout(LayoutKind.Sequential)]
struct PointerTestStruct
{
public int A;
public byte B;
public short C;
public int D;
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct FixedBufferStruct
{
public int Header;
public fixed byte Buffer[64];
public int Footer;
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct NestedFixedStruct
{
public fixed int IntArray[4];
public fixed double DoubleArray[2];
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct MarshalStringStruct
{
public int Id;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string Name;
public double Value;
}
[StructLayout(LayoutKind.Sequential)]
struct MarshalArrayStruct
{
public int Count;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public int[] Values;
}
[StructLayout(LayoutKind.Sequential)]
struct BlittableStruct
{
public int X;
public double Y;
public long Z;
}
ref struct RefStruct
{
public int Value;
public Span<int> Span;
public RefStruct(int value)
{
Value = value;
Span = new Span<int>(new int[] { value, value * 2, value * 3 });
}
}
readonly struct ReadOnlyStruct
{
public readonly int X;
public readonly int Y;
public ReadOnlyStruct(int x, int y)
{
X = x;
Y = y;
}
public int Sum => X + Y;
}
readonly ref struct ReadOnlyRefStruct
{
public readonly int Value;
public readonly ReadOnlySpan<byte> Data;
public ReadOnlyRefStruct(int value, ReadOnlySpan<byte> data)
{
Value = value;
Data = data;
}
}
struct Generic<T> where T : struct
{
public T Value;
public int Index;
public Generic(T value, int index)
{
Value = value;
Index = index;
}
}
struct DoubleGeneric<T, U>
{
public T First;
public U Second;
}
interface IIndexable
{
int GetIndex();
void SetIndex(int value);
}
struct StructWithInterface : IIndexable
{
public int Index;
public string Data;
public int GetIndex() => Index;
public void SetIndex(int value) => Index = value;
}
interface IMutable
{
void Mutate();
}
struct MutableStruct : IMutable
{
public int Counter;
public void Mutate()
{
Counter++;
}
}
struct RefReturnStruct
{
public int A;
public int B;
public int C;
public static ref int GetRef(ref RefReturnStruct s, int index)
{
if (index == 0) return ref s.A;
if (index == 1) return ref s.B;
return ref s.C;
}
}
static unsafe int TestUnsafePointers()
{
var s = new PointerTestStruct { A = 0x12345678, B = 0xAB, C = 0x1234, D = unchecked((int)0xDEADBEEF) };
// Test sizeof
int size = sizeof(PointerTestStruct);
if (size == 0) return 1;
// Test pointer access
PointerTestStruct* ptr = &s;
if (ptr->A != 0x12345678) return 2;
if (ptr->B != 0xAB) return 3;
if (ptr->C != 0x1234) return 4;
if (ptr->D != unchecked((int)0xDEADBEEF)) return 5;
// Test pointer arithmetic and casting
byte* bytePtr = (byte*)ptr;
int* intPtr = (int*)bytePtr;
if (*intPtr != 0x12345678) return 6; // First int field
// Verify field offsets
int* dPtr = &(ptr->D);
int* aPtr = &(ptr->A);
long ptrDiff = (byte*)dPtr - (byte*)aPtr;
if (ptrDiff < 8) return 7; // D should be at least 8 bytes from A
// Test modification through pointer
ptr->A = 999;
if (s.A != 999) return 8;
return 0;
}
static unsafe int TestFixedBuffers()
{
var f = new FixedBufferStruct();
f.Header = 0xFEED;
f.Footer = 0xBEEF;
// Test fixed buffer access
for (int i = 0; i < 64; i++)
{
f.Buffer[i] = (byte)(i % 256);
}
if (f.Header != 0xFEED) return 10;
if (f.Footer != 0xBEEF) return 11;
// Verify buffer contents
for (int i = 0; i < 64; i++)
{
if (f.Buffer[i] != (byte)(i % 256)) return 12;
}
// Test pointer to fixed buffer
byte* bufPtr = f.Buffer;
bufPtr[0] = 255;
if (f.Buffer[0] != 255) return 13;
// Test nested fixed arrays
var n = new NestedFixedStruct();
n.IntArray[0] = 100;
n.IntArray[3] = 400;
n.DoubleArray[0] = 1.5;
n.DoubleArray[1] = 2.5;
if (n.IntArray[0] != 100) return 14;
if (n.IntArray[3] != 400) return 15;
if (Math.Abs(n.DoubleArray[0] - 1.5) > 0.0001) return 16;
if (Math.Abs(n.DoubleArray[1] - 2.5) > 0.0001) return 17;
return 0;
}
static unsafe int TestMarshaling()
{
// Test string marshaling
var ms = new MarshalStringStruct
{
Id = 42,
Name = "TestString",
Value = 3.14159
};
if (ms.Id != 42) return 20;
if (ms.Name != "TestString") return 21;
if (Math.Abs(ms.Value - 3.14159) > 0.00001) return 22;
// Test Marshal.SizeOf
int marshalSize = Marshal.SizeOf(typeof(MarshalStringStruct));
if (marshalSize == 0) return 23;
// Test array marshaling
var ma = new MarshalArrayStruct
{
Count = 5,
Values = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 }
};
if (ma.Count != 5) return 24;
if (ma.Values.Length != 8) return 25;
if (ma.Values[7] != 8) return 26;
// Test StructureToPtr and PtrToStructure
var blittable = new BlittableStruct { X = 100, Y = 200.5, Z = 300 };
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(BlittableStruct)));
try
{
Marshal.StructureToPtr(blittable, ptr, false);
var recovered = (BlittableStruct)Marshal.PtrToStructure(ptr, typeof(BlittableStruct));
if (recovered.X != 100) return 27;
if (Math.Abs(recovered.Y - 200.5) > 0.00001) return 28;
if (recovered.Z != 300) return 29;
}
finally
{
Marshal.FreeHGlobal(ptr);
}
return 0;
}
static int TestRefStructs()
{
// Test ref struct
var rs = new RefStruct(10);
if (rs.Value != 10) return 30;
if (rs.Span.Length != 3) return 31;
if (rs.Span[0] != 10) return 32;
if (rs.Span[1] != 20) return 33;
if (rs.Span[2] != 30) return 34;
// Modify through span
rs.Span[0] = 100;
if (rs.Span[0] != 100) return 35;
// Test readonly struct
var ros = new ReadOnlyStruct(5, 7);
if (ros.X != 5) return 36;
if (ros.Y != 7) return 37;
if (ros.Sum != 12) return 38;
// Verify immutability - create new instance
var ros2 = new ReadOnlyStruct(10, 20);
if (ros.X != 5) return 39; // Original should be unchanged
// Test readonly ref struct
byte[] data = { 1, 2, 3, 4 };
var rors = new ReadOnlyRefStruct(42, new ReadOnlySpan<byte>(data));
if (rors.Value != 42) return 40;
if (rors.Data.Length != 4) return 41;
if (rors.Data[3] != 4) return 42;
return 0;
}
static int TestGenerics()
{
// Test single generic parameter
var g1 = new Generic<int>(42, 1);
if (g1.Value != 42) return 50;
if (g1.Index != 1) return 51;
var g2 = new Generic<double>(3.14, 2);
if (Math.Abs(g2.Value - 3.14) > 0.00001) return 52;
if (g2.Index != 2) return 53;
// Test with custom struct
var inner = new ReadOnlyStruct(10, 20);
var g3 = new Generic<ReadOnlyStruct>(inner, 3);
if (g3.Value.X != 10) return 54;
if (g3.Value.Y != 20) return 55;
if (g3.Index != 3) return 56;
// Test double generic
var dg = new DoubleGeneric<int, string> { First = 100, Second = "test" };
if (dg.First != 100) return 57;
if (dg.Second != "test") return 58;
// Test with different type combinations
var dg2 = new DoubleGeneric<double, long> { First = 2.718, Second = long.MaxValue };
if (Math.Abs(dg2.First - 2.718) > 0.00001) return 59;
if (dg2.Second != long.MaxValue) return 60;
return 0;
}
static int TestByRefReturns()
{
var r = new RefReturnStruct { A = 10, B = 20, C = 30 };
// Test ref return
ref int refA = ref RefReturnStruct.GetRef(ref r, 0);
if (refA != 10) return 70;
// Modify through ref
refA = 100;
if (r.A != 100) return 71;
ref int refB = ref RefReturnStruct.GetRef(ref r, 1);
refB = 200;
if (r.B != 200) return 72;
ref int refC = ref RefReturnStruct.GetRef(ref r, 2);
refC = 300;
if (r.C != 300) return 73;
// Test ref local
ref int localRef = ref r.A;
localRef = 1000;
if (r.A != 1000) return 74;
// Test that ref points to actual field
localRef = 2000;
if (refA != 2000) return 75; // Both should see the change
return 0;
}
static int TestStructInterfaces()
{
// Test struct implementing interface
var s = new StructWithInterface { Index = 42, Data = "test" };
if (s.GetIndex() != 42) return 80;
s.SetIndex(100);
if (s.Index != 100) return 81;
// Test boxing to interface
IIndexable boxed = s; // Boxing occurs here
if (boxed.GetIndex() != 100) return 82;
// Modify through interface (modifies boxed copy)
boxed.SetIndex(200);
if (boxed.GetIndex() != 200) return 83;
if (s.Index != 100) return 84; // Original should be unchanged
// Test mutable interface
var m = new MutableStruct { Counter = 0 };
m.Mutate();
if (m.Counter != 1) return 85;
// Box to interface and mutate
IMutable boxedMutable = m; // Boxing
boxedMutable.Mutate();
if (m.Counter != 1) return 86; // Original unchanged
// Cast back to see boxed mutation
var unboxed = (MutableStruct)boxedMutable;
if (unboxed.Counter != 2) return 87;
// Direct interface call on boxed struct maintains state
boxedMutable.Mutate();
boxedMutable.Mutate();
var unboxed2 = (MutableStruct)boxedMutable;
if (unboxed2.Counter != 4) return 88;
return 0;
}
static unsafe int TestCombinedScenarios()
{
// Test generic with fixed buffer struct
var f = new FixedBufferStruct();
f.Header = 999;
f.Buffer[0] = 123;
f.Footer = 111;
var generic = new Generic<FixedBufferStruct>(f, 42);
if (generic.Value.Header != 999) return 90;
if (generic.Value.Buffer[0] != 123) return 91;
if (generic.Value.Footer != 111) return 92;
if (generic.Index != 42) return 93;
// Test marshaling with generic
var marshalable = new BlittableStruct { X = 10, Y = 20.0, Z = 30 };
var genericMarshal = new Generic<BlittableStruct>(marshalable, 5);
if (genericMarshal.Value.X != 10) return 94;
if (Math.Abs(genericMarshal.Value.Y - 20.0) > 0.00001) return 95;
if (genericMarshal.Value.Z != 30) return 96;
return 0;
}
public static int Main(string[] argv)
{
int result = 0;
unsafe
{
result = TestUnsafePointers();
if (result != 0) return result;
result = TestFixedBuffers();
if (result != 0) return result;
}
result = TestMarshaling();
if (result != 0) return result;
result = TestRefStructs();
if (result != 0) return result;
result = TestGenerics();
if (result != 0) return result;
result = TestByRefReturns();
if (result != 0) return result;
result = TestStructInterfaces();
if (result != 0) return result;
unsafe
{
result = TestCombinedScenarios();
if (result != 0) return result;
}
return 0; // All tests passed
}
}

View File

@@ -0,0 +1,19 @@
using System.Linq;
namespace HelloWorldApp
{
class Program
{
static int Main(string[] args)
{
int[] array = new[] { 1, 2, 3 };
if (array.Sum() != 6)
{
return 1;
}
return 0;
}
}
}

View File

@@ -0,0 +1,503 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
public class TestInitobj
{
// Simple struct with various primitive types
private struct SimpleStruct
{
public int IntField;
public bool BoolField;
public char CharField;
public double DoubleField;
public byte ByteField;
}
// Nested struct
private struct NestedStruct
{
public SimpleStruct Inner;
public int OuterField;
}
// Struct with arrays and references
private struct ComplexStruct
{
public object ObjectRef;
public string StringRef;
public int[] ArrayRef;
public int ValueField;
}
// Generic struct
private struct GenericStruct<T>
{
public T Value;
public int Count;
}
// Struct for field tests
private struct StructWithStructField
{
public SimpleStruct NestedField;
public int OtherField;
}
// Class with struct field for heap test
private class ClassWithStructField
{
public SimpleStruct StructField;
public int IntField;
}
// Test 1: Initialize simple struct with local variable
public static int Test1()
{
SimpleStruct s = new SimpleStruct
{
IntField = 42,
BoolField = true,
CharField = 'X',
DoubleField = 3.14,
ByteField = 255
};
// Verify initial values
if (s.IntField != 42) return 1;
if (s.BoolField != true) return 2;
if (s.CharField != 'X') return 3;
if (s.DoubleField != 3.14) return 4;
if (s.ByteField != 255) return 5;
// Use default to reset the struct (generates initobj)
s = default(SimpleStruct);
// Verify all fields are zeroed
if (s.IntField != 0) return 6;
if (s.BoolField != false) return 7;
if (s.CharField != '\0') return 8;
if (s.DoubleField != 0.0) return 9;
if (s.ByteField != 0) return 10;
return 0;
}
// Test 2: Initialize nested struct
public static int Test2()
{
NestedStruct n = new NestedStruct
{
Inner = new SimpleStruct
{
IntField = 100,
BoolField = true,
CharField = 'A',
DoubleField = 1.23,
ByteField = 128
},
OuterField = 999
};
// Verify initial values
if (n.Inner.IntField != 100) return 20;
if (n.OuterField != 999) return 21;
// Reset using default keyword (which should generate initobj)
n = default(NestedStruct);
// Verify all fields are zeroed
if (n.Inner.IntField != 0) return 22;
if (n.Inner.BoolField != false) return 23;
if (n.Inner.CharField != '\0') return 24;
if (n.Inner.DoubleField != 0.0) return 25;
if (n.Inner.ByteField != 0) return 26;
if (n.OuterField != 0) return 27;
return 0;
}
// Test 3: Initialize struct with reference types
public static int Test3()
{
ComplexStruct c = new ComplexStruct
{
ObjectRef = new object(),
StringRef = "Hello",
ArrayRef = new int[3],
ValueField = 42
};
c.ArrayRef[0] = 1;
c.ArrayRef[1] = 2;
c.ArrayRef[2] = 3;
// Verify initial values
if (c.ObjectRef == null) return 30;
if (c.StringRef != "Hello") return 31;
if (c.ArrayRef == null || c.ArrayRef.Length != 3) return 32;
if (c.ValueField != 42) return 33;
// Reset using default
c = default(ComplexStruct);
// Verify references are null and value is zero
if (c.ObjectRef != null) return 34;
if (c.StringRef != null) return 35;
if (c.ArrayRef != null) return 36;
if (c.ValueField != 0) return 37;
return 0;
}
// Test 4: Initialize generic struct
public static int Test4()
{
GenericStruct<int> gi = new GenericStruct<int>
{
Value = 123,
Count = 456
};
if (gi.Value != 123) return 40;
if (gi.Count != 456) return 41;
gi = default(GenericStruct<int>);
if (gi.Value != 0) return 42;
if (gi.Count != 0) return 43;
// Test with reference type
GenericStruct<string> gs = new GenericStruct<string>
{
Value = "Test",
Count = 789
};
if (gs.Value != "Test") return 44;
if (gs.Count != 789) return 45;
gs = default(GenericStruct<string>);
if (gs.Value != null) return 46;
if (gs.Count != 0) return 47;
return 0;
}
// Test 5: Initialize struct in array element using ref
public static int Test5()
{
SimpleStruct[] array = new SimpleStruct[3];
// Set values in first element
array[0].IntField = 111;
array[0].BoolField = true;
array[0].CharField = 'Z';
if (array[0].IntField != 111) return 50;
if (array[0].BoolField != true) return 51;
if (array[0].CharField != 'Z') return 52;
// Reset first element using default assignment
array[0] = default(SimpleStruct);
if (array[0].IntField != 0) return 53;
if (array[0].BoolField != false) return 54;
if (array[0].CharField != '\0') return 55;
// Also test with ref local
array[1].IntField = 222;
ref SimpleStruct secondElement = ref array[1];
secondElement = default(SimpleStruct);
if (array[1].IntField != 0) return 56;
return 0;
}
// Test 6: Initialize struct through method parameter
public static int Test6()
{
SimpleStruct s = new SimpleStruct
{
IntField = 200,
BoolField = true,
CharField = 'M',
DoubleField = 2.71,
ByteField = 64
};
ResetStruct(ref s);
if (s.IntField != 0) return 60;
if (s.BoolField != false) return 61;
if (s.CharField != '\0') return 62;
if (s.DoubleField != 0.0) return 63;
if (s.ByteField != 0) return 64;
return 0;
}
private static void ResetStruct(ref SimpleStruct s)
{
s = default(SimpleStruct);
}
// Test 7: Initialize multiple structs
public static int Test7()
{
SimpleStruct s1 = new SimpleStruct { IntField = 1 };
SimpleStruct s2 = new SimpleStruct { IntField = 2 };
SimpleStruct s3 = new SimpleStruct { IntField = 3 };
if (s1.IntField != 1) return 70;
if (s2.IntField != 2) return 71;
if (s3.IntField != 3) return 72;
s1 = default(SimpleStruct);
s2 = default(SimpleStruct);
s3 = default(SimpleStruct);
if (s1.IntField != 0) return 73;
if (s2.IntField != 0) return 74;
if (s3.IntField != 0) return 75;
return 0;
}
// Test 8: Initialize struct passed as argument (tests Argument case)
public static int Test8()
{
SimpleStruct s = new SimpleStruct
{
IntField = 333,
BoolField = true,
CharField = 'Q',
DoubleField = 4.56,
ByteField = 77
};
int result = InitializeArgumentStruct(ref s);
if (result != 0) return result;
// Verify struct was reset
if (s.IntField != 0) return 80;
if (s.BoolField != false) return 81;
if (s.CharField != '\0') return 82;
if (s.DoubleField != 0.0) return 83;
if (s.ByteField != 0) return 84;
return 0;
}
private static int InitializeArgumentStruct(ref SimpleStruct arg)
{
// Verify initial values
if (arg.IntField != 333) return 85;
if (arg.BoolField != true) return 86;
// Reset using default - this should use initobj on the argument
arg = default(SimpleStruct);
return 0;
}
// Test 9: Initialize struct field (tests Field case)
public static int Test9()
{
StructWithStructField container = new StructWithStructField
{
NestedField = new SimpleStruct
{
IntField = 444,
BoolField = true,
CharField = 'F',
DoubleField = 7.89,
ByteField = 88
},
OtherField = 555
};
// Verify initial values
if (container.NestedField.IntField != 444) return 90;
if (container.OtherField != 555) return 91;
// Reset the nested field using ref
ref SimpleStruct fieldRef = ref container.NestedField;
fieldRef = default(SimpleStruct);
// Verify nested field was reset but other field unchanged
if (container.NestedField.IntField != 0) return 92;
if (container.NestedField.BoolField != false) return 93;
if (container.NestedField.CharField != '\0') return 94;
if (container.NestedField.DoubleField != 0.0) return 95;
if (container.NestedField.ByteField != 0) return 96;
if (container.OtherField != 555) return 97;
return 0;
}
// Test 10: Initialize struct in heap-allocated object (tests Heap case)
public static int Test10()
{
ClassWithStructField obj = new ClassWithStructField
{
StructField = new SimpleStruct
{
IntField = 666,
BoolField = true,
CharField = 'H',
DoubleField = 9.99,
ByteField = 99
},
IntField = 777
};
// Verify initial values
if (obj.StructField.IntField != 666) return 100;
if (obj.IntField != 777) return 101;
// Reset the struct field
obj.StructField = default(SimpleStruct);
// Verify struct field was reset but other field unchanged
if (obj.StructField.IntField != 0) return 102;
if (obj.StructField.BoolField != false) return 103;
if (obj.StructField.CharField != '\0') return 104;
if (obj.StructField.DoubleField != 0.0) return 105;
if (obj.StructField.ByteField != 0) return 106;
if (obj.IntField != 777) return 107;
return 0;
}
// Test 11: Initialize struct through unsafe pointer manipulation
public static unsafe int Test11()
{
SimpleStruct s = new SimpleStruct
{
IntField = 888,
BoolField = true,
CharField = 'P',
DoubleField = 11.11,
ByteField = 111
};
// Get a pointer to the struct
SimpleStruct* ptr = &s;
// Initialize through pointer
*ptr = default(SimpleStruct);
// Verify all fields are zeroed
if (s.IntField != 0) return 110;
if (s.BoolField != false) return 111;
if (s.CharField != '\0') return 112;
if (s.DoubleField != 0.0) return 113;
if (s.ByteField != 0) return 114;
return 0;
}
// Test 12: Initialize struct through Unsafe.AsRef
public static int Test12()
{
SimpleStruct s = new SimpleStruct
{
IntField = 999,
BoolField = true,
CharField = 'U',
DoubleField = 12.34,
ByteField = 200
};
// Use Unsafe to get a ref and initialize it
ref SimpleStruct sRef = ref s;
sRef = default(SimpleStruct);
// Verify all fields are zeroed
if (s.IntField != 0) return 120;
if (s.BoolField != false) return 121;
if (s.CharField != '\0') return 122;
if (s.DoubleField != 0.0) return 123;
if (s.ByteField != 0) return 124;
return 0;
}
// Test 13: Initialize readonly struct
public static int Test13()
{
ReadonlyStruct ros = new ReadonlyStruct(100, true);
// Verify initial values through properties
if (ros.IntValue != 100) return 130;
if (ros.BoolValue != true) return 131;
// Reset using default
ros = default(ReadonlyStruct);
// Verify zeroed
if (ros.IntValue != 0) return 132;
if (ros.BoolValue != false) return 133;
return 0;
}
private readonly struct ReadonlyStruct
{
public readonly int IntValue;
public readonly bool BoolValue;
public ReadonlyStruct(int i, bool b)
{
IntValue = i;
BoolValue = b;
}
}
public static int Main(string[] argv)
{
var result = Test1();
if (result != 0) return result;
result = Test2();
if (result != 0) return result;
result = Test3();
if (result != 0) return result;
result = Test4();
if (result != 0) return result;
result = Test5();
if (result != 0) return result;
result = Test6();
if (result != 0) return result;
result = Test7();
if (result != 0) return result;
result = Test8();
if (result != 0) return result;
result = Test9();
if (result != 0) return result;
result = Test10();
if (result != 0) return result;
result = Test11();
if (result != 0) return result;
result = Test12();
if (result != 0) return result;
result = Test13();
if (result != 0) return result;
// All tests passed
return 0;
}
}

View File

@@ -0,0 +1,339 @@
using System;
public class InterfaceDispatchTests
{
public static int Main(string[] argv)
{
int result = 0;
result |= TestBasicInterface();
result |= TestExplicitImplementation() << 1;
result |= TestMultipleInterfaces() << 2;
result |= TestInterfaceInheritance() << 3;
result |= TestDiamondInheritance() << 4;
result |= TestGenericInterface() << 5;
result |= TestCovariantInterface() << 6;
result |= TestReimplementation() << 7;
// TODO
/*
result |= TestStructInterface() << 8;
result |= TestNullDispatch() << 9;
*/
result |= TestSharedMethodSignature() << 10;
return result;
}
// Test 1: Basic interface dispatch
static int TestBasicInterface()
{
ISimple obj = new SimpleImpl();
return obj.GetValue() == 42 ? 0 : 1;
}
// Test 2: Explicit interface implementation
static int TestExplicitImplementation()
{
var obj = new ExplicitImpl();
IExplicit iface = obj;
// Direct call should return 10, interface call should return 20
if (obj.GetValue() != 10) return 1;
if (iface.GetValue() != 20) return 1;
return 0;
}
// Test 3: Multiple interfaces
static int TestMultipleInterfaces()
{
var obj = new MultiImpl();
IFirst first = obj;
ISecond second = obj;
if (first.GetFirst() != 1) return 1;
if (second.GetSecond() != 2) return 1;
return 0;
}
// Test 4: Interface inheritance
static int TestInterfaceInheritance()
{
IDerived obj = new DerivedImpl();
IBase baseIface = obj;
if (baseIface.GetBase() != 100) return 1;
if (obj.GetDerived() != 200) return 1;
return 0;
}
// Test 5: Diamond inheritance pattern
static int TestDiamondInheritance()
{
var obj = new DiamondImpl();
ILeft left = obj;
IRight right = obj;
IDiamond diamond = obj;
if (left.GetValue() != 300) return 1;
if (right.GetValue() != 300) return 1;
if (diamond.GetDiamondValue() != 400) return 1;
return 0;
}
// Test 6: Generic interface dispatch
static int TestGenericInterface()
{
IGeneric<int> intObj = new GenericImpl<int>();
IGeneric<string> strObj = new GenericImpl<string>();
if (intObj.Process(5) != 3) return 1;
if (strObj.Process("test") != 5) return 1;
return 0;
}
// Test 7: Covariant interface dispatch
static int TestCovariantInterface()
{
ICovariant<string> strCov = new CovariantImpl();
ICovariant<object> objCov = strCov; // Covariance allows this
object result = objCov.Get();
if (!(result is string s && s == "covariant")) return 1;
return 0;
}
// Test 8: Interface reimplementation in derived class
static int TestReimplementation()
{
BaseClass baseObj = new DerivedClass();
IReimpl iface = baseObj;
// Should call derived implementation
if (iface.Method() != 500) return 1;
// Now test with base reference
BaseClass pureBase = new BaseClass();
IReimpl baseIface = pureBase;
if (baseIface.Method() != 600) return 1;
return 0;
}
// Test 9: Struct implementing interface
static int TestStructInterface()
{
StructImpl s = new StructImpl { Value = 700 };
ISimple boxed = s; // Boxing happens here
if (boxed.GetValue() != 700) return 1;
// Verify boxing created a copy
s.Value = 800;
if (boxed.GetValue() != 700) return 1; // Should still be 700
return 0;
}
// Test 10: Null dispatch (should throw)
static int TestNullDispatch()
{
ISimple nullRef = null;
try
{
nullRef.GetValue();
return 1; // Should have thrown
}
catch (NullReferenceException)
{
return 0; // Expected
}
}
// Test 11: Same method signature on multiple unrelated interfaces
static int TestSharedMethodSignature()
{
var obj = new SharedMethodImpl();
IReader reader = obj;
IScanner scanner = obj;
// Both interfaces should be satisfied by the single implementation
if (reader.Read() != "shared") return 1;
if (scanner.Read() != "shared") return 1;
// Also test with explicit + implicit combination
var mixed = new MixedSharedImpl();
IReader readerMixed = mixed;
IScanner scannerMixed = mixed;
if (readerMixed.Read() != "explicit-reader") return 1;
if (scannerMixed.Read() != "implicit-scanner") return 1;
if (mixed.Read() != "implicit-scanner") return 1; // Public method
return 0;
}
// Test interfaces and implementations
interface ISimple
{
int GetValue();
}
class SimpleImpl : ISimple
{
public int GetValue() => 42;
}
interface IExplicit
{
int GetValue();
}
class ExplicitImpl : IExplicit
{
public int GetValue() => 10;
int IExplicit.GetValue() => 20;
}
interface IFirst
{
int GetFirst();
}
interface ISecond
{
int GetSecond();
}
class MultiImpl : IFirst, ISecond
{
public int GetFirst() => 1;
public int GetSecond() => 2;
}
interface IBase
{
int GetBase();
}
interface IDerived : IBase
{
int GetDerived();
}
class DerivedImpl : IDerived
{
public int GetBase() => 100;
public int GetDerived() => 200;
}
interface ICommon
{
int GetValue();
}
interface ILeft : ICommon
{
}
interface IRight : ICommon
{
}
interface IDiamond : ILeft, IRight
{
int GetDiamondValue();
}
class DiamondImpl : IDiamond
{
public int GetValue() => 300;
public int GetDiamondValue() => 400;
}
interface IGeneric<T>
{
int Process(T value);
}
class GenericImpl<T> : IGeneric<T>
{
public int Process(T value)
{
if (typeof(T) == typeof(int))
{
return 3;
}
if (typeof(T) == typeof(string))
{
return 5;
}
return 0;
}
}
interface ICovariant<out T>
{
T Get();
}
class CovariantImpl : ICovariant<string>
{
public string Get() => "covariant";
}
interface IReimpl
{
int Method();
}
class BaseClass : IReimpl
{
public virtual int Method() => 600;
}
class DerivedClass : BaseClass, IReimpl
{
public override int Method() => 500;
}
struct StructImpl : ISimple
{
public int Value;
public int GetValue() => Value;
}
interface IReader
{
string Read();
}
interface IScanner
{
string Read(); // Same signature as IReader.Read()
}
// Single implicit implementation satisfies both interfaces
class SharedMethodImpl : IReader, IScanner
{
public string Read() => "shared";
}
// Mixed: explicit for one, implicit for the other
class MixedSharedImpl : IReader, IScanner
{
// Explicit implementation for IReader
string IReader.Read() => "explicit-reader";
// Implicit implementation (public) - satisfies IScanner
public string Read() => "implicit-scanner";
}
}

View File

@@ -39,7 +39,7 @@ unsafe class LdindTest
failures += TestTruncation();
// Test with managed pointers (ref)
// failures += TestManagedPointers();
failures += TestManagedPointers();
// Test Ldind.i (native int)
failures += TestLdindI();
@@ -325,7 +325,10 @@ unsafe class LdindTest
}
// Test with array element
int[] array = { 10, 20, 30 };
int[] array = new int[3];
array[0] = 10;
array[1] = 20;
array[2] = 30;
ref int element = ref array[1];
if (element != 20)
{

View File

@@ -0,0 +1,128 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace LdtokenFieldTest
{
class Program
{
// Various field types to test ldtoken with
public static int StaticIntField = 42;
public string InstanceStringField = "test";
private readonly double PrivateReadonlyField = 3.14;
internal decimal InternalField;
protected bool ProtectedField;
public static readonly DateTime StaticReadonlyField = DateTime.MinValue;
// Generic type fields
public GenericClass<int>.NestedClass<string> GenericField;
static int Main(string[] args)
{
int testsFailed = 0;
// Test 1: Static field via FieldInfo
FieldInfo staticField = typeof(Program).GetField(nameof(StaticIntField));
if (staticField == null || staticField.FieldType != typeof(int))
{
testsFailed++;
}
// Test 2: Instance field via FieldInfo
FieldInfo instanceField = typeof(Program).GetField(nameof(InstanceStringField));
if (instanceField == null || instanceField.FieldType != typeof(string))
{
testsFailed++;
}
// Test 3: Private field via FieldInfo with binding flags
FieldInfo privateField = typeof(Program).GetField("PrivateReadonlyField",
BindingFlags.NonPublic | BindingFlags.Instance);
if (privateField == null || privateField.FieldType != typeof(double))
{
testsFailed++;
}
// Test 4: Using RuntimeFieldHandle directly
RuntimeFieldHandle handle = staticField.FieldHandle;
FieldInfo fieldFromHandle = FieldInfo.GetFieldFromHandle(handle);
if (!ReferenceEquals(fieldFromHandle, staticField))
{
testsFailed++;
}
// Test 5: Field from generic type
Type genericType = typeof(GenericClass<>);
FieldInfo genericFieldInfo = genericType.GetField("GenericField");
if (genericFieldInfo == null)
{
testsFailed++;
}
// Test 6: Field from nested type
Type nestedType = typeof(OuterClass.InnerClass);
FieldInfo nestedField = nestedType.GetField("NestedField");
if (nestedField == null || nestedField.FieldType != typeof(int))
{
testsFailed++;
}
// Test 7: Field handle with generic context
Type constructedGeneric = typeof(GenericClass<int>);
FieldInfo constructedField = constructedGeneric.GetField("GenericField");
RuntimeFieldHandle genericHandle = constructedField.FieldHandle;
FieldInfo reconstructed = FieldInfo.GetFieldFromHandle(genericHandle, constructedGeneric.TypeHandle);
if (reconstructed.DeclaringType != constructedGeneric)
{
testsFailed++;
}
// Test 8: Struct field
Type structType = typeof(TestStruct);
FieldInfo structField = structType.GetField("StructField");
if (structField == null || structField.FieldType != typeof(long))
{
testsFailed++;
}
// Test 9: Volatile field
FieldInfo volatileField = typeof(VolatileFieldClass).GetField("VolatileField");
if (volatileField == null || !volatileField.GetRequiredCustomModifiers().Any(t => t == typeof(IsVolatile)))
{
testsFailed++;
}
return testsFailed;
}
}
// Supporting types for testing
public class GenericClass<T>
{
public T GenericField;
public class NestedClass<U>
{
public U NestedGenericField;
}
}
public class OuterClass
{
public class InnerClass
{
public int NestedField = 100;
}
}
public struct TestStruct
{
public long StructField;
}
public class VolatileFieldClass
{
public volatile int VolatileField;
}
}

View File

@@ -0,0 +1,364 @@
using System;
using System.Runtime.InteropServices;
public class StructLayoutTests
{
// Test structs with various layouts
[StructLayout(LayoutKind.Sequential)]
struct SequentialStruct
{
public int A;
public byte B;
public long C;
}
[StructLayout(LayoutKind.Explicit)]
struct ExplicitUnion
{
[FieldOffset(0)] public int AsInt;
[FieldOffset(0)] public float AsFloat;
[FieldOffset(0)] public byte Byte0;
[FieldOffset(1)] public byte Byte1;
[FieldOffset(2)] public byte Byte2;
[FieldOffset(3)] public byte Byte3;
}
[StructLayout(LayoutKind.Explicit, Size = 16)]
struct FixedSizeStruct
{
[FieldOffset(0)] public long First;
[FieldOffset(8)] public int Second;
[FieldOffset(12)] public short Third;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct PackedStruct
{
public byte A;
public int B;
public byte C;
}
[StructLayout(LayoutKind.Auto)]
struct AutoLayoutStruct
{
public int X;
public string Y;
public double Z;
}
[StructLayout(LayoutKind.Explicit)]
struct NestedUnion
{
[FieldOffset(0)] public ExplicitUnion Inner;
[FieldOffset(0)] public long AsLong;
[FieldOffset(4)] public int UpperInt;
}
[StructLayout(LayoutKind.Explicit)]
struct LargeUnion
{
[FieldOffset(0)] public long Long1;
[FieldOffset(8)] public long Long2;
[FieldOffset(0)] public double Double1;
[FieldOffset(8)] public double Double2;
[FieldOffset(0)] public decimal AsDecimal;
}
// Static fields for testing
static SequentialStruct staticSequential;
static ExplicitUnion staticUnion;
static FixedSizeStruct staticFixed;
// Instance fields for testing
class FieldContainer
{
public SequentialStruct instanceSequential;
public ExplicitUnion instanceUnion;
public PackedStruct instancePacked;
public NestedUnion instanceNested;
}
static int TestSequentialLayout()
{
var s = new SequentialStruct { A = 42, B = 255, C = long.MaxValue };
// Test field access
if (s.A != 42) return 1;
if (s.B != 255) return 2;
if (s.C != long.MaxValue) return 3;
// Test copy semantics
var s2 = s;
s2.A = 100;
if (s.A != 42) return 4; // Should be unchanged (value type)
if (s2.A != 100) return 5;
// Test static field storage
staticSequential = s;
if (staticSequential.A != 42) return 6;
if (staticSequential.C != long.MaxValue) return 7;
return 0;
}
static int TestExplicitUnion()
{
var u = new ExplicitUnion();
// Test overlapping int/float
u.AsInt = 0x3F800000; // IEEE 754 representation of 1.0f
if (Math.Abs(u.AsFloat - 1.0f) > 0.0001f) return 10;
// Test byte-level access
u.AsInt = 0x12345678;
bool isLittleEndian = BitConverter.IsLittleEndian;
if (isLittleEndian)
{
if (u.Byte0 != 0x78) return 11;
if (u.Byte1 != 0x56) return 12;
if (u.Byte2 != 0x34) return 13;
if (u.Byte3 != 0x12) return 14;
}
else
{
if (u.Byte0 != 0x12) return 11;
if (u.Byte1 != 0x34) return 12;
if (u.Byte2 != 0x56) return 13;
if (u.Byte3 != 0x78) return 14;
}
// Test static field
staticUnion = u;
if (staticUnion.AsInt != 0x12345678) return 15;
return 0;
}
static int TestFixedSizeStruct()
{
var f = new FixedSizeStruct { First = -1, Second = 42, Third = 1000 };
if (f.First != -1) return 20;
if (f.Second != 42) return 21;
if (f.Third != 1000) return 22;
// Test size is respected
int size = Marshal.SizeOf(typeof(FixedSizeStruct));
if (size != 16) return 23;
staticFixed = f;
if (staticFixed.Second != 42) return 24;
return 0;
}
static int TestPackedStruct()
{
var p = new PackedStruct { A = 1, B = 0x12345678, C = 2 };
if (p.A != 1) return 30;
if (p.B != 0x12345678) return 31;
if (p.C != 2) return 32;
// Packed struct should be 6 bytes (1 + 4 + 1)
int size = Marshal.SizeOf(typeof(PackedStruct));
if (size != 6) return 33;
return 0;
}
static int TestInstanceFields()
{
var container = new FieldContainer();
container.instanceSequential = new SequentialStruct { A = 111, B = 222, C = 333 };
if (container.instanceSequential.A != 111) return 40;
container.instanceUnion = new ExplicitUnion { AsInt = unchecked((int)0xDEADBEEF) };
if (container.instanceUnion.AsInt != unchecked((int)0xDEADBEEF)) return 41;
container.instancePacked = new PackedStruct { A = 10, B = 20, C = 30 };
if (container.instancePacked.B != 20) return 42;
container.instanceNested = new NestedUnion();
container.instanceNested.Inner.AsInt = 100;
if (container.instanceNested.Inner.AsInt != 100) return 43;
return 0;
}
static int TestStructPassing()
{
var s = new SequentialStruct { A = 500, B = 50, C = 5005 };
int result = ProcessSequential(s);
if (result != 555) return 50; // 500 + 50 + 5 (C % 1000)
var u = new ExplicitUnion { AsInt = 1000 };
u = TransformUnion(u);
if (u.AsInt != 2000) return 51;
return 0;
}
static int ProcessSequential(SequentialStruct s)
{
return s.A + s.B + (int)(s.C % 1000);
}
static ExplicitUnion TransformUnion(ExplicitUnion u)
{
u.AsInt *= 2;
return u;
}
static int TestNestedUnion()
{
var n = new NestedUnion();
n.Inner.AsInt = 0x12345678;
// Lower 32 bits should match Inner.AsInt
if ((n.AsLong & 0xFFFFFFFF) != 0x12345678) return 60;
// Modify upper int
n.UpperInt = unchecked((int)0xABCDEF00);
// Check both parts
if (n.Inner.AsInt != 0x12345678) return 61;
if (n.UpperInt != unchecked((int)0xABCDEF00)) return 62;
return 0;
}
static int TestLargeUnion()
{
var l = new LargeUnion();
// Test double/long overlap
l.Double1 = 1.0;
l.Double2 = 2.0;
// IEEE 754: 1.0 = 0x3FF0000000000000
if (l.Long1 != 0x3FF0000000000000) return 70;
// IEEE 754: 2.0 = 0x4000000000000000
if (l.Long2 != 0x4000000000000000) return 71;
// Test decimal overlap (decimal is 128 bits)
l.AsDecimal = 42m;
// Just verify it doesn't crash and maintains some structure
if (l.AsDecimal != 42m) return 72;
return 0;
}
static int TestAutoLayout()
{
// Auto layout structs can't use FieldOffset, but we can still test basic functionality
var a = new AutoLayoutStruct { X = 100, Y = "test", Z = 3.14159 };
if (a.X != 100) return 80;
if (a.Y != "test") return 81;
if (Math.Abs(a.Z - 3.14159) > 0.00001) return 82;
// Test copy
var a2 = a;
a2.X = 200;
if (a.X != 100) return 83; // Original should be unchanged
if (a2.X != 200) return 84;
return 0;
}
static int TestStructArray()
{
var arr = new ExplicitUnion[3];
arr[0].AsInt = 10;
arr[1].AsInt = 20;
arr[2].AsInt = 30;
if (arr[0].AsInt != 10) return 90;
if (arr[1].AsInt != 20) return 91;
if (arr[2].AsInt != 30) return 92;
// Modify through float view
arr[1].AsFloat = 2.5f;
if (Math.Abs(arr[1].AsFloat - 2.5f) > 0.0001f) return 93;
return 0;
}
static int TestBoxingUnboxing()
{
ExplicitUnion u = new ExplicitUnion { AsInt = 999 };
object boxed = u; // Box
ExplicitUnion unboxed = (ExplicitUnion)boxed; // Unbox
if (unboxed.AsInt != 999) return 100;
// Modify original, boxed should remain unchanged
u.AsInt = 111;
ExplicitUnion fromBoxed = (ExplicitUnion)boxed;
if (fromBoxed.AsInt != 999) return 101; // Should still be 999
return 0;
}
static int TestDefaultValues()
{
// Test that default struct initialization zeroes memory
var s = new SequentialStruct();
if (s.A != 0) return 110;
if (s.B != 0) return 111;
if (s.C != 0) return 112;
var u = new ExplicitUnion();
if (u.AsInt != 0) return 113;
if (u.AsFloat != 0.0f) return 114;
return 0;
}
public static int Main(string[] argv)
{
int result = 0;
result = TestSequentialLayout();
if (result != 0) return result;
result = TestExplicitUnion();
if (result != 0) return result;
result = TestFixedSizeStruct();
if (result != 0) return result;
result = TestPackedStruct();
if (result != 0) return result;
result = TestInstanceFields();
if (result != 0) return result;
result = TestStructPassing();
if (result != 0) return result;
result = TestNestedUnion();
if (result != 0) return result;
result = TestLargeUnion();
if (result != 0) return result;
result = TestAutoLayout();
if (result != 0) return result;
result = TestStructArray();
if (result != 0) return result;
result = TestBoxingUnboxing();
if (result != 0) return result;
result = TestDefaultValues();
if (result != 0) return result;
return 0; // All tests passed
}
}

View File

@@ -0,0 +1,118 @@
using System;
using System.Runtime.InteropServices;
unsafe public class Program
{
public struct SmallStruct
{
public byte Value;
}
public struct MediumStruct
{
public int MediumValue1;
public int MediumValue2;
}
public struct LargeStruct
{
public long LongValue1;
public long LongValue2;
public long LongValue3;
public long LongValue4;
}
public struct NestedStruct
{
public SmallStruct Small;
public MediumStruct Medium;
public int Extra;
}
[StructLayout(LayoutKind.Explicit)]
public struct UnionStruct
{
[FieldOffset(0)]
public int AsInt;
[FieldOffset(0)]
public float AsFloat;
}
public static int Main(string[] args)
{
// Test 1: Basic primitive types
if (sizeof(byte) != 1) return 1;
if (sizeof(sbyte) != 1) return 2;
if (sizeof(short) != 2) return 3;
if (sizeof(ushort) != 2) return 4;
if (sizeof(int) != 4) return 5;
if (sizeof(uint) != 4) return 6;
if (sizeof(long) != 8) return 7;
if (sizeof(ulong) != 8) return 8;
if (sizeof(float) != 4) return 9;
if (sizeof(double) != 8) return 10;
if (sizeof(char) != 2) return 11;
if (sizeof(bool) != 1) return 12;
// Test 2: Struct sizes
if (sizeof(SmallStruct) != 1) return 13;
if (sizeof(MediumStruct) != 8) return 14;
if (sizeof(LargeStruct) != 32) return 15;
// Test 3: Nested struct size
// SmallStruct (1) + padding (3) + MediumStruct (8) + int (4) = 16
if (sizeof(NestedStruct) != 16) return 16;
// Test 4: Union struct size
if (sizeof(UnionStruct) != 4) return 17;
// Test 5: Enum size (underlying type is int)
if (sizeof(DayOfWeek) != 4) return 18;
// Test 6: Pointer types
unsafe
{
if (sizeof(IntPtr) != sizeof(void*)) return 19;
if (sizeof(UIntPtr) != sizeof(void*)) return 20;
}
// Test 7: Using sizeof in expressions
int totalSize = sizeof(int) + sizeof(long) + sizeof(byte);
if (totalSize != 13) return 21;
// Test 8: Array element size calculation
int arrayElementSize = sizeof(MediumStruct);
int arraySize = arrayElementSize * 3;
if (arraySize != 24) return 22;
// Test 9: Conditional using sizeof
bool is32Bit = sizeof(IntPtr) == 4;
bool is64Bit = sizeof(IntPtr) == 8;
if (!is32Bit && !is64Bit) return 23;
if (is32Bit && is64Bit) return 24;
// Test 10: Sizeof in switch statement
int result = 0;
switch (sizeof(int))
{
case 1:
result = 1;
break;
case 2:
result = 2;
break;
case 4:
result = 4;
break;
case 8:
result = 8;
break;
default:
result = -1;
break;
}
if (result != 4) return 25;
return 0;
}
}

View File

@@ -0,0 +1,235 @@
using System;
using System.Runtime.InteropServices;
unsafe public class Program
{
// Test for empty struct (should be 1 byte, not 0)
public struct EmptyStruct
{
}
// Test for char alignment (should align to 2, not 1)
public struct CharStruct
{
public byte B;
public char C; // Should be at offset 2, not 1
}
// Test for end padding
public struct NeedsEndPadding
{
public int X;
public byte Y;
// Should pad to 8 bytes total (multiple of 4)
}
// Test Pack=1 (no padding)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PackedStruct
{
public byte B;
public int I; // At offset 1, not 4
public byte B2;
// Total 6 bytes, no padding
}
// Test Pack=2
[StructLayout(LayoutKind.Sequential, Pack = 2)]
public struct Pack2Struct
{
public byte B;
public int I; // At offset 2 (2-byte aligned, not 4)
public byte B2;
// Should pad to 8 bytes (multiple of 2)
}
// Test custom size smaller than natural size
[StructLayout(LayoutKind.Sequential, Size = 12)]
public struct CustomSizeSmaller
{
public long L1;
public long L2;
// Natural size is 16, but Size=12 is ignored (12 < 16)
}
// Test custom size larger than natural size
[StructLayout(LayoutKind.Sequential, Size = 20)]
public struct CustomSizeLarger
{
public long L;
// Natural size is 8, custom size 20 should win
}
// Test custom size not multiple of alignment
[StructLayout(LayoutKind.Sequential, Size = 15)]
public struct CustomSizeOdd
{
public long L;
// Size=15 should be honored even though not multiple of 8
}
// Test Pack=0 (means default, not 0)
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct Pack0Struct
{
public byte B;
public int I; // Should be at offset 4 (default packing)
}
// Test both Pack and Size
[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 10)]
public struct PackAndSize
{
public byte B;
public int I;
// Natural packed size is 5, custom size 10 should win
}
// Test explicit with custom Size
[StructLayout(LayoutKind.Explicit, Size = 10)]
public struct ExplicitWithSize
{
[FieldOffset(0)]
public int I;
[FieldOffset(2)]
public short S;
// Max offset+size is 4, but Size=10 should win
}
public struct SmallStruct
{
public byte Value;
}
public struct MediumStruct
{
public int MediumValue1;
public int MediumValue2;
}
public struct LargeStruct
{
public long LongValue1;
public long LongValue2;
public long LongValue3;
public long LongValue4;
}
public struct NestedStruct
{
public SmallStruct Small;
public MediumStruct Medium;
public int Extra;
}
[StructLayout(LayoutKind.Explicit)]
public struct UnionStruct
{
[FieldOffset(0)]
public int AsInt;
[FieldOffset(0)]
public float AsFloat;
}
public static int Main(string[] args)
{
// Test 1: Basic primitive types
if (sizeof(byte) != 1) return 1;
if (sizeof(sbyte) != 1) return 2;
if (sizeof(short) != 2) return 3;
if (sizeof(ushort) != 2) return 4;
if (sizeof(int) != 4) return 5;
if (sizeof(uint) != 4) return 6;
if (sizeof(long) != 8) return 7;
if (sizeof(ulong) != 8) return 8;
if (sizeof(float) != 4) return 9;
if (sizeof(double) != 8) return 10;
if (sizeof(char) != 2) return 11;
if (sizeof(bool) != 1) return 12;
// Test 2: Struct sizes
if (sizeof(SmallStruct) != 1) return 13;
if (sizeof(MediumStruct) != 8) return 14;
if (sizeof(LargeStruct) != 32) return 15;
// Test 3: Nested struct size
// SmallStruct (1) + padding (3) + MediumStruct (8) + int (4) = 16
if (sizeof(NestedStruct) != 16) return 16;
// Test 4: Union struct size
if (sizeof(UnionStruct) != 4) return 17;
// Test 5: Enum size (underlying type is int)
if (sizeof(DayOfWeek) != 4) return 18;
// Test 6: Empty struct (should be 1, not 0)
if (sizeof(EmptyStruct) != 1) return 19;
// Test 7: Char alignment
// byte (1) + padding (1) + char (2) = 4
if (sizeof(CharStruct) != 4) return 20;
// Test 8: End padding
// int (4) + byte (1) + padding (3) = 8
if (sizeof(NeedsEndPadding) != 8) return 21;
// Test 9: Pack=1 removes all padding
// byte (1) + int (4) + byte (1) = 6
if (sizeof(PackedStruct) != 6) return 22;
// Test 10: Pack=2
// byte (1) + padding (1) + int (4) + byte (1) + padding (1) = 8
if (sizeof(Pack2Struct) != 8) return 23;
// Test 11: Custom size smaller than natural (ignored)
if (sizeof(CustomSizeSmaller) != 16) return 24;
// Test 12: Custom size larger than natural (honored)
if (sizeof(CustomSizeLarger) != 20) return 25;
// Test 13: Custom size not multiple of alignment (honored)
if (sizeof(CustomSizeOdd) != 15) return 26;
// Test 14: Pack=0 means default packing
// byte (1) + padding (3) + int (4) = 8
if (sizeof(Pack0Struct) != 8) return 27;
// Test 15: Pack and Size together
// Natural packed: byte (1) + int (4) = 5, but Size=10
if (sizeof(PackAndSize) != 10) return 28;
// Test 16: Explicit with Size
// Max used is 4, but Size=10
if (sizeof(ExplicitWithSize) != 10) return 29;
// Test 17: Pointer types
unsafe
{
if (sizeof(IntPtr) != sizeof(void*)) return 30;
if (sizeof(UIntPtr) != sizeof(void*)) return 31;
}
// Test 18: Using sizeof in expressions
int totalSize = sizeof(int) + sizeof(long) + sizeof(byte);
if (totalSize != 13) return 32;
// Test 19: Array element size calculation
int arrayElementSize = sizeof(MediumStruct);
int arraySize = arrayElementSize * 3;
if (arraySize != 24) return 33;
// Test 20: Complex nested struct with Pack
// byte (1) + CharStruct (4) + byte (1) = 6
if (sizeof(PackedNested) != 6) return 34;
return 0;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct PackedNested
{
public byte B;
public CharStruct C;
public byte B2;
}
}

View File

@@ -0,0 +1,276 @@
using System;
using System.Runtime.CompilerServices;
public class TestUnsafeAs
{
private struct Int32Wrapper
{
public int Value;
}
private struct UInt32Wrapper
{
public uint Value;
}
private struct TwoInt16s
{
public short First;
public short Second;
}
private struct FourBytes
{
public byte B0;
public byte B1;
public byte B2;
public byte B3;
}
private enum TestEnum : int
{
Value1 = 0x12345678,
Value2 = -1
}
// Test 1: Int32 -> UInt32 reinterpretation
public static int Test1()
{
int original = -1;
ref uint reinterpreted = ref Unsafe.As<int, uint>(ref original);
if (reinterpreted != 0xFFFFFFFF)
return 1;
reinterpreted = 0x12345678;
if (original != 0x12345678)
return 2;
original = int.MinValue;
if (reinterpreted != 0x80000000)
return 3;
return 0;
}
// Test 2: Struct -> Struct reinterpretation
public static int Test2()
{
Int32Wrapper wrapper = new Int32Wrapper { Value = 0x01020304 };
ref FourBytes bytes = ref Unsafe.As<Int32Wrapper, FourBytes>(ref wrapper);
if (BitConverter.IsLittleEndian)
{
if (bytes.B0 != 0x04) return 10;
if (bytes.B1 != 0x03) return 11;
if (bytes.B2 != 0x02) return 12;
if (bytes.B3 != 0x01) return 13;
}
else
{
if (bytes.B0 != 0x01) return 14;
if (bytes.B1 != 0x02) return 15;
if (bytes.B2 != 0x03) return 16;
if (bytes.B3 != 0x04) return 17;
}
bytes.B0 = 0xFF;
int expectedValue = BitConverter.IsLittleEndian ? 0x010203FF : unchecked((int)0xFF020304);
if (wrapper.Value != expectedValue)
return 18;
return 0;
}
// Test 3: Int32 -> Two Int16s
public static int Test3()
{
int value = 0x12345678;
ref TwoInt16s halves = ref Unsafe.As<int, TwoInt16s>(ref value);
if (BitConverter.IsLittleEndian)
{
if (halves.First != unchecked((short)0x5678)) return 20;
if (halves.Second != 0x1234) return 21;
}
else
{
if (halves.First != 0x1234) return 22;
if (halves.Second != unchecked((short)0x5678)) return 23;
}
halves.First = -1;
int expectedValue = BitConverter.IsLittleEndian ? 0x1234FFFF : unchecked((int)0xFFFF5678);
if (value != expectedValue)
return 24;
return 0;
}
// Test 4: Array element reinterpretation
public static int Test4()
{
int[] intArray = new int[] { 0x01020304, 0x05060708 };
ref uint uintRef = ref Unsafe.As<int, uint>(ref intArray[0]);
if (uintRef != 0x01020304u)
return 30;
uintRef = 0xAABBCCDD;
if (intArray[0] != unchecked((int)0xAABBCCDD))
return 31;
if (intArray[1] != 0x05060708)
return 32;
return 0;
}
// Test 5: Bool -> Byte
public static int Test5()
{
bool trueValue = true;
bool falseValue = false;
ref byte trueByte = ref Unsafe.As<bool, byte>(ref trueValue);
ref byte falseByte = ref Unsafe.As<bool, byte>(ref falseValue);
if (trueByte != 1)
return 40;
if (falseByte != 0)
return 41;
// Modify through byte reference
trueByte = 0;
if (trueValue != false)
return 42;
falseByte = 1;
if (falseValue != true)
return 43;
return 0;
}
// Test 6: Char -> UInt16
public static int Test6()
{
char ch = 'A';
ref ushort asUInt16 = ref Unsafe.As<char, ushort>(ref ch);
if (asUInt16 != 65)
return 50;
asUInt16 = 0x03B1; // Greek lowercase alpha
if (ch != 'α')
return 51;
return 0;
}
// Test 7: Float -> Int32
public static int Test7()
{
float floatValue = 1.0f;
ref int intBits = ref Unsafe.As<float, int>(ref floatValue);
// IEEE 754: 1.0f = 0x3F800000
if (intBits != 0x3F800000)
return 60;
intBits = 0x40000000; // 2.0f in IEEE 754
if (floatValue != 2.0f)
return 61;
floatValue = -0.0f;
if (intBits != unchecked((int)0x80000000))
return 62;
return 0;
}
// Test 8: Double -> Int64
public static int Test8()
{
double doubleValue = 1.0;
ref long longBits = ref Unsafe.As<double, long>(ref doubleValue);
// IEEE 754: 1.0 = 0x3FF0000000000000
if (longBits != 0x3FF0000000000000L)
return 70;
longBits = 0x4000000000000000L; // 2.0 in IEEE 754
if (doubleValue != 2.0)
return 71;
return 0;
}
// Test 9: Enum -> Underlying type
public static int Test9()
{
TestEnum enumValue = TestEnum.Value1;
ref int underlying = ref Unsafe.As<TestEnum, int>(ref enumValue);
if (underlying != 0x12345678)
return 80;
underlying = -1;
if (enumValue != TestEnum.Value2)
return 81;
return 0;
}
// Test 10: Local variable reinterpretation
public static int Test10()
{
int local = unchecked((int)0xDEADBEEF);
ref uint localAsUint = ref Unsafe.As<int, uint>(ref local);
if (localAsUint != 0xDEADBEEF)
return 90;
localAsUint = 0xCAFEBABE;
if (local != unchecked((int)0xCAFEBABE))
return 91;
return 0;
}
public static int Main(string[] argv)
{
var result = Test1();
if (result != 0) return result;
result = Test2();
if (result != 0) return result;
result = Test3();
if (result != 0) return result;
result = Test4();
if (result != 0) return result;
result = Test5();
if (result != 0) return result;
result = Test6();
if (result != 0) return result;
result = Test7();
if (result != 0) return result;
result = Test8();
if (result != 0) return result;
result = Test9();
if (result != 0) return result;
result = Test10();
if (result != 0) return result;
// All tests passed
return 0;
}
}

View File

@@ -1,6 +1,5 @@
namespace WoofWare.PawPrint
open System.Collections.Immutable
open Microsoft.Extensions.Logging
open Microsoft.FSharp.Core
open WoofWare.PawPrint.ExternImplementations
@@ -55,18 +54,19 @@ module AbstractMachine =
// We've been instructed to run a delegate.
let delegateToRunAddr =
match instruction.Arguments.[0] with
| CliType.RuntimePointer (CliRuntimePointer.Managed (ManagedPointerSource.Heap addr))
| CliType.ObjectRef (Some addr) -> addr
| _ -> failwith "expected a managed object ref to delegate"
let delegateToRun = state.ManagedHeap.NonArrayObjects.[delegateToRunAddr]
let target =
match delegateToRun.Fields.["_target"] with
match delegateToRun |> AllocatedNonArrayObject.DereferenceField "_target" with
| CliType.ObjectRef addr -> addr
| x -> failwith $"TODO: delegate target wasn't an object ref: %O{x}"
let methodPtr =
match delegateToRun.Fields.["_methodPtr"] with
match delegateToRun |> AllocatedNonArrayObject.DereferenceField "_methodPtr" with
| CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer mi)) -> mi
| d -> failwith $"unexpectedly not a method pointer in delegate invocation: {d}"
@@ -103,13 +103,14 @@ module AbstractMachine =
let currentThreadState = state.ThreadState.[thread]
let state =
IlMachineState.callMethod
IlMachineStateExecution.callMethod
loggerFactory
baseClassTypes
None
constructing
false
false
false
methodGenerics
methodPtr
thread
@@ -125,42 +126,55 @@ module AbstractMachine =
targetType.Namespace,
targetType.Name,
instruction.ExecutingMethod.Name,
instruction.ExecutingMethod.RawSignature.ParameterTypes,
instruction.ExecutingMethod.RawSignature.ReturnType
instruction.ExecutingMethod.Signature.ParameterTypes,
instruction.ExecutingMethod.Signature.ReturnType
with
| "System.Private.CoreLib",
"System",
"Environment",
"GetProcessorCount",
[],
TypeDefn.PrimitiveType PrimitiveType.Int32 ->
ConcretePrimitive state.ConcreteTypes PrimitiveType.Int32 ->
let env = ISystem_Environment_Env.get impls
env.GetProcessorCount thread state
| "System.Private.CoreLib",
"System",
"Environment",
"_Exit",
[ TypeDefn.PrimitiveType PrimitiveType.Int32 ],
TypeDefn.Void ->
[ ConcretePrimitive state.ConcreteTypes PrimitiveType.Int32 ],
ConcreteVoid state.ConcreteTypes ->
let env = ISystem_Environment_Env.get impls
env._Exit thread state
| "System.Private.CoreLib",
"System.Threading",
"Monitor",
"ReliableEnter",
[ TypeDefn.PrimitiveType PrimitiveType.Object
TypeDefn.Byref (TypeDefn.PrimitiveType PrimitiveType.Boolean) ],
TypeDefn.Void ->
[ ConcretePrimitive state.ConcreteTypes PrimitiveType.Object
ConcreteByref (ConcretePrimitive state.ConcreteTypes PrimitiveType.Boolean) ],
ConcreteVoid state.ConcreteTypes ->
let env = ISystem_Threading_Monitor_Env.get impls
env.ReliableEnter thread state
| "System.Private.CoreLib",
"System.Threading",
"Monitor",
"Exit",
[ TypeDefn.PrimitiveType PrimitiveType.Object ],
TypeDefn.Void ->
[ ConcretePrimitive state.ConcreteTypes PrimitiveType.Object ],
ConcreteVoid state.ConcreteTypes ->
let env = ISystem_Threading_Monitor_Env.get impls
env.Exit thread state
| "System.Private.CoreLib",
"System",
"Type",
"GetField",
[ ConcretePrimitive state.ConcreteTypes PrimitiveType.String ; ty ],
ret ->
let ty = AllConcreteTypes.lookup ty state.ConcreteTypes |> Option.get
let ret = AllConcreteTypes.lookup ret state.ConcreteTypes |> Option.get
match ty.Namespace, ty.Name, ty.Generics.IsEmpty, ret.Namespace, ret.Name, ret.Generics.IsEmpty with
| "System.Reflection", "BindingFlags", true, "System.Reflection", "FieldInfo", true ->
failwith "TODO: GetField"
| _ -> failwith "unexpected signature for Type.GetField"
| assy, ns, typeName, methName, param, retType ->
failwith
$"TODO: tried to IL-interpret a method in {assy} {ns}.{typeName} named {methName} with no implementation; {param} -> {retType}"
@@ -208,5 +222,5 @@ module AbstractMachine =
|> ExecutionResult.Stepped
| IlOp.Switch immutableArray -> failwith "TODO: Switch unimplemented"
| IlOp.UnaryStringToken (unaryStringTokenIlOp, stringHandle) ->
UnaryStringTokenIlOp.execute baseClassTypes unaryStringTokenIlOp stringHandle state thread
UnaryStringTokenIlOp.execute loggerFactory baseClassTypes unaryStringTokenIlOp stringHandle state thread
|> ExecutionResult.Stepped

View File

@@ -14,6 +14,3 @@ type ManagedHeapAddress =
override this.ToString () : string =
match this with
| ManagedHeapAddress.ManagedHeapAddress i -> $"<object #%i{i}>"
[<Measure>]
type typeHandle

View File

@@ -41,7 +41,9 @@ type ManagedPointerSource =
| Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
| Heap of ManagedHeapAddress
| ArrayIndex of arr : ManagedHeapAddress * index : int
| Field of ManagedPointerSource * fieldName : string
| Null
| InterpretedAsType of ManagedPointerSource * ConcreteType<ConcreteTypeHandle>
override this.ToString () =
match this with
@@ -52,6 +54,8 @@ type ManagedPointerSource =
| ManagedPointerSource.Argument (source, method, var) ->
$"<argument %i{var} in method frame %i{method} of thread %O{source}>"
| ManagedPointerSource.ArrayIndex (arr, index) -> $"<index %i{index} of array %O{arr}>"
| ManagedPointerSource.Field (source, name) -> $"<field %s{name} of %O{source}>"
| ManagedPointerSource.InterpretedAsType (src, ty) -> $"<%O{src} as %s{ty.Namespace}.%s{ty.Name}>"
[<RequireQualifiedAccess>]
type UnsignedNativeIntSource =
@@ -63,7 +67,7 @@ type NativeIntSource =
| Verbatim of int64
| ManagedPointer of ManagedPointerSource
| FunctionPointer of MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>
| TypeHandlePtr of int64<typeHandle>
| TypeHandlePtr of ConcreteTypeHandle
override this.ToString () : string =
match this with
@@ -71,7 +75,7 @@ type NativeIntSource =
| NativeIntSource.ManagedPointer ptr -> $"<managed pointer {ptr}>"
| NativeIntSource.FunctionPointer methodDefinition ->
$"<pointer to {methodDefinition.Name} in {methodDefinition.DeclaringType.Assembly.Name}>"
| NativeIntSource.TypeHandlePtr ptr -> $"<type ID %i{ptr}>"
| NativeIntSource.TypeHandlePtr ptr -> $"<type ID %O{ptr}>"
[<RequireQualifiedAccess>]
module NativeIntSource =
@@ -113,29 +117,28 @@ type CliNumericType =
| Float32 of float32
| Float64 of float
type CliValueType =
private
| Bool of byte
/// A UTF-16 code unit, i.e. two bytes. We store the most significant one first.
| Char of byte * byte
| UInt8 of uint8
| UInt16 of uint16
| Int8 of int8
| Int16 of int16
| Float32 of float32
| Float64 of float
[<RequireQualifiedAccess>]
type CliRuntimePointerSource =
| LocalVariable of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
| Argument of sourceThread : ThreadId * methodFrame : int * whichVar : uint16
| Heap of ManagedHeapAddress
| ArrayIndex of arr : ManagedHeapAddress * index : int
| Null
static member SizeOf (t : CliNumericType) : int =
match t with
| CliNumericType.Int32 _ -> 4
| CliNumericType.Int64 _ -> 8
| CliNumericType.NativeInt _ -> 8
| CliNumericType.NativeFloat _ -> 8
| CliNumericType.Int8 _ -> 1
| CliNumericType.Int16 _ -> 2
| CliNumericType.UInt8 _ -> 1
| CliNumericType.UInt16 _ -> 2
| CliNumericType.Float32 _ -> 4
| CliNumericType.Float64 _ -> 8
type CliRuntimePointer =
| Unmanaged of int64
| Managed of CliRuntimePointerSource
| Managed of ManagedPointerSource
type SizeofResult =
{
Alignment : int
Size : int
}
/// This is the kind of type that can be stored in arguments, local variables, statics, array elements, fields.
type CliType =
@@ -151,15 +154,262 @@ type CliType =
| RuntimePointer of CliRuntimePointer
/// This is *not* a CLI type as such. I don't actually know its status. A value type is represented simply
/// as a concatenated list of its fields.
| ValueType of (string * CliType) list
| ValueType of CliValueType
/// In fact any non-zero value will do for True, but we'll use 1
static member OfBool (b : bool) = CliType.Bool (if b then 1uy else 0uy)
static member SizeOf (t : CliType) : SizeofResult =
match t with
| CliType.Numeric ty ->
let size = CliNumericType.SizeOf ty
static member OfChar (c : char) =
CliType.Char (byte (int c / 256), byte (int c % 256))
{
Size = size
Alignment = size
}
| CliType.Bool _ ->
{
Size = 1
Alignment = 1
}
| CliType.Char _ ->
{
Size = 2
Alignment = 2
}
| CliType.ObjectRef _ ->
{
Size = 8
Alignment = 8
}
| CliType.RuntimePointer _ ->
{
Size = 8
Alignment = 8
}
| CliType.ValueType vt -> CliValueType.SizeOf vt
static member OfManagedObject (ptr : ManagedHeapAddress) = CliType.ObjectRef (Some ptr)
and CliField =
{
Name : string
Contents : CliType
/// "None" for "no explicit offset specified"; we expect most offsets to be None.
Offset : int option
}
and private CliConcreteField =
{
Name : string
Contents : CliType
Offset : int
Size : int
Alignment : int
ConfiguredOffset : int option
}
static member ToCliField (this : CliConcreteField) : CliField =
{
Offset = this.ConfiguredOffset
Contents = this.Contents
Name = this.Name
}
and CliValueType =
private
{
_Fields : CliConcreteField list
Layout : Layout
}
static member private ComputeConcreteFields (layout : Layout) (fields : CliField list) : CliConcreteField list =
// Minimum size only matters for `sizeof` computation
let _minimumSize, packingSize =
match layout with
| Layout.Custom (size = size ; packingSize = packing) ->
size, if packing = 0 then DEFAULT_STRUCT_ALIGNMENT else packing
| Layout.Default -> 0, DEFAULT_STRUCT_ALIGNMENT
let seqFields, nonSeqFields =
fields |> List.partition (fun field -> field.Offset.IsNone)
match seqFields, nonSeqFields with
| [], [] -> []
| _ :: _, [] ->
// Sequential layout: compute offsets respecting alignment
let _, concreteFields =
((0, []), seqFields)
||> List.fold (fun (currentOffset, acc) field ->
let size = CliType.SizeOf field.Contents
let alignmentCap = min size.Alignment packingSize
let error = currentOffset % alignmentCap
let alignedOffset =
if error > 0 then
currentOffset + (alignmentCap - error)
else
currentOffset
let concreteField =
{
Name = field.Name
Contents = field.Contents
Offset = alignedOffset
Size = size.Size
Alignment = size.Alignment
ConfiguredOffset = field.Offset
}
alignedOffset + size.Size, concreteField :: acc
)
List.rev concreteFields
| [], _ :: _ ->
// Explicit layout: use provided offsets
nonSeqFields
|> List.map (fun field ->
let size = CliType.SizeOf field.Contents
{
Name = field.Name
Contents = field.Contents
Offset = field.Offset.Value
Size = size.Size
Alignment = size.Alignment
ConfiguredOffset = field.Offset
}
)
| _ :: _, _ :: _ -> failwith "unexpectedly mixed explicit and automatic layout of fields"
static member OfFields (layout : Layout) (f : CliField list) : CliValueType =
let fields = CliValueType.ComputeConcreteFields layout f
{
_Fields = fields
Layout = layout
}
static member AddField (f : CliField) (vt : CliValueType) : CliValueType =
// Recompute all fields with the new one added
// TODO: the existence of this function at all is rather dubious, but it's there
// at the moment to support delegate types
let allFields =
f
:: (vt._Fields
|> List.map (fun cf ->
{
Name = cf.Name
Contents = cf.Contents
Offset =
match vt.Layout with
| Layout.Default -> None
| Layout.Custom _ -> Some cf.Offset
}
))
{
_Fields = CliValueType.ComputeConcreteFields vt.Layout allFields
Layout = vt.Layout
}
static member DereferenceField (name : string) (f : CliValueType) : CliType =
// TODO: this is wrong, it doesn't account for overlapping fields
f._Fields |> List.find (fun f -> f.Name = name) |> _.Contents
static member SizeOf (vt : CliValueType) : SizeofResult =
let minimumSize, packingSize =
match vt.Layout with
| Layout.Custom (size = size ; packingSize = packing) ->
size, if packing = 0 then DEFAULT_STRUCT_ALIGNMENT else packing
| Layout.Default -> 0, DEFAULT_STRUCT_ALIGNMENT
if vt._Fields.IsEmpty then
{
Size = minimumSize
Alignment = 1
}
else
// Now we can just use the precomputed offsets and sizes
let finalOffset, alignment =
vt._Fields
|> List.fold
(fun (maxEnd, maxAlign) field ->
let fieldEnd = field.Offset + field.Size
let alignmentCap = min field.Alignment packingSize
max maxEnd fieldEnd, max maxAlign alignmentCap
)
(0, 0)
let error = finalOffset % alignment
let size =
if error = 0 then
finalOffset
else
finalOffset + (alignment - error)
{
Size = max size minimumSize
Alignment = alignment
}
static member WithFieldSet (field : string) (value : CliType) (cvt : CliValueType) : CliValueType =
let storageSize = CliType.SizeOf value
let targetField =
cvt._Fields
|> List.tryFind (fun f -> f.Name = field)
|> Option.defaultWith (fun () -> failwithf $"Field '%s{field}' not found")
if targetField.Size < storageSize.Size then
failwith "TODO: trying to store a value into a field that's too small to contain it"
// Identify all fields that overlap with the target field's memory range
let targetStart = targetField.Offset
let targetEnd = targetField.Offset + targetField.Size
let affectedFields =
cvt._Fields
|> List.filter (fun f ->
let fieldStart = f.Offset
let fieldEnd = f.Offset + f.Size
// Fields overlap if their ranges intersect
fieldStart < targetEnd && targetStart < fieldEnd
)
match affectedFields |> List.tryExactlyOne with
| None -> failwith "TODO: overlapping fields"
| Some toReplace ->
{
Layout = cvt.Layout
_Fields =
cvt._Fields
|> List.replaceWhere (fun f ->
if f.Name = toReplace.Name then
{ f with
Contents = value
}
|> Some
else
None
)
}
/// To facilitate bodges. This function absolutely should not exist.
static member TryExactlyOneField (cvt : CliValueType) : CliField option =
match cvt._Fields with
| [] -> None
| [ x ] -> Some (CliConcreteField.ToCliField x)
| _ -> None
/// To facilitate bodges. This function absolutely should not exist.
static member TrySequentialFields (cvt : CliValueType) : CliField list option =
let isNone, isSome =
cvt._Fields |> List.partition (fun field -> field.ConfiguredOffset.IsNone)
match isSome with
| [] -> Some (isNone |> List.map CliConcreteField.ToCliField)
| [ field ] when field.ConfiguredOffset = Some 0 -> Some [ CliConcreteField.ToCliField field ]
| _ -> None
type CliTypeResolutionResult =
| Resolved of CliType
@@ -167,6 +417,16 @@ type CliTypeResolutionResult =
[<RequireQualifiedAccess>]
module CliType =
/// In fact any non-zero value will do for True, but we'll use 1
let ofBool (b : bool) : CliType = CliType.Bool (if b then 1uy else 0uy)
let ofChar (c : char) : CliType =
CliType.Char (byte (int c / 256), byte (int c % 256))
let ofManagedObject (ptr : ManagedHeapAddress) : CliType = CliType.ObjectRef (Some ptr)
let sizeOf (ty : CliType) : int = CliType.SizeOf(ty).Size
let zeroOfPrimitive (primitiveType : PrimitiveType) : CliType =
match primitiveType with
| PrimitiveType.Boolean -> CliType.Bool 0uy
@@ -187,8 +447,24 @@ module CliType =
| PrimitiveType.Double -> CliType.Numeric (CliNumericType.Float64 0.0)
| PrimitiveType.String -> CliType.ObjectRef None
| PrimitiveType.TypedReference -> failwith "todo"
| PrimitiveType.IntPtr -> CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
| PrimitiveType.UIntPtr -> CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
| PrimitiveType.IntPtr ->
{
Name = "_value"
Contents = CliType.RuntimePointer (CliRuntimePointer.Managed ManagedPointerSource.Null)
Offset = None
}
|> List.singleton
|> CliValueType.OfFields Layout.Default
|> CliType.ValueType
| PrimitiveType.UIntPtr ->
{
Name = "_value"
Contents = CliType.RuntimePointer (CliRuntimePointer.Managed ManagedPointerSource.Null)
Offset = None
}
|> List.singleton
|> CliValueType.OfFields Layout.Default
|> CliType.ValueType
| PrimitiveType.Object -> CliType.ObjectRef None
let rec zeroOf
@@ -213,7 +489,7 @@ module CliType =
match handle with
| ConcreteTypeHandle.Byref _ ->
// Byref types are managed references - the zero value is a null reference
CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null), concreteTypes
CliType.RuntimePointer (CliRuntimePointer.Managed ManagedPointerSource.Null), concreteTypes
| ConcreteTypeHandle.Pointer _ ->
// Pointer types are unmanaged pointers - the zero value is a null pointer
@@ -265,38 +541,45 @@ module CliType =
zeroOfPrimitive PrimitiveType.IntPtr, concreteTypes
elif TypeInfo.NominallyEqual typeDef corelib.UIntPtr then
zeroOfPrimitive PrimitiveType.UIntPtr, concreteTypes
elif TypeInfo.NominallyEqual typeDef corelib.Array then
// Arrays are reference types
CliType.ObjectRef None, concreteTypes
else if
// Check if it's an array type
typeDef = corelib.Array
then
CliType.ObjectRef None, concreteTypes // Arrays are reference types
else if
// Not a known primitive, now check for cycles
Set.contains handle visited
then
// We're in a cycle - return a default zero value for the type
// For value types in cycles, we'll return a null reference as a safe fallback
// This should only happen with self-referential types
// Value types can't be self-referential unless they are specifically known to the
// runtime - for example, System.Byte is a value type with a single field,
// of type System.Byte.
// Since we check for (nominal) equality against all such types in the first branch,
// this code path is only hit with reference types.
CliType.ObjectRef None, concreteTypes
else
let visited = Set.add handle visited
// Not a known primitive, check if it's a value type or reference type
determineZeroForCustomType concreteTypes assemblies corelib handle concreteType typeDef visited
else if
// Not from corelib or has generics
concreteType.Assembly = corelib.Corelib.Name
&& typeDef = corelib.Array
&& concreteType.Generics.Length = 1
then
// This is an array type
// This is an array type, so null is appropriate
CliType.ObjectRef None, concreteTypes
else if
// Custom type - now check for cycles
Set.contains handle visited
then
// We're in a cycle - return a default zero value for the type
// For value types in cycles, we'll return a null reference as a safe fallback
// This should only happen with self-referential types
// We're in a cycle - return a default zero value for the type.
// Value types can't be self-referential unless they are specifically known to the
// runtime - for example, System.Byte is a value type with a single field,
// of type System.Byte.
// Since we check for (nominal) equality against all such types in the first branch,
// this code path is only hit with reference types.
CliType.ObjectRef None, concreteTypes
else
let visited = Set.add handle visited
@@ -309,7 +592,7 @@ module CliType =
(corelib : BaseClassTypes<DumpedAssembly>)
(handle : ConcreteTypeHandle)
(concreteType : ConcreteType<ConcreteTypeHandle>)
(typeDef : WoofWare.PawPrint.TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>)
(typeDef : WoofWare.PawPrint.TypeInfo<GenericParamFromMetadata, TypeDefn>)
(visited : Set<ConcreteTypeHandle>)
: CliType * AllConcreteTypes
=
@@ -326,7 +609,7 @@ module CliType =
// It's a value type - need to create zero values for all non-static fields
let mutable currentConcreteTypes = concreteTypes
let fieldZeros =
let vt =
typeDef.Fields
|> List.filter (fun field -> not (field.Attributes.HasFlag FieldAttributes.Static))
|> List.map (fun field ->
@@ -342,10 +625,16 @@ module CliType =
zeroOfWithVisited currentConcreteTypes assemblies corelib fieldHandle visited
currentConcreteTypes <- updatedConcreteTypes2
(field.Name, fieldZero)
)
CliType.ValueType fieldZeros, currentConcreteTypes
{
Name = field.Name
Contents = fieldZero
Offset = field.Offset
}
)
|> CliValueType.OfFields typeDef.Layout
CliType.ValueType vt, currentConcreteTypes
else
// It's a reference type
CliType.ObjectRef None, concreteTypes
@@ -369,31 +658,50 @@ module CliType =
}
// The field type might reference generic parameters of the declaring type
let typeGenerics = declaringType.Generics |> ImmutableArray.CreateRange
let methodGenerics = ImmutableArray.Empty // Fields don't have method generics
let loadAssembly
(assyName : AssemblyName)
(ref : AssemblyReferenceHandle)
: (ImmutableDictionary<string, DumpedAssembly> * DumpedAssembly)
=
match assemblies.TryGetValue assyName.FullName with
| true, currentAssy ->
let targetAssyRef = currentAssy.AssemblyReferences.[ref]
let loadAssembly =
{ new IAssemblyLoad with
member _.LoadAssembly loaded assyName ref =
match loaded.TryGetValue assyName.FullName with
| true, currentAssy ->
let targetAssyRef = currentAssy.AssemblyReferences.[ref]
match assemblies.TryGetValue targetAssyRef.Name.FullName with
| true, targetAssy -> assemblies, targetAssy
| false, _ ->
failwithf "Assembly %s not loaded when trying to resolve reference" targetAssyRef.Name.FullName
| false, _ -> failwithf "Current assembly %s not loaded when trying to resolve reference" assyName.FullName
match loaded.TryGetValue targetAssyRef.Name.FullName with
| true, targetAssy -> loaded, targetAssy
| false, _ ->
failwithf
"Assembly %s not loaded when trying to resolve reference"
targetAssyRef.Name.FullName
| false, _ ->
failwithf "Current assembly %s not loaded when trying to resolve reference" assyName.FullName
}
let handle, newCtx =
TypeConcretization.concretizeType
ctx
loadAssembly
declaringType.Assembly
typeGenerics
declaringType.Generics
methodGenerics
fieldType
handle, newCtx.ConcreteTypes
let withFieldSet (field : string) (value : CliType) (c : CliType) : CliType =
match c with
| CliType.Numeric cliNumericType -> failwith "todo"
| CliType.Bool b -> failwith "todo"
| CliType.Char (high, low) -> failwith "todo"
| CliType.ObjectRef managedHeapAddressOption -> failwith "todo"
| CliType.RuntimePointer cliRuntimePointer -> failwith "todo"
| CliType.ValueType cvt -> CliValueType.WithFieldSet field value cvt |> CliType.ValueType
let getField (field : string) (value : CliType) : CliType =
match value with
| CliType.Numeric cliNumericType -> failwith "todo"
| CliType.Bool b -> failwith "todo"
| CliType.Char (high, low) -> failwith "todo"
| CliType.ObjectRef managedHeapAddressOption -> failwith "todo"
| CliType.RuntimePointer cliRuntimePointer -> failwith "todo"
| CliType.ValueType cvt -> CliValueType.DereferenceField field cvt

View File

@@ -6,6 +6,7 @@ type IArithmeticOperation =
abstract Int32Int32 : int32 -> int32 -> int32
abstract Int64Int64 : int64 -> int64 -> int64
abstract FloatFloat : float -> float -> float
abstract NativeIntNativeInt : nativeint -> nativeint -> nativeint
abstract Name : string
[<RequireQualifiedAccess>]
@@ -15,6 +16,7 @@ module ArithmeticOperation =
member _.Int32Int32 a b = (# "add" a b : int32 #)
member _.Int64Int64 a b = (# "add" a b : int64 #)
member _.FloatFloat a b = (# "add" a b : float #)
member _.NativeIntNativeInt a b = (# "add" a b : nativeint #)
member _.Name = "add"
}
@@ -23,6 +25,7 @@ module ArithmeticOperation =
member _.Int32Int32 a b = (# "sub" a b : int32 #)
member _.Int64Int64 a b = (# "sub" a b : int64 #)
member _.FloatFloat a b = (# "sub" a b : float #)
member _.NativeIntNativeInt a b = (# "sub" a b : nativeint #)
member _.Name = "sub"
}
@@ -31,9 +34,19 @@ module ArithmeticOperation =
member _.Int32Int32 a b = (# "mul" a b : int32 #)
member _.Int64Int64 a b = (# "mul" a b : int64 #)
member _.FloatFloat a b = (# "mul" a b : float #)
member _.NativeIntNativeInt a b = (# "mul" a b : nativeint #)
member _.Name = "mul"
}
let mulOvf =
{ new IArithmeticOperation with
member _.Int32Int32 a b = (# "mul.ovf" a b : int32 #)
member _.Int64Int64 a b = (# "mul.ovf" a b : int64 #)
member _.FloatFloat a b = (# "mul.ovf" a b : float #)
member _.NativeIntNativeInt a b = (# "mul.ovf" a b : nativeint #)
member _.Name = "mul_ovf"
}
[<RequireQualifiedAccess>]
module BinaryArithmetic =
let execute (op : IArithmeticOperation) (val1 : EvalStackValue) (val2 : EvalStackValue) : EvalStackValue =
@@ -45,7 +58,21 @@ module BinaryArithmetic =
| EvalStackValue.Int32 val1, EvalStackValue.ObjectRef val2 -> failwith "" |> EvalStackValue.ObjectRef
| EvalStackValue.Int64 val1, EvalStackValue.Int64 val2 -> op.Int64Int64 val1 val2 |> EvalStackValue.Int64
| EvalStackValue.NativeInt val1, EvalStackValue.Int32 val2 -> failwith "" |> EvalStackValue.NativeInt
| EvalStackValue.NativeInt val1, EvalStackValue.NativeInt val2 -> failwith "" |> EvalStackValue.NativeInt
| EvalStackValue.NativeInt val1, EvalStackValue.NativeInt val2 ->
let val1 =
match val1 with
| NativeIntSource.Verbatim n -> nativeint<int64> n
| v -> failwith $"refusing to multiply non-verbatim native int %O{v}"
let val2 =
match val2 with
| NativeIntSource.Verbatim n -> nativeint<int64> n
| v -> failwith $"refusing to multiply non-verbatim native int %O{v}"
op.NativeIntNativeInt val1 val2
|> int64<nativeint>
|> NativeIntSource.Verbatim
|> EvalStackValue.NativeInt
| EvalStackValue.NativeInt val1, EvalStackValue.ManagedPointer val2 ->
failwith "" |> EvalStackValue.ManagedPointer
| EvalStackValue.NativeInt val1, EvalStackValue.ObjectRef val2 -> failwith "" |> EvalStackValue.ObjectRef

View File

@@ -0,0 +1,13 @@
namespace WoofWare.PawPrint
[<AutoOpen>]
module Constants =
[<Literal>]
let SIZEOF_INT = 4
[<Literal>]
let SIZEOF_OBJ = 8
[<Literal>]
let DEFAULT_STRUCT_ALIGNMENT = 8

View File

@@ -134,6 +134,21 @@ module Corelib =
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "UIntPtr" then Some v else None)
|> Seq.exactlyOne
let runtimeFieldInfoStubType =
corelib.TypeDefs
|> Seq.choose (fun (KeyValue (_, v)) -> if v.Name = "RuntimeFieldInfoStub" then Some v else None)
|> Seq.exactlyOne
let runtimeFieldHandleInternalType =
corelib.TypeDefs
|> Seq.choose (fun (KeyValue (_, v)) ->
if v.Name = "RuntimeFieldHandleInternal" then
Some v
else
None
)
|> Seq.exactlyOne
{
Corelib = corelib
String = stringType
@@ -157,6 +172,8 @@ module Corelib =
RuntimeTypeHandle = runtimeTypeHandleType
RuntimeMethodHandle = runtimeMethodHandleType
RuntimeFieldHandle = runtimeFieldHandleType
RuntimeFieldInfoStub = runtimeFieldInfoStubType
RuntimeFieldHandleInternal = runtimeFieldHandleInternalType
RuntimeType = runtimeTypeType
Void = voidType
TypedReference = typedReferenceType

View File

@@ -8,10 +8,7 @@ type EvalStackValue =
| Float of float
| ManagedPointer of ManagedPointerSource
| ObjectRef of ManagedHeapAddress
// Fraser thinks this isn't really a thing in CoreCLR
// | TransientPointer of TransientPointerSource
/// Mapping of field name to value
| UserDefinedValueType of (string * EvalStackValue) list
| UserDefinedValueType of EvalStackValueUserType
override this.ToString () =
match this with
@@ -23,12 +20,49 @@ type EvalStackValue =
| EvalStackValue.ObjectRef managedHeapAddress -> $"ObjectRef(%O{managedHeapAddress})"
| EvalStackValue.UserDefinedValueType evalStackValues ->
let desc =
evalStackValues
|> List.map (snd >> string<EvalStackValue>)
evalStackValues.Fields
|> List.map (_.ContentsEval >> string<EvalStackValue>)
|> String.concat " | "
$"Struct(%s{desc})"
and EvalStackValueField =
{
Name : string
ContentsEval : EvalStackValue
Offset : int option
}
and EvalStackValueUserType =
{
Fields : EvalStackValueField list
Layout : Layout
}
static member DereferenceField (name : string) (this : EvalStackValueUserType) =
// TODO: this doesn't account for overlapping fields
this.Fields
|> List.pick (fun stackField ->
if stackField.Name = name then
Some stackField.ContentsEval
else
None
)
static member OfFields (layout : Layout) (fields : EvalStackValueField list) =
{
Fields = fields
Layout = layout
}
static member TrySequentialFields (cvt : EvalStackValueUserType) : EvalStackValueField list option =
let isNone, isSome = cvt.Fields |> List.partition (fun field -> field.Offset.IsNone)
match isSome with
| [] -> Some isNone
| [ field ] when field.Offset = Some 0 -> Some [ field ]
| _ -> None
[<RequireQualifiedAccess>]
module EvalStackValue =
/// The conversion performed by Conv_u.
@@ -107,7 +141,24 @@ module EvalStackValue =
| CliNumericType.Int32 _ ->
match popped with
| EvalStackValue.Int32 i -> CliType.Numeric (CliNumericType.Int32 i)
| EvalStackValue.UserDefinedValueType [ popped ] -> toCliTypeCoerced target (snd popped)
| EvalStackValue.UserDefinedValueType popped ->
match popped.Fields with
| [] -> failwith "unexpectedly empty"
| [ popped ] -> toCliTypeCoerced target popped.ContentsEval
| fields ->
match fields.[0].Offset with
| None -> failwith "TODO"
| Some _ ->
let fields =
fields
|> List.map (fun f ->
match f.Offset with
| None -> failwith "unexpectedly got a field which didn't have an offset"
| Some offset -> offset, f
)
|> List.sortBy fst
failwith "TODO"
| i -> failwith $"TODO: %O{i}"
| CliNumericType.Int64 _ ->
match popped with
@@ -123,11 +174,16 @@ module EvalStackValue =
| i -> failwith $"TODO: %O{i}"
| CliNumericType.NativeInt _ ->
match popped with
| EvalStackValue.NativeInt s -> CliNumericType.NativeInt s
| EvalStackValue.NativeInt s -> CliNumericType.NativeInt s |> CliType.Numeric
| EvalStackValue.ManagedPointer ptrSrc ->
CliNumericType.NativeInt (NativeIntSource.ManagedPointer ptrSrc)
|> CliType.Numeric
| EvalStackValue.UserDefinedValueType vt ->
match vt.Fields with
| [] -> failwith "unexpected"
| [ vt ] -> toCliTypeCoerced target vt.ContentsEval
| _ -> failwith $"TODO: {popped}"
| _ -> failwith $"TODO: {popped}"
|> CliType.Numeric
| CliNumericType.NativeFloat f -> failwith "todo"
| CliNumericType.Int8 _ ->
match popped with
@@ -156,19 +212,11 @@ module EvalStackValue =
| CliType.ObjectRef _ ->
match popped with
| EvalStackValue.ManagedPointer ptrSource ->
match ptrSource with
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, whichVar)
|> CliRuntimePointer.Managed
|> CliType.RuntimePointer
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
CliRuntimePointerSource.Argument (sourceThread, methodFrame, whichVar)
|> CliRuntimePointer.Managed
|> CliType.RuntimePointer
| ManagedPointerSource.Heap managedHeapAddress -> CliType.ObjectRef (Some managedHeapAddress)
| ManagedPointerSource.Null -> CliType.ObjectRef None
| ManagedPointerSource.ArrayIndex (arr, ind) ->
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.ArrayIndex (arr, ind)))
ptrSource |> CliRuntimePointer.Managed |> CliType.RuntimePointer
| EvalStackValue.ObjectRef ptr ->
ManagedPointerSource.Heap ptr
|> CliRuntimePointer.Managed
|> CliType.RuntimePointer
| EvalStackValue.NativeInt nativeIntSource ->
match nativeIntSource with
| NativeIntSource.Verbatim 0L -> CliType.ObjectRef None
@@ -180,9 +228,9 @@ module EvalStackValue =
| ManagedPointerSource.Null -> CliType.ObjectRef None
| ManagedPointerSource.Heap s -> CliType.ObjectRef (Some s)
| _ -> failwith "TODO"
| EvalStackValue.UserDefinedValueType fields ->
match fields with
| [ esv ] -> toCliTypeCoerced target (snd esv)
| EvalStackValue.UserDefinedValueType obj ->
match obj.Fields with
| [ esv ] -> toCliTypeCoerced target esv.ContentsEval
| fields -> failwith $"TODO: don't know how to coerce struct of {fields} to a pointer"
| _ -> failwith $"TODO: {popped}"
| CliType.Bool _ ->
@@ -195,41 +243,18 @@ module EvalStackValue =
| 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 (sourceThread, methodFrame, var) ->
CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, var)
|> CliRuntimePointer.Managed
|> CliType.RuntimePointer
| ManagedPointerSource.Argument (sourceThread, methodFrame, var) ->
CliRuntimePointerSource.Argument (sourceThread, methodFrame, var)
|> CliRuntimePointer.Managed
|> CliType.RuntimePointer
| ManagedPointerSource.ArrayIndex (arr, index) ->
CliRuntimePointerSource.ArrayIndex (arr, index)
|> CliRuntimePointer.Managed
|> CliType.RuntimePointer
| EvalStackValue.ManagedPointer src -> src |> CliRuntimePointer.Managed |> CliType.RuntimePointer
| EvalStackValue.NativeInt intSrc ->
match intSrc with
| NativeIntSource.Verbatim i -> CliType.RuntimePointer (CliRuntimePointer.Unmanaged i)
| NativeIntSource.ManagedPointer src ->
match src with
| ManagedPointerSource.Heap src ->
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Heap src))
| ManagedPointerSource.Null ->
CliType.RuntimePointer (CliRuntimePointer.Managed CliRuntimePointerSource.Null)
| ManagedPointerSource.LocalVariable (a, b, c) ->
CliType.RuntimePointer (
CliRuntimePointer.Managed (CliRuntimePointerSource.LocalVariable (a, b, c))
)
| ManagedPointerSource.Argument (a, b, c) ->
CliType.RuntimePointer (CliRuntimePointer.Managed (CliRuntimePointerSource.Argument (a, b, c)))
| ManagedPointerSource.ArrayIndex _ -> failwith "TODO"
| NativeIntSource.ManagedPointer src -> src |> CliRuntimePointer.Managed |> CliType.RuntimePointer
| NativeIntSource.FunctionPointer methodInfo ->
CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.FunctionPointer methodInfo))
| NativeIntSource.TypeHandlePtr int64 -> failwith "todo"
| EvalStackValue.ObjectRef addr ->
ManagedPointerSource.Heap addr
|> CliRuntimePointer.Managed
|> CliType.RuntimePointer
| _ -> failwith $"TODO: %O{popped}"
| CliType.Char _ ->
match popped with
@@ -238,26 +263,37 @@ module EvalStackValue =
let low = i % 256
CliType.Char (byte<int> high, byte<int> low)
| popped -> failwith $"Unexpectedly wanted a char from {popped}"
| CliType.ValueType fields ->
| CliType.ValueType vt ->
match popped with
| EvalStackValue.UserDefinedValueType popped ->
if fields.Length <> popped.Length then
failwith
$"mismatch: popped value type {popped} (length %i{popped.Length}) into {fields} (length %i{fields.Length})"
| EvalStackValue.UserDefinedValueType popped' ->
match CliValueType.TrySequentialFields vt, EvalStackValueUserType.TrySequentialFields popped' with
| Some vt, Some popped ->
if vt.Length <> popped.Length then
failwith
$"mismatch: popped value type {popped} (length %i{popped.Length}) into {vt} (length %i{vt.Length})"
List.map2
(fun (name1, v1) (name2, v2) ->
if name1 <> name2 then
failwith $"TODO: name mismatch, {name1} vs {name2}"
(vt, popped)
||> List.map2 (fun field1 popped ->
if field1.Name <> popped.Name then
failwith $"TODO: name mismatch, {field1.Name} vs {popped.Name}"
name1, toCliTypeCoerced v1 v2
if field1.Offset <> popped.Offset then
failwith $"TODO: offset mismatch for {field1.Name}, {field1.Offset} vs {popped.Offset}"
let contents = toCliTypeCoerced field1.Contents popped.ContentsEval
{
CliField.Name = field1.Name
Contents = contents
Offset = field1.Offset
}
)
fields
popped
|> CliType.ValueType
|> CliValueType.OfFields popped'.Layout
|> CliType.ValueType
| _, _ -> failwith "TODO: overlapping fields going onto eval stack"
| popped ->
match fields with
| [ _, target ] -> toCliTypeCoerced target popped
match CliValueType.TryExactlyOneField vt with
| Some field -> toCliTypeCoerced field.Contents popped
| _ -> failwith $"TODO: {popped} into value type {target}"
let rec ofCliType (v : CliType) : EvalStackValue =
@@ -286,21 +322,21 @@ module EvalStackValue =
| CliType.RuntimePointer ptr ->
match ptr with
| CliRuntimePointer.Unmanaged ptrInt -> NativeIntSource.Verbatim ptrInt |> EvalStackValue.NativeInt
| CliRuntimePointer.Managed ptr ->
match ptr with
| CliRuntimePointerSource.LocalVariable (sourceThread, methodFrame, var) ->
ManagedPointerSource.LocalVariable (sourceThread, methodFrame, var)
|> EvalStackValue.ManagedPointer
| CliRuntimePointerSource.ArrayIndex (arr, ind) ->
ManagedPointerSource.ArrayIndex (arr, ind) |> EvalStackValue.ManagedPointer
| CliRuntimePointerSource.Argument (sourceThread, methodFrame, var) ->
ManagedPointerSource.Argument (sourceThread, methodFrame, var)
|> EvalStackValue.ManagedPointer
| CliRuntimePointerSource.Heap addr -> EvalStackValue.ObjectRef addr
| CliRuntimePointerSource.Null -> EvalStackValue.ManagedPointer ManagedPointerSource.Null
| CliRuntimePointer.Managed ptr -> ptr |> EvalStackValue.ManagedPointer
| CliType.ValueType fields ->
fields
|> List.map (fun (name, f) -> name, ofCliType f)
// The only allowable use of _Fields
fields._Fields
|> List.map (fun field ->
let contents = ofCliType field.Contents
{
Name = field.Name
// TODO: this probably wants to be a real offset
Offset = field.ConfiguredOffset
ContentsEval = contents
}
)
|> EvalStackValueUserType.OfFields fields.Layout
|> EvalStackValue.UserDefinedValueType
type EvalStack =
@@ -335,3 +371,5 @@ type EvalStack =
let v = EvalStackValue.ofCliType v
EvalStack.Push' v stack
static member PeekNthFromTop (n : int) (stack : EvalStack) : EvalStackValue option = stack.Values |> List.tryItem n

View File

@@ -120,9 +120,27 @@ module EvalStackValueComparisons =
failwith "TODO"
| other1, other2 -> failwith $"Cgt.un instruction invalid for comparing {other1} vs {other2}"
let ceq (var1 : EvalStackValue) (var2 : EvalStackValue) : bool =
let rec ceq (var1 : EvalStackValue) (var2 : EvalStackValue) : bool =
// Table III.4
match var1, var2 with
| EvalStackValue.UserDefinedValueType {
Fields = [ f ]
},
v -> ceq f.ContentsEval v
| u,
EvalStackValue.UserDefinedValueType {
Fields = [ f ]
} -> ceq u f.ContentsEval
| EvalStackValue.UserDefinedValueType {
Fields = []
},
EvalStackValue.UserDefinedValueType {
Fields = []
} ->
// hmm, surely this can't happen, but :shrug:
true
| EvalStackValue.UserDefinedValueType _, _
| _, EvalStackValue.UserDefinedValueType _ -> failwith $"TODO: ceq {var1} vs {var2}"
| EvalStackValue.Int32 var1, EvalStackValue.Int32 var2 -> var1 = var2
| EvalStackValue.Int32 var1, EvalStackValue.NativeInt var2 -> failwith "TODO: int32 CEQ nativeint"
| EvalStackValue.Int32 _, _ -> failwith $"bad ceq: Int32 vs {var2}"
@@ -146,9 +164,18 @@ module EvalStackValueComparisons =
failwith $"TODO (CEQ): nativeint vs managed pointer"
| EvalStackValue.NativeInt _, _ -> failwith $"bad ceq: NativeInt vs {var2}"
| EvalStackValue.ObjectRef var1, EvalStackValue.ObjectRef var2 -> var1 = var2
| EvalStackValue.ManagedPointer src, EvalStackValue.ObjectRef var1
| EvalStackValue.ObjectRef var1, EvalStackValue.ManagedPointer src ->
match src with
| ManagedPointerSource.Heap src -> src = var1
| ManagedPointerSource.Null -> false
| ManagedPointerSource.Field _
| ManagedPointerSource.LocalVariable _
| ManagedPointerSource.Argument _ -> false
| ManagedPointerSource.ArrayIndex (arr, index) -> failwith "todo"
| ManagedPointerSource.InterpretedAsType (src, ty) -> failwith "todo"
| EvalStackValue.ObjectRef _, _ -> failwith $"bad ceq: ObjectRef vs {var2}"
| EvalStackValue.ManagedPointer var1, EvalStackValue.ManagedPointer var2 -> var1 = var2
| EvalStackValue.ManagedPointer var1, EvalStackValue.NativeInt var2 ->
failwith $"TODO (CEQ): managed pointer vs nativeint"
| EvalStackValue.ManagedPointer _, _ -> failwith $"bad ceq: ManagedPointer vs {var2}"
| EvalStackValue.UserDefinedValueType _, _ -> failwith $"bad ceq: {var1} vs {var2}"

View File

@@ -34,7 +34,7 @@ module ExceptionHandling =
/// Check if an exception type matches a catch handler type
let private isExceptionAssignableTo
(exceptionTypeCrate : TypeInfoCrate)
(exceptionType : ConcreteTypeHandle)
(catchTypeToken : MetadataToken)
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
: bool
@@ -46,7 +46,7 @@ module ExceptionHandling =
/// Also returns `isFinally : bool`: whether this is a `finally` block (as opposed to e.g. a `catch`).
let findExceptionHandler
(currentPC : int)
(exceptionTypeCrate : TypeInfoCrate)
(exceptionType : ConcreteTypeHandle)
(method : WoofWare.PawPrint.MethodInfo<'typeGen, 'methodGeneric, 'methodVar>)
(assemblies : ImmutableDictionary<string, DumpedAssembly>)
: (WoofWare.PawPrint.ExceptionRegion * bool) option // handler, isFinally
@@ -62,7 +62,7 @@ module ExceptionHandling =
| ExceptionRegion.Catch (typeToken, offset) ->
if currentPC >= offset.TryOffset && currentPC < offset.TryOffset + offset.TryLength then
// Check if exception type matches
if isExceptionAssignableTo exceptionTypeCrate typeToken assemblies then
if isExceptionAssignableTo exceptionType typeToken assemblies then
Some (region, false)
else
None

View File

@@ -62,6 +62,7 @@ module System_Threading_Monitor =
match lockObj with
| EvalStackValue.ManagedPointer ManagedPointerSource.Null ->
failwith "TODO: throw ArgumentNullException"
| EvalStackValue.ObjectRef addr
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) ->
match IlMachineState.getSyncBlock addr state with
| SyncBlock.Free ->
@@ -82,11 +83,13 @@ module System_Threading_Monitor =
| ManagedPointerSource.Null -> failwith "logic error"
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
state
|> IlMachineState.setLocalVariable sourceThread methodFrame whichVar (CliType.OfBool true)
|> IlMachineState.setLocalVariable sourceThread methodFrame whichVar (CliType.ofBool true)
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
failwith "not really expecting to *edit* an argument..."
| ManagedPointerSource.Heap addr -> failwith "todo: managed heap"
| ManagedPointerSource.ArrayIndex _ -> failwith "todo: array index"
| ManagedPointerSource.Field (managedPointerSource, fieldName) -> failwith "todo"
| ManagedPointerSource.InterpretedAsType _ -> failwith "TODO"
(state, WhatWeDid.Executed) |> ExecutionResult.Stepped
@@ -100,6 +103,7 @@ module System_Threading_Monitor =
match lockObj with
| EvalStackValue.ManagedPointer ManagedPointerSource.Null ->
failwith "TODO: throw ArgumentNullException"
| EvalStackValue.ObjectRef addr
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) ->
match IlMachineState.getSyncBlock addr state with
| SyncBlock.Free -> failwith "TODO: throw SynchronizationLockException"

View File

@@ -0,0 +1,147 @@
namespace WoofWare.PawPrint
open System.Reflection
open System.Reflection.Metadata
type FieldHandle =
private
{
AssemblyFullName : string
DeclaringType : ConcreteTypeHandle
FieldHandle : ComparableFieldDefinitionHandle
}
type FieldHandleRegistry =
private
{
FieldHandleToId : Map<FieldHandle, int64>
FieldHandleToField : Map<ManagedHeapAddress, FieldHandle>
FieldToHandle : Map<FieldHandle, ManagedHeapAddress>
NextHandle : int64
}
[<RequireQualifiedAccess>]
module FieldHandleRegistry =
let empty () =
{
FieldHandleToField = Map.empty
FieldToHandle = Map.empty
FieldHandleToId = Map.empty
NextHandle = 1L
}
/// Returns a (struct) System.RuntimeFieldHandle, with its contents (reference type) freshly allocated if necessary.
let getOrAllocate
(baseClassTypes : BaseClassTypes<'corelib>)
(allocState : 'allocState)
(allocate : CliValueType -> 'allocState -> ManagedHeapAddress * 'allocState)
(declaringAssy : AssemblyName)
(declaringType : ConcreteTypeHandle)
(handle : FieldDefinitionHandle)
(reg : FieldHandleRegistry)
: CliType * FieldHandleRegistry * 'allocState
=
let runtimeFieldHandle (runtimeFieldInfoStub : ManagedHeapAddress) =
// RuntimeFieldHandle is a struct; it contains one field, an IRuntimeFieldInfo
// https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1048
// In practice we expect to use RuntimeFieldInfoStub for that IRuntimeFieldInfo:
// https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1157
let runtimeFieldHandleType = baseClassTypes.RuntimeFieldHandle
let field = runtimeFieldHandleType.Fields |> List.exactlyOne
if field.Name <> "m_ptr" then
failwith $"unexpected field name %s{field.Name} for BCL type RuntimeFieldHandle"
{
Name = "m_ptr"
Contents = CliType.ofManagedObject runtimeFieldInfoStub
Offset = None
}
|> List.singleton
|> CliValueType.OfFields Layout.Default
|> CliType.ValueType
let handle =
{
AssemblyFullName = declaringAssy.FullName
FieldHandle = ComparableFieldDefinitionHandle.Make handle
DeclaringType = declaringType
}
match Map.tryFind handle reg.FieldToHandle with
| Some v -> runtimeFieldHandle v, reg, allocState
| None ->
let newHandle = reg.NextHandle
let runtimeFieldHandleInternal =
let field = baseClassTypes.RuntimeFieldHandleInternal.Fields |> List.exactlyOne
if field.Name <> "m_handle" then
failwith $"unexpected field name %s{field.Name} for BCL type RuntimeFieldHandleInternal"
match field.Signature with
| TypeDefn.PrimitiveType PrimitiveType.IntPtr -> ()
| s -> failwith $"bad sig: {s}"
// https://github.com/dotnet/runtime/blob/2b21c73fa2c32fa0195e4a411a435dda185efd08/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1380
{
Name = "m_handle"
Contents = CliType.RuntimePointer (CliRuntimePointer.Unmanaged newHandle)
Offset = None // no struct layout was specified
}
|> List.singleton
|> CliValueType.OfFields Layout.Default
|> CliType.ValueType
// https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1074
let runtimeFieldInfoStub =
// LayoutKind.Sequential
[
// If we ever implement a GC, something should change here
{
Name = "m_keepalive"
Contents = CliType.ObjectRef None
Offset = None
}
{
Name = "m_c"
Contents = CliType.ObjectRef None
Offset = None
}
{
Name = "m_d"
Contents = CliType.ObjectRef None
Offset = None
}
{
Name = "m_b"
Contents = CliType.Numeric (CliNumericType.Int32 0)
Offset = None
}
{
Name = "m_e"
Contents = CliType.ObjectRef None
Offset = None
}
// RuntimeFieldHandleInternal: https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L1048
{
Name = "m_fieldHandle"
Contents = runtimeFieldHandleInternal
Offset = None
}
]
|> CliValueType.OfFields Layout.Default // explicitly sequential but no custom packing size
let alloc, state = allocate runtimeFieldInfoStub allocState
let reg =
{
FieldHandleToField = reg.FieldHandleToField |> Map.add alloc handle
FieldToHandle = reg.FieldToHandle |> Map.add handle alloc
FieldHandleToId = reg.FieldHandleToId |> Map.add handle newHandle
NextHandle = reg.NextHandle + 1L
}
runtimeFieldHandle alloc, reg, state

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,811 @@
namespace WoofWare.PawPrint
open System
open System.Collections.Immutable
open System.Reflection
open System.Reflection.Metadata
open System.Runtime.CompilerServices
open Microsoft.Extensions.Logging
[<RequireQualifiedAccess>]
module IlMachineStateExecution =
let getTypeOfObj
(loggerFactory : ILoggerFactory)
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
(state : IlMachineState)
(esv : EvalStackValue)
: IlMachineState * ConcreteTypeHandle
=
match esv with
| EvalStackValue.Int32 _ ->
DumpedAssembly.typeInfoToTypeDefn' baseClassTypes state._LoadedAssemblies baseClassTypes.Int32
|> IlMachineState.concretizeType
loggerFactory
baseClassTypes
state
baseClassTypes.Corelib.Name
ImmutableArray.Empty
ImmutableArray.Empty
| EvalStackValue.Int64 _ ->
DumpedAssembly.typeInfoToTypeDefn' baseClassTypes state._LoadedAssemblies baseClassTypes.Int64
|> IlMachineState.concretizeType
loggerFactory
baseClassTypes
state
baseClassTypes.Corelib.Name
ImmutableArray.Empty
ImmutableArray.Empty
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
| EvalStackValue.Float _ ->
DumpedAssembly.typeInfoToTypeDefn' baseClassTypes state._LoadedAssemblies baseClassTypes.Double
|> IlMachineState.concretizeType
loggerFactory
baseClassTypes
state
baseClassTypes.Corelib.Name
ImmutableArray.Empty
ImmutableArray.Empty
| EvalStackValue.ManagedPointer src ->
match src with
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) -> failwith "todo"
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) -> failwith "todo"
| ManagedPointerSource.Heap addr ->
let o = ManagedHeap.get addr state.ManagedHeap
state, o.ConcreteType
| ManagedPointerSource.ArrayIndex (arr, index) -> failwith "todo"
| ManagedPointerSource.Null -> failwith "todo"
| ManagedPointerSource.Field (managedPointerSource, fieldName) -> failwith "todo"
| ManagedPointerSource.InterpretedAsType (src, ty) -> failwith "todo"
| EvalStackValue.ObjectRef addr ->
let o = ManagedHeap.get addr state.ManagedHeap
state, o.ConcreteType
| EvalStackValue.UserDefinedValueType tuples -> failwith "todo"
let isAssignableFrom
(objToCast : ConcreteTypeHandle)
(possibleTargetType : ConcreteTypeHandle)
(state : IlMachineState)
: bool
=
if objToCast = possibleTargetType then
true
else
let objToCast' = AllConcreteTypes.lookup objToCast state.ConcreteTypes |> Option.get
let possibleTargetType' =
AllConcreteTypes.lookup possibleTargetType state.ConcreteTypes |> Option.get
// TODO: null can be assigned to any reference type; might not be relevant here?
match possibleTargetType with
| ConcreteObj state.ConcreteTypes -> true
| ConcreteValueType state.ConcreteTypes when failwith "check if objToCast inherits ValueType" -> true
| _ ->
// Claude describes the algorithm here:
// https://claude.ai/chat/f15e23f6-a27b-4655-9e69-e4d445dd1249
failwith
$"TODO: check inheritance chain and interfaces: is {objToCast'} assignable from {possibleTargetType'}?"
let callMethod
(loggerFactory : ILoggerFactory)
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
(wasInitialising : ConcreteTypeHandle option)
(wasConstructing : ManagedHeapAddress option)
(performInterfaceResolution : bool)
(wasClassConstructor : bool)
(advanceProgramCounterOfCaller : bool)
(methodGenerics : ImmutableArray<ConcreteTypeHandle>)
(methodToCall : WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>)
(thread : ThreadId)
(threadState : ThreadState)
(state : IlMachineState)
: IlMachineState
=
let logger = loggerFactory.CreateLogger "CallMethod"
let activeAssy = state.ActiveAssembly thread
// Check for intrinsics first
let isIntrinsic =
MethodInfo.isJITIntrinsic
(fun handle ->
match activeAssy.Members.[handle].Parent with
| MetadataToken.TypeReference r -> activeAssy.TypeRefs.[r]
| x -> failwith $"{x}"
)
activeAssy.Methods
methodToCall
match
if isIntrinsic then
Intrinsics.call loggerFactory baseClassTypes methodToCall thread state
else
None
with
| Some result -> result
| None ->
// Get zero values for all parameters
let state, argZeroObjects =
((state, []), methodToCall.Signature.ParameterTypes)
||> List.fold (fun (state, zeros) tyHandle ->
let zero, state = IlMachineState.cliTypeZeroOfHandle state baseClassTypes tyHandle
state, zero :: zeros
)
let argZeroObjects = List.rev argZeroObjects
let activeMethodState = threadState.MethodStates.[threadState.ActiveMethodState]
let state, methodToCall =
match methodToCall.Instructions, performInterfaceResolution, methodToCall.IsStatic with
| None, true, false ->
logger.LogDebug (
"Identifying target of virtual call for {TypeName}.{MethodName}",
methodToCall.DeclaringType.Name,
methodToCall.Name
)
// This might be an interface implementation, or implemented by native code.
// If native code, we'll deal with that when we actually start implementing.
// Since we're not static, there's a `this` on the eval stack.
// It comes *below* all the arguments.
let callingObj =
match
activeMethodState.EvaluationStack
|> EvalStack.PeekNthFromTop methodToCall.Parameters.Length
with
| None -> failwith "unexpectedly no `this` on the eval stack of instance method"
| Some this -> this
let state, callingObjTyHandle =
getTypeOfObj loggerFactory baseClassTypes state callingObj
let callingObjTy =
let ty =
AllConcreteTypes.lookup callingObjTyHandle state.ConcreteTypes |> Option.get
state.LoadedAssembly(ty.Assembly).Value.TypeDefs.[ty.Definition.Get]
let declaringAssy = state.LoadedAssembly(methodToCall.DeclaringType.Assembly).Value
let methodDeclaringType =
declaringAssy.TypeDefs.[methodToCall.DeclaringType.Definition.Get]
let interfaceExplicitNamedMethod =
if methodDeclaringType.IsInterface then
Some
$"{TypeInfo.fullName (fun h -> declaringAssy.TypeDefs.[h]) methodDeclaringType}.{methodToCall.Name}"
else
None
// Does type `callingObjTy` implement this method? If so, this is probably a JIT intrinsic or
// is supplied by the runtime.
let selfImplementation, state =
(state, callingObjTy.Methods)
||> List.mapFold (fun state meth ->
if
meth.Signature.GenericParameterCount
<> methodToCall.Signature.GenericParameterCount
|| meth.Signature.RequiredParameterCount
<> methodToCall.Signature.RequiredParameterCount
then
None, state
else if
meth.Name <> methodToCall.Name && Some meth.Name <> interfaceExplicitNamedMethod
then
None, state
else
// TODO: check if methodToCall's declaringtype is an interface; if so, check the possible prefixed name first
let state, retType =
meth.Signature.ReturnType
|> IlMachineState.concretizeType
loggerFactory
baseClassTypes
state
meth.DeclaringType.Assembly
methodToCall.DeclaringType.Generics
methodToCall.Generics
let paramTypes, state =
(state, meth.Signature.ParameterTypes)
||> Seq.mapFold (fun state ty ->
ty
|> IlMachineState.concretizeType
loggerFactory
baseClassTypes
state
meth.DeclaringType.Assembly
methodToCall.DeclaringType.Generics
methodToCall.Generics
|> fun (a, b) -> b, a
)
let paramTypes = List.ofSeq paramTypes
if
isAssignableFrom retType methodToCall.Signature.ReturnType state
&& paramTypes = methodToCall.Signature.ParameterTypes
then
Some (meth, Some meth.Name = interfaceExplicitNamedMethod), state
else
None, state
)
let selfImplementation =
selfImplementation
|> List.choose id
|> List.sortBy (fun (_, isInterface) -> if isInterface then -1 else 0)
match selfImplementation with
| (impl, true) :: l when (l |> List.forall (fun (_, b) -> not b)) ->
logger.LogDebug "Found concrete implementation from an interface"
let typeGenerics =
AllConcreteTypes.lookup callingObjTyHandle state.ConcreteTypes
|> Option.get
|> _.Generics
let state, meth, _ =
IlMachineState.concretizeMethodWithAllGenerics
loggerFactory
baseClassTypes
typeGenerics
impl
methodGenerics
state
state, meth
| [ impl, false ] ->
logger.LogDebug "Found concrete implementation"
// Yes, callingObjTy implements the method directly. No need to look up interfaces.
let typeGenerics =
AllConcreteTypes.lookup callingObjTyHandle state.ConcreteTypes
|> Option.get
|> _.Generics
let state, meth, _ =
IlMachineState.concretizeMethodWithAllGenerics
loggerFactory
baseClassTypes
typeGenerics
impl
methodGenerics
state
state, meth
| _ :: _ ->
selfImplementation
|> List.map (fun (m, _) -> m.Name)
|> String.concat ", "
|> failwithf "multiple options: %s"
| [] ->
logger.LogDebug "No concrete implementation found; scanning interfaces"
// If not, what interfaces does it implement, and do any of those implement the method?
let possibleInterfaceMethods, state =
(state, callingObjTy.ImplementedInterfaces)
||> Seq.mapFold (fun state impl ->
let assy = state.LoadedAssembly impl.RelativeToAssembly |> Option.get
let state, defn =
match impl.InterfaceHandle with
| MetadataToken.TypeDefinition defn ->
let state, defn = IlMachineState.lookupTypeDefn baseClassTypes state assy defn
let state, _, defn =
// TODO: generics
IlMachineState.resolveTypeFromDefn
loggerFactory
baseClassTypes
defn
ImmutableArray.Empty
ImmutableArray.Empty
assy
state
state, defn
| MetadataToken.TypeReference ty ->
let state, defn, assy =
IlMachineState.lookupTypeRef loggerFactory baseClassTypes state assy Seq.empty ty
state, failwith "TODO"
| MetadataToken.TypeSpecification spec ->
// TODO: generics
let state, assy, defn =
IlMachineState.resolveTypeFromSpec
loggerFactory
baseClassTypes
spec
assy
ImmutableArray.Empty
ImmutableArray.Empty
state
state, defn
| handle -> failwith $"unexpected: {handle}"
logger.LogDebug (
"Interface {InterfaceName} (generics: {InterfaceGenerics})",
defn.Name,
defn.Generics
)
let s, state =
defn.Methods
|> Seq.filter (fun mi -> mi.Name = methodToCall.Name
// TODO: also the rest of the signature
)
|> Seq.mapFold
(fun state meth ->
// TODO: generics
let state, mi, _ =
IlMachineState.concretizeMethodForExecution
loggerFactory
baseClassTypes
thread
meth
None
(if defn.Generics.IsEmpty then None else Some defn.Generics)
state
mi, state
)
state
s, state
)
let possibleInterfaceMethods = possibleInterfaceMethods |> Seq.concat |> Seq.toList
match possibleInterfaceMethods with
| [] ->
logger.LogDebug "No interface implementation found either"
state, methodToCall
| [ meth ] ->
logger.LogDebug (
"Exactly one interface implementation found {DeclaringTypeNamespace}.{DeclaringTypeName}.{MethodName} ({MethodGenerics})",
meth.DeclaringType.Namespace,
meth.DeclaringType.Name,
meth.Name,
meth.Generics
)
state, meth
| _ -> failwith "TODO: handle overloads"
| _, _, true
| _, false, _
| Some _, _, _ -> state, methodToCall
// Helper to pop and coerce a single argument
let popAndCoerceArg zeroType methodState =
let value, newState = MethodState.popFromStack methodState
EvalStackValue.toCliTypeCoerced zeroType value, newState
// Collect arguments based on calling convention
let args, afterPop =
if methodToCall.IsStatic then
// Static method: pop args in reverse order
let args = ImmutableArray.CreateBuilder methodToCall.Parameters.Length
let mutable currentState = activeMethodState
for i = methodToCall.Parameters.Length - 1 downto 0 do
let arg, newState = popAndCoerceArg argZeroObjects.[i] currentState
args.Add arg
currentState <- newState
args.Reverse ()
args.ToImmutable (), currentState
else
// Instance method: handle `this` pointer
let argCount = methodToCall.Parameters.Length
let args = ImmutableArray.CreateBuilder (argCount + 1)
let mutable currentState = activeMethodState
match wasConstructing with
| Some _ ->
// Constructor: `this` is on top of stack, by our own odd little calling convention
// where Newobj puts the object pointer on top
let thisArg, newState =
popAndCoerceArg
(CliType.RuntimePointer (CliRuntimePointer.Managed ManagedPointerSource.Null))
currentState
currentState <- newState
// Pop remaining args in reverse
for i = argCount - 1 downto 0 do
let arg, newState = popAndCoerceArg argZeroObjects.[i] currentState
args.Add arg
currentState <- newState
args.Add thisArg
args.Reverse ()
args.ToImmutable (), currentState
| None ->
// Regular instance method: args then `this`
for i = argCount - 1 downto 0 do
let arg, newState = popAndCoerceArg argZeroObjects.[i] currentState
args.Add arg
currentState <- newState
let thisArg, newState =
popAndCoerceArg
(CliType.RuntimePointer (CliRuntimePointer.Managed ManagedPointerSource.Null))
currentState
args.Add thisArg
currentState <- newState
args.Reverse ()
args.ToImmutable (), currentState
// Helper to create new frame with assembly loading
let rec createNewFrame state =
let returnInfo =
Some
{
JumpTo = threadState.ActiveMethodState
WasInitialisingType = wasInitialising
WasConstructingObj = wasConstructing
}
match
MethodState.Empty
state.ConcreteTypes
baseClassTypes
state._LoadedAssemblies
(state.ActiveAssembly thread)
methodToCall
methodGenerics
args
returnInfo
with
| Ok frame -> state, frame
| Error toLoad ->
let state' =
(state, toLoad)
||> List.fold (fun s (asmRef : WoofWare.PawPrint.AssemblyReference) ->
let s, _, _ =
IlMachineState.loadAssembly
loggerFactory
(state.LoadedAssembly methodToCall.DeclaringType.Assembly |> Option.get)
(fst asmRef.Handle)
s
s
)
createNewFrame state'
let state, newFrame = createNewFrame state
let oldFrame =
if wasClassConstructor || not advanceProgramCounterOfCaller then
afterPop
else
afterPop |> MethodState.advanceProgramCounter
let newThreadState =
{ threadState with
MethodStates = threadState.MethodStates.Add(newFrame).SetItem (threadState.ActiveMethodState, oldFrame)
ActiveMethodState = threadState.MethodStates.Length
}
{ state with
ThreadState = state.ThreadState |> Map.add thread newThreadState
}
let rec loadClass
(loggerFactory : ILoggerFactory)
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
(ty : ConcreteTypeHandle)
(currentThread : ThreadId)
(state : IlMachineState)
: StateLoadResult
=
let logger = loggerFactory.CreateLogger "LoadClass"
match TypeInitTable.tryGet ty state.TypeInitTable with
| Some TypeInitState.Initialized ->
// Type already initialized; nothing to do
StateLoadResult.NothingToDo state
| Some (TypeInitState.InProgress tid) when tid = currentThread ->
// We're already initializing this type on this thread; just proceed with the initialisation, no extra
// class loading required.
StateLoadResult.NothingToDo state
| Some (TypeInitState.InProgress _) ->
// This is usually signalled by WhatWeDid.Blocked
failwith
"TODO: cross-thread class init synchronization unimplemented - this thread has to wait for the other thread to finish initialisation"
| None ->
// We have work to do!
// Look up the concrete type from the handle
let concreteType =
match AllConcreteTypes.lookup ty state.ConcreteTypes with
| Some ct -> ct
| None -> failwith $"ConcreteTypeHandle {ty} not found in ConcreteTypes mapping"
let state, origAssyName =
state.WithThreadSwitchedToAssembly concreteType.Assembly currentThread
let sourceAssembly = state.LoadedAssembly concreteType.Assembly |> Option.get
let typeDef =
match sourceAssembly.TypeDefs.TryGetValue concreteType.Definition.Get with
| false, _ ->
failwith
$"Failed to find type definition {concreteType.Definition.Get} in {concreteType.Assembly.FullName}"
| true, v -> v
logger.LogDebug ("Resolving type {TypeDefNamespace}.{TypeDefName}", typeDef.Namespace, typeDef.Name)
// First mark as in-progress to detect cycles
let state = state.WithTypeBeginInit currentThread ty
// Check if the type has a base type that needs initialization
let firstDoBaseClass =
match typeDef.BaseType with
| Some baseTypeInfo ->
// Determine if base type is in the same or different assembly
match baseTypeInfo with
| BaseTypeInfo.ForeignAssemblyType _ -> failwith "TODO"
//logger.LogDebug (
// "Resolved base type of {TypeDefNamespace}.{TypeDefName} to foreign assembly {ForeignAssemblyName}",
// typeDef.Namespace,
// typeDef.Name,
// baseAssemblyName.Name
//)
//match loadClass loggerFactory baseTypeHandle baseAssemblyName currentThread state with
//| FirstLoadThis state -> Error state
//| NothingToDo state -> Ok state
| BaseTypeInfo.TypeDef typeDefinitionHandle ->
logger.LogDebug (
"Resolved base type of {TypeDefNamespace}.{TypeDefName} to this assembly, typedef",
typeDef.Namespace,
typeDef.Name
)
let baseTypeDefn =
DumpedAssembly.typeInfoToTypeDefn' baseClassTypes state._LoadedAssemblies typeDef
// Concretize the base type
let state, baseTypeHandle =
IlMachineState.concretizeType
loggerFactory
baseClassTypes
state
sourceAssembly.Name
concreteType.Generics
// TODO: surely we have generics in scope here?
ImmutableArray.Empty
baseTypeDefn
// Recursively load the base class
match loadClass loggerFactory baseClassTypes baseTypeHandle currentThread state with
| FirstLoadThis state -> Error state
| NothingToDo state -> Ok state
| BaseTypeInfo.TypeRef typeReferenceHandle ->
let state, assy, targetType =
// TypeRef won't have any generics; it would be a TypeSpec if it did
IlMachineState.resolveType
loggerFactory
typeReferenceHandle
ImmutableArray.Empty
(state.ActiveAssembly currentThread)
state
logger.LogDebug (
"Resolved base type of {TypeDefNamespace}.{TypeDefName} to a typeref in assembly {ResolvedAssemblyName}, {BaseTypeNamespace}.{BaseTypeName}",
typeDef.Namespace,
typeDef.Name,
assy.Name.Name,
targetType.Namespace,
targetType.Name
)
// Create a TypeDefn from the resolved TypeRef
let baseTypeDefn =
targetType
|> DumpedAssembly.typeInfoToTypeDefn baseClassTypes state._LoadedAssemblies
// Concretize the base type
let state, baseTypeHandle =
IlMachineState.concretizeType
loggerFactory
baseClassTypes
state
sourceAssembly.Name
concreteType.Generics
// TODO: surely we have generics in scope here?
ImmutableArray.Empty
baseTypeDefn
// Recursively load the base class
match loadClass loggerFactory baseClassTypes baseTypeHandle currentThread state with
| FirstLoadThis state -> Error state
| NothingToDo state -> Ok state
| BaseTypeInfo.TypeSpec typeSpecificationHandle ->
failwith "TODO: TypeSpec base type loading unimplemented"
| None -> Ok state // No base type (or it's System.Object)
match firstDoBaseClass with
| Error state -> FirstLoadThis state
| Ok state ->
// TODO: also need to initialise all interfaces implemented by the type
// Find the class constructor (.cctor) if it exists
let cctor =
typeDef.Methods
|> List.tryFind (fun method -> method.Name = ".cctor" && method.IsStatic && method.Parameters.IsEmpty)
match cctor with
| Some cctorMethod ->
// Call the class constructor! Note that we *don't* use `callMethodInActiveAssembly`, because that
// performs class loading, but we're already in the middle of loading this class.
// TODO: factor out the common bit.
let currentThreadState = state.ThreadState.[currentThread]
// Convert the method's type generics from TypeDefn to ConcreteTypeHandle
let cctorMethodWithTypeGenerics =
cctorMethod
|> MethodInfo.mapTypeGenerics (fun (par, _) -> concreteType.Generics.[par.SequenceNumber])
// Convert method generics (should be empty for cctor)
let cctorMethodWithMethodGenerics =
cctorMethodWithTypeGenerics
|> MethodInfo.mapMethodGenerics (fun _ -> failwith "cctor cannot be generic")
// Convert method signature from TypeDefn to ConcreteTypeHandle using concretization
let state, convertedSignature =
cctorMethodWithMethodGenerics.Signature
|> TypeMethodSignature.map
state
(fun state typeDefn ->
IlMachineState.concretizeType
loggerFactory
baseClassTypes
state
concreteType.Assembly
concreteType.Generics
// no method generics for cctor
ImmutableArray.Empty
typeDefn
)
// Convert method instructions (local variables)
let state, convertedInstructions =
match cctorMethodWithMethodGenerics.Instructions with
| None -> state, None
| Some methodInstr ->
let state, convertedLocalVars =
match methodInstr.LocalVars with
| None -> state, None
| Some localVars ->
// Concretize each local variable type
let state, convertedVars =
((state, []), localVars)
||> Seq.fold (fun (state, acc) typeDefn ->
let state, handle =
IlMachineState.concretizeType
loggerFactory
baseClassTypes
state
concreteType.Assembly
concreteType.Generics
ImmutableArray.Empty // no method generics for cctor
typeDefn
state, handle :: acc
)
|> Tuple.rmap ImmutableArray.CreateRange
state, Some convertedVars
state, Some (MethodInstructions.setLocalVars convertedLocalVars methodInstr)
let fullyConvertedMethod =
MethodInfo.setMethodVars convertedInstructions convertedSignature cctorMethodWithMethodGenerics
callMethod
loggerFactory
baseClassTypes
(Some ty)
None
true
true
false
// constructor is surely not generic
ImmutableArray.Empty
fullyConvertedMethod
currentThread
currentThreadState
state
|> FirstLoadThis
| None ->
// No constructor, just continue.
// Mark the type as initialized.
let state = state.WithTypeEndInit currentThread ty
// Restore original assembly context if needed
state.WithThreadSwitchedToAssembly origAssyName currentThread
|> fst
|> NothingToDo
let ensureTypeInitialised
(loggerFactory : ILoggerFactory)
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
(thread : ThreadId)
(ty : ConcreteTypeHandle)
(state : IlMachineState)
: IlMachineState * WhatWeDid
=
match TypeInitTable.tryGet ty state.TypeInitTable with
| None ->
match loadClass loggerFactory baseClassTypes ty thread state with
| NothingToDo state -> state, WhatWeDid.Executed
| FirstLoadThis state -> state, WhatWeDid.SuspendedForClassInit
| Some TypeInitState.Initialized -> state, WhatWeDid.Executed
| Some (InProgress threadId) ->
if threadId = thread then
// II.10.5.3.2: avoid the deadlock by simply proceeding.
state, WhatWeDid.Executed
else
state, WhatWeDid.BlockedOnClassInit threadId
/// It may be useful to *not* advance the program counter of the caller, e.g. if you're using `callMethodInActiveAssembly`
/// as a convenient way to move to a different method body rather than to genuinely perform a call.
/// (Delegates do this, for example: we get a call to invoke the delegate, and then we implement the delegate as
/// another call to its function pointer.)
let callMethodInActiveAssembly
(loggerFactory : ILoggerFactory)
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
(thread : ThreadId)
(performInterfaceResolution : bool)
(advanceProgramCounterOfCaller : bool)
(methodGenerics : TypeDefn ImmutableArray option)
(methodToCall : WoofWare.PawPrint.MethodInfo<TypeDefn, GenericParamFromMetadata, TypeDefn>)
(weAreConstructingObj : ManagedHeapAddress option)
(typeArgsFromMetadata : TypeDefn ImmutableArray option)
(state : IlMachineState)
: IlMachineState * WhatWeDid
=
let threadState = state.ThreadState.[thread]
let state, concretizedMethod, declaringTypeHandle =
IlMachineState.concretizeMethodForExecution
loggerFactory
baseClassTypes
thread
methodToCall
methodGenerics
typeArgsFromMetadata
state
let state, typeInit =
ensureTypeInitialised loggerFactory baseClassTypes thread declaringTypeHandle state
match typeInit with
| WhatWeDid.Executed ->
callMethod
loggerFactory
baseClassTypes
None
weAreConstructingObj
performInterfaceResolution
false
advanceProgramCounterOfCaller
concretizedMethod.Generics
concretizedMethod
thread
threadState
state,
WhatWeDid.Executed
| _ -> state, typeInit

View File

@@ -0,0 +1,14 @@
namespace WoofWare.PawPrint
open System.Collections.Immutable
[<RequireQualifiedAccess>]
module internal ImmutableArray =
let map (f : 'a -> 'b) (arr : ImmutableArray<'a>) : ImmutableArray<'b> =
let b = ImmutableArray.CreateBuilder ()
for i in arr do
b.Add (f i)
b.ToImmutable ()

View File

@@ -0,0 +1,572 @@
namespace WoofWare.PawPrint
open System
open System.Collections.Immutable
open Microsoft.Extensions.Logging
[<RequireQualifiedAccess>]
module Intrinsics =
let private safeIntrinsics =
[
// The IL implementation is fine: https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs#L677
"System.Private.CoreLib", "Unsafe", "AsRef"
// https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/String.cs#L739-L750
"System.Private.CoreLib", "String", "get_Length"
// https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/ArgumentNullException.cs#L54
"System.Private.CoreLib", "ArgumentNullException", "ThrowIfNull"
// https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/coreclr/System.Private.CoreLib/src/System/Type.CoreCLR.cs#L82
"System.Private.CoreLib", "Type", "GetTypeFromHandle"
// https://github.com/dotnet/runtime/blob/108fa7856efcfd39bc991c2d849eabbf7ba5989c/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs#L161
"System.Private.CoreLib", "ReadOnlySpan`1", "get_Length"
// https://github.com/dotnet/runtime/blob/9e5e6aa7bc36aeb2a154709a9d1192030c30a2ef/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs#L153
"System.Private.CoreLib", "RuntimeHelpers", "CreateSpan"
// https://github.com/dotnet/runtime/blob/d258af50034c192bf7f0a18856bf83d2903d98ae/src/libraries/System.Private.CoreLib/src/System/Math.cs#L127
// https://github.com/dotnet/runtime/blob/d258af50034c192bf7f0a18856bf83d2903d98ae/src/libraries/System.Private.CoreLib/src/System/Math.cs#L137
"System.Private.CoreLib", "Math", "Abs"
// https://github.com/dotnet/runtime/blob/d258af50034c192bf7f0a18856bf83d2903d98ae/src/libraries/System.Private.CoreLib/src/System/Math.cs#L965C10-L1062C19
"System.Private.CoreLib", "Math", "Max"
// https://github.com/dotnet/runtime/blob/d258af50034c192bf7f0a18856bf83d2903d98ae/src/libraries/System.Private.CoreLib/src/System/Buffer.cs#L150
"System.Private.CoreLib", "Buffer", "Memmove"
]
|> Set.ofList
type private RefTypeProcessingStatus =
| InProgress
| Completed of bool
let rec private containsRefType
(loggerFactory : ILoggerFactory)
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
(state : IlMachineState)
(seenSoFar : ImmutableDictionary<TypeInfo<TypeDefn, TypeDefn>, RefTypeProcessingStatus>)
(td : TypeInfo<TypeDefn, TypeDefn>)
: IlMachineState * ImmutableDictionary<_, RefTypeProcessingStatus> * bool
=
match seenSoFar.TryGetValue td with
| true, InProgress ->
// We've hit a cycle. Optimistically assume this path does not introduce a reference type.
// If another path finds a reference type, its 'true' will override this.
state, seenSoFar, false
| true, Completed v ->
// We've already calculated this; return the memoized result.
state, seenSoFar, v
| false, _ ->
// Check if this type itself is a reference type.
let baseType =
td.BaseType
|> DumpedAssembly.resolveBaseType baseClassTypes state._LoadedAssemblies td.Assembly
match baseType with
| ResolvedBaseType.Delegate
| ResolvedBaseType.Object ->
// Short-circuit: if the type itself is a reference type, we're done.
let seenSoFar = seenSoFar.Add (td, Completed true)
state, seenSoFar, true
| ResolvedBaseType.Enum
| ResolvedBaseType.ValueType ->
// It's a value type, so we must check its fields.
// Mark as in progress before recursing.
let seenSoFarWithInProgress = seenSoFar.Add (td, InProgress)
let stateAfterFieldResolution, nonStaticFields =
((state, []), td.Fields)
||> List.fold (fun (currentState, acc) field ->
if field.IsStatic then
currentState, acc
else
// TODO: generics
let newState, _, info =
IlMachineState.resolveTypeFromDefn
loggerFactory
baseClassTypes
field.Signature
ImmutableArray.Empty
ImmutableArray.Empty
(currentState.LoadedAssembly (td.Assembly) |> Option.get)
currentState
newState, info :: acc
)
// Recurse through the fields, correctly propagating state.
let finalState, finalSeenSoFar, fieldsContainRefType =
((stateAfterFieldResolution, seenSoFarWithInProgress, false), nonStaticFields)
||> List.fold (fun (currentState, currentSeenSoFar, currentResult) field ->
if currentResult then
(currentState, currentSeenSoFar, true) // Short-circuit
else
let newState, newSeenSoFar, fieldResult =
containsRefType loggerFactory baseClassTypes currentState currentSeenSoFar field
(newState, newSeenSoFar, currentResult || fieldResult)
)
// Mark as completed with the final result before returning.
let finalSeenSoFar = finalSeenSoFar.SetItem (td, Completed fieldsContainRefType)
finalState, finalSeenSoFar, fieldsContainRefType
let call
(loggerFactory : ILoggerFactory)
(baseClassTypes : BaseClassTypes<_>)
(methodToCall : WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>)
(currentThread : ThreadId)
(state : IlMachineState)
: IlMachineState option
=
let callerAssy =
state.ThreadState.[currentThread].MethodState.ExecutingMethod.DeclaringType.Assembly
if
methodToCall.DeclaringType.Assembly.Name = "System.Private.CoreLib"
&& methodToCall.DeclaringType.Name = "Volatile"
then
// These are all safely implemented in IL, just inefficient.
// https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/Threading/Volatile.cs#L13
None
elif
Set.contains
(methodToCall.DeclaringType.Assembly.Name, methodToCall.DeclaringType.Name, methodToCall.Name)
safeIntrinsics
then
None
else
// In general, some implementations are in:
// https://github.com/dotnet/runtime/blob/108fa7856efcfd39bc991c2d849eabbf7ba5989c/src/coreclr/tools/Common/TypeSystem/IL/Stubs/UnsafeIntrinsics.cs#L192
match methodToCall.DeclaringType.Assembly.Name, methodToCall.DeclaringType.Name, methodToCall.Name with
| "System.Private.CoreLib", "Type", "get_TypeHandle" ->
// TODO: check return type is RuntimeTypeHandle
match methodToCall.Signature.ParameterTypes with
| _ :: _ -> failwith "bad signature Type.get_TypeHandle"
| _ -> ()
// https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/Type.cs#L470
// TODO: check return type is RuntimeTypeHandle
match methodToCall.Signature.ParameterTypes with
| _ :: _ -> failwith "bad signature Type.get_TypeHandle"
| _ -> ()
// no args, returns RuntimeTypeHandle, a struct with a single field (a RuntimeType class)
// https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L18
// The thing on top of the stack will be a RuntimeType.
let arg, state = IlMachineState.popEvalStack currentThread state
let arg =
let rec go (arg : EvalStackValue) =
match arg with
| EvalStackValue.UserDefinedValueType vt ->
match vt.Fields with
| [ field ] -> go field.ContentsEval
| _ -> failwith $"TODO: %O{vt}"
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE"
| EvalStackValue.ObjectRef addr
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> Some addr
| s -> failwith $"TODO: called with unrecognised arg %O{s}"
go arg
let state =
let vt =
// https://github.com/dotnet/runtime/blob/2b21c73fa2c32fa0195e4a411a435dda185efd08/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs#L92
{
Name = "m_type"
Contents = CliType.ObjectRef arg
Offset = None
}
|> List.singleton
|> CliValueType.OfFields Layout.Default
IlMachineState.pushToEvalStack (CliType.ValueType vt) currentThread state
|> IlMachineState.advanceProgramCounter currentThread
Some state
| "System.Private.CoreLib", "Unsafe", "AsPointer" ->
// Method signature: 1 generic parameter, we take a Byref of that parameter, and return a TypeDefn.Pointer(Void)
let arg, state = IlMachineState.popEvalStack currentThread state
let toPush =
match arg with
| EvalStackValue.ManagedPointer ptr -> CliRuntimePointer.Managed ptr
| x -> failwith $"TODO: Unsafe.AsPointer(%O{x})"
IlMachineState.pushToEvalStack (CliType.RuntimePointer toPush) currentThread state
|> IlMachineState.advanceProgramCounter currentThread
|> Some
| "System.Private.CoreLib", "BitConverter", "SingleToInt32Bits" ->
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
| [ ConcreteSingle state.ConcreteTypes ], ConcreteInt32 state.ConcreteTypes -> ()
| _ -> failwith "bad signature BitConverter.SingleToInt32Bits"
let arg, state = IlMachineState.popEvalStack currentThread state
let result =
match arg with
| EvalStackValue.Float f -> BitConverter.SingleToInt32Bits (float32<float> f) |> EvalStackValue.Int32
| _ -> failwith "TODO"
state
|> IlMachineState.pushToEvalStack' result currentThread
|> IlMachineState.advanceProgramCounter currentThread
|> Some
| "System.Private.CoreLib", "BitConverter", "Int32BitsToSingle" ->
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
| [ ConcreteInt32 state.ConcreteTypes ], ConcreteSingle state.ConcreteTypes -> ()
| _ -> failwith "bad signature BitConverter.Int64BitsToSingle"
let arg, state = IlMachineState.popEvalStack currentThread state
let arg =
match arg with
| EvalStackValue.Int32 i -> i
| _ -> failwith "$TODO: {arr}"
let result =
BitConverter.Int32BitsToSingle arg |> CliNumericType.Float32 |> CliType.Numeric
state
|> IlMachineState.pushToEvalStack result currentThread
|> IlMachineState.advanceProgramCounter currentThread
|> Some
| "System.Private.CoreLib", "BitConverter", "Int64BitsToDouble" ->
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
| [ ConcreteInt64 state.ConcreteTypes ], ConcreteDouble state.ConcreteTypes -> ()
| _ -> failwith "bad signature BitConverter.Int64BitsToDouble"
let arg, state = IlMachineState.popEvalStack currentThread state
let arg =
match arg with
| EvalStackValue.Int64 i -> i
| _ -> failwith "$TODO: {arr}"
let result =
BitConverter.Int64BitsToDouble arg |> CliNumericType.Float64 |> CliType.Numeric
state
|> IlMachineState.pushToEvalStack result currentThread
|> IlMachineState.advanceProgramCounter currentThread
|> Some
| "System.Private.CoreLib", "BitConverter", "DoubleToInt64Bits" ->
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
| [ ConcreteDouble state.ConcreteTypes ], ConcreteInt64 state.ConcreteTypes -> ()
| _ -> failwith "bad signature BitConverter.DoubleToInt64Bits"
let arg, state = IlMachineState.popEvalStack currentThread state
let result =
match arg with
| EvalStackValue.Float f -> BitConverter.DoubleToInt64Bits f |> EvalStackValue.Int64
| _ -> failwith "TODO"
state
|> IlMachineState.pushToEvalStack' result currentThread
|> IlMachineState.advanceProgramCounter currentThread
|> Some
| "System.Private.CoreLib", "BitConverter", "SingleToUInt32Bits" ->
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
| [ ConcreteSingle state.ConcreteTypes ], ConcreteUInt32 state.ConcreteTypes -> ()
| _ -> failwith "bad signature BitConverter.SingleToUInt32Bits"
let arg, state = IlMachineState.popEvalStack currentThread state
let result =
match arg with
| EvalStackValue.Float f ->
BitConverter.SingleToUInt32Bits (float32<float> f)
|> int<uint32>
|> EvalStackValue.Int32
| _ -> failwith "TODO"
state
|> IlMachineState.pushToEvalStack' result currentThread
|> IlMachineState.advanceProgramCounter currentThread
|> Some
| "System.Private.CoreLib", "BitConverter", "UInt32BitsToSingle" ->
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
| [ ConcreteUInt32 state.ConcreteTypes ], ConcreteSingle state.ConcreteTypes -> ()
| _ -> failwith "bad signature BitConverter.UInt32BitsToSingle"
let arg, state = IlMachineState.popEvalStack currentThread state
let result =
match arg with
| EvalStackValue.Int32 f ->
BitConverter.UInt32BitsToSingle (uint32<int> f)
|> float<float32>
|> EvalStackValue.Float
| _ -> failwith "TODO"
state
|> IlMachineState.pushToEvalStack' result currentThread
|> IlMachineState.advanceProgramCounter currentThread
|> Some
| "System.Private.CoreLib", "String", "Equals" ->
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
| [ ConcreteString state.ConcreteTypes ; ConcreteString state.ConcreteTypes ],
ConcreteBool state.ConcreteTypes ->
let arg1, state = IlMachineState.popEvalStack currentThread state
let arg1 =
match arg1 with
| EvalStackValue.ObjectRef h
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap h) -> h
| EvalStackValue.Int32 _
| EvalStackValue.Int64 _
| EvalStackValue.Float _ -> failwith $"this isn't a string! {arg1}"
| _ -> failwith $"TODO: %O{arg1}"
let arg2, state = IlMachineState.popEvalStack currentThread state
let arg2 =
match arg2 with
| EvalStackValue.ObjectRef h
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap h) -> h
| EvalStackValue.Int32 _
| EvalStackValue.Int64 _
| EvalStackValue.Float _ -> failwith $"this isn't a string! {arg2}"
| _ -> failwith $"TODO: %O{arg2}"
if arg1 = arg2 then
state
|> IlMachineState.pushToEvalStack (CliType.ofBool true) currentThread
|> IlMachineState.advanceProgramCounter currentThread
|> Some
else
let arg1 = ManagedHeap.get arg1 state.ManagedHeap
let arg2 = ManagedHeap.get arg2 state.ManagedHeap
if
AllocatedNonArrayObject.DereferenceField "_firstChar" arg1
<> AllocatedNonArrayObject.DereferenceField "_firstChar" arg2
then
state
|> IlMachineState.pushToEvalStack (CliType.ofBool false) currentThread
|> IlMachineState.advanceProgramCounter currentThread
|> Some
else
failwith "TODO"
| _ -> None
| "System.Private.CoreLib", "Unsafe", "ReadUnaligned" ->
let ptr, state = IlMachineState.popEvalStack currentThread state
let v : CliType =
let rec go ptr =
match ptr with
| EvalStackValue.ManagedPointer src -> IlMachineState.dereferencePointer state src
| EvalStackValue.NativeInt src -> failwith "TODO"
| EvalStackValue.ObjectRef ptr -> failwith "TODO"
| EvalStackValue.UserDefinedValueType {
Fields = [ f ]
} -> go f.ContentsEval
| EvalStackValue.UserDefinedValueType {
Fields = []
} -> failwith "unexpected no-fields object"
| EvalStackValue.UserDefinedValueType {
Fields = _ :: _ :: _
} ->
failwith "TODO: check overlapping fields to see if this is a pointer"
| EvalStackValue.Int32 _
| EvalStackValue.Int64 _
| EvalStackValue.Float _ -> failwith $"this isn't a pointer! {ptr}"
go ptr
let state =
state
|> IlMachineState.pushToEvalStack v currentThread
|> IlMachineState.advanceProgramCounter currentThread
Some state
| "System.Private.CoreLib", "String", "op_Implicit" ->
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
| [ par ], ret ->
let par = state.ConcreteTypes |> AllConcreteTypes.lookup par |> Option.get
let ret = state.ConcreteTypes |> AllConcreteTypes.lookup ret |> Option.get
if
par.Namespace = "System"
&& par.Name = "String"
&& ret.Namespace = "System"
&& ret.Name = "ReadOnlySpan`1"
then
match ret.Generics |> Seq.toList with
| [ gen ] ->
let gen = state.ConcreteTypes |> AllConcreteTypes.lookup gen |> Option.get
if gen.Namespace = "System" && gen.Name = "Char" then
// This is just an optimisation
// https://github.com/dotnet/runtime/blob/ab105b51f8b50ec5567d7cfe9001ca54dd6f64c3/src/libraries/System.Private.CoreLib/src/System/String.cs#L363-L366
None
else
failwith "TODO: unexpected params to String.op_Implicit"
| _ -> failwith "TODO: unexpected params to String.op_Implicit"
else
failwith "TODO: unexpected params to String.op_Implicit"
| _ -> failwith "TODO: unexpected params to String.op_Implicit"
| "System.Private.CoreLib", "RuntimeHelpers", "IsReferenceOrContainsReferences" ->
// https://github.com/dotnet/runtime/blob/1d1bf92fcf43aa6981804dc53c5174445069c9e4/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs#L207
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
| [], ConcreteBool state.ConcreteTypes -> ()
| _ -> failwith "bad signature for System.Private.CoreLib.RuntimeHelpers.IsReferenceOrContainsReference"
let arg = Seq.exactlyOne methodToCall.Generics
let state, result =
// Some types appear circular, because they're hardcoded in the runtime. We have to special-case them.
match arg with
| ConcreteChar state.ConcreteTypes -> state, false
| _ ->
let generic = AllConcreteTypes.lookup arg state.ConcreteTypes
let generic =
match generic with
| None -> failwith "somehow have not already concretised type in IsReferenceOrContainsReferences"
| Some generic -> generic
let td =
state.LoadedAssembly generic.Assembly
|> Option.get
|> fun a -> a.TypeDefs.[generic.Definition.Get]
let baseType =
td.BaseType
|> DumpedAssembly.resolveBaseType baseClassTypes state._LoadedAssemblies generic.Assembly
match baseType with
| ResolvedBaseType.Enum
| ResolvedBaseType.ValueType ->
td
|> TypeInfo.mapGeneric (fun (par, _) -> TypeDefn.GenericTypeParameter par.SequenceNumber)
|> containsRefType loggerFactory baseClassTypes state ImmutableDictionary.Empty
|> fun (state, _, result) -> state, result
| ResolvedBaseType.Object
| ResolvedBaseType.Delegate -> state, true
let state =
state
|> IlMachineState.pushToEvalStack (CliType.ofBool result) currentThread
|> IlMachineState.advanceProgramCounter currentThread
Some state
| "System.Private.CoreLib", "RuntimeHelpers", "InitializeArray" ->
// https://github.com/dotnet/runtime/blob/9e5e6aa7bc36aeb2a154709a9d1192030c30a2ef/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs#L18
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
| [ ConcreteNonGenericArray state.ConcreteTypes ; ConcreteRuntimeFieldHandle state.ConcreteTypes ],
ConcreteVoid state.ConcreteTypes -> ()
| _ -> failwith "bad signature for System.Private.CoreLib.RuntimeHelpers.InitializeArray"
failwith "TODO: if arg0 is null, throw NRE"
failwith "TODO: if arg1 contains null handle, throw ArgumentException"
failwith "TODO: array initialization"
| "System.Private.CoreLib", "Unsafe", "As" ->
// https://github.com/dotnet/runtime/blob/721fdf6dcb032da1f883d30884e222e35e3d3c99/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs#L64
let inputType, retType =
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
| [ input ], ret -> input, ret
| _ -> failwith "bad signature Unsafe.As"
let from, to_ =
match Seq.toList methodToCall.Generics with
| [ from ; to_ ] -> from, to_
| _ -> failwith "bad generics"
if ConcreteTypeHandle.Byref to_ <> retType then
failwith "bad return type"
if ConcreteTypeHandle.Byref from <> inputType then
failwith "bad input type"
let from =
match AllConcreteTypes.lookup from state.ConcreteTypes with
| None -> failwith "somehow have not concretised input type"
| Some t -> t
let to_ =
match AllConcreteTypes.lookup to_ state.ConcreteTypes with
| None -> failwith "somehow have not concretised ret type"
| Some t -> t
let inputAddr, state = IlMachineState.popEvalStack currentThread state
let ptr =
match inputAddr with
| EvalStackValue.Int32 _
| EvalStackValue.Int64 _
| EvalStackValue.Float _ -> failwith "expected pointer type"
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
| EvalStackValue.ManagedPointer src ->
ManagedPointerSource.InterpretedAsType (src, to_)
|> EvalStackValue.ManagedPointer
| EvalStackValue.ObjectRef addr ->
ManagedPointerSource.InterpretedAsType (ManagedPointerSource.Heap addr, to_)
|> EvalStackValue.ManagedPointer
| EvalStackValue.UserDefinedValueType evalStackValueUserType -> failwith "todo"
let state =
state
|> IlMachineState.pushToEvalStack' ptr currentThread
|> IlMachineState.advanceProgramCounter currentThread
Some state
| "System.Private.CoreLib", "Unsafe", "SizeOf" ->
// https://github.com/dotnet/runtime/blob/721fdf6dcb032da1f883d30884e222e35e3d3c99/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs#L51
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
| [], ConcreteInt32 state.ConcreteTypes -> ()
| _ -> failwith "bad signature Unsafe.SizeOf"
let ty =
match Seq.toList methodToCall.Generics with
| [ ty ] -> ty
| _ -> failwith "bad generics"
let zero, state = IlMachineState.cliTypeZeroOfHandle state baseClassTypes ty
let size = CliType.sizeOf zero
state
|> IlMachineState.pushToEvalStack (CliType.Numeric (CliNumericType.Int32 size)) currentThread
|> IlMachineState.advanceProgramCounter currentThread
|> Some
| "System.Private.CoreLib", "RuntimeHelpers", "CreateSpan" ->
// https://github.com/dotnet/runtime/blob/9e5e6aa7bc36aeb2a154709a9d1192030c30a2ef/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs#L153
None
| "System.Private.CoreLib", "Type", "op_Equality" ->
// https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/libraries/System.Private.CoreLib/src/System/Type.cs#L703
None
| "System.Private.CoreLib", "MemoryMarshal", "GetArrayDataReference" ->
// https://github.com/dotnet/runtime/blob/d258af50034c192bf7f0a18856bf83d2903d98ae/src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/MemoryMarshal.CoreCLR.cs#L20
let generic = Seq.exactlyOne methodToCall.Generics
match methodToCall.Signature.ParameterTypes, methodToCall.Signature.ReturnType with
| [ ConcreteGenericArray state.ConcreteTypes generic ], ConcreteByref t when t = generic -> ()
| _ -> failwith "bad signature MemoryMarshal.GetArrayDataReference"
let arr, state = IlMachineState.popEvalStack currentThread state
let toPush =
match arr with
| EvalStackValue.Int32 _
| EvalStackValue.Int64 _
| EvalStackValue.Float _ -> failwith "expected reference"
| EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
| EvalStackValue.ObjectRef addr
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) ->
if not (state.ManagedHeap.Arrays.ContainsKey addr) then
failwith "array not found"
EvalStackValue.ManagedPointer (ManagedPointerSource.ArrayIndex (addr, 0))
| EvalStackValue.UserDefinedValueType evalStackValueUserType -> failwith "todo"
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: raise NRE"
| EvalStackValue.ManagedPointer _ -> failwith "todo"
state
|> IlMachineState.pushToEvalStack' toPush currentThread
|> IlMachineState.advanceProgramCounter currentThread
|> Some
| a, b, c -> failwith $"TODO: implement JIT intrinsic {a}.{b}.{c}"
|> Option.map (fun s -> s.WithThreadSwitchedToAssembly callerAssy currentThread |> fst)

12
WoofWare.PawPrint/List.fs Normal file
View File

@@ -0,0 +1,12 @@
namespace WoofWare.PawPrint
[<RequireQualifiedAccess>]
module List =
let replaceWhere (f : 'a -> 'a option) (l : 'a list) : 'a list =
([], l)
||> List.fold (fun acc x ->
match f x with
| None -> x :: acc
| Some y -> y :: acc
)
|> List.rev

View File

@@ -8,11 +8,20 @@ type SyncBlock =
type AllocatedNonArrayObject =
{
Fields : Map<string, CliType>
Type : WoofWare.PawPrint.TypeInfoCrate
// TODO: this is a slightly odd domain; the same type for value types as class types!
Contents : CliValueType
ConcreteType : ConcreteTypeHandle
SyncBlock : SyncBlock
}
static member DereferenceField (name : string) (f : AllocatedNonArrayObject) : CliType =
CliValueType.DereferenceField name f.Contents
static member SetField (name : string) (v : CliType) (f : AllocatedNonArrayObject) : AllocatedNonArrayObject =
{ f with
Contents = CliValueType.WithFieldSet name v f.Contents
}
type AllocatedArray =
{
Length : int
@@ -29,7 +38,9 @@ type ManagedHeap =
StringArrayData : ImmutableArray<char>
}
static member Empty : ManagedHeap =
[<RequireQualifiedAccess>]
module ManagedHeap =
let empty : ManagedHeap =
{
NonArrayObjects = Map.empty
FirstAvailableAddress = 1
@@ -37,12 +48,12 @@ type ManagedHeap =
StringArrayData = ImmutableArray.Empty
}
static member GetSyncBlock (addr : ManagedHeapAddress) (heap : ManagedHeap) : SyncBlock =
let getSyncBlock (addr : ManagedHeapAddress) (heap : ManagedHeap) : SyncBlock =
match heap.NonArrayObjects.TryGetValue addr with
| false, _ -> failwith "TODO: getting sync block of array"
| true, v -> v.SyncBlock
static member SetSyncBlock (addr : ManagedHeapAddress) (syncValue : SyncBlock) (heap : ManagedHeap) : ManagedHeap =
let setSyncBlock (addr : ManagedHeapAddress) (syncValue : SyncBlock) (heap : ManagedHeap) : ManagedHeap =
match heap.NonArrayObjects.TryGetValue addr with
| false, _ -> failwith "TODO: locked on an array object"
| true, v ->
@@ -55,7 +66,7 @@ type ManagedHeap =
NonArrayObjects = heap.NonArrayObjects |> Map.add addr newV
}
static member AllocateArray (ty : AllocatedArray) (heap : ManagedHeap) : ManagedHeapAddress * ManagedHeap =
let allocateArray (ty : AllocatedArray) (heap : ManagedHeap) : ManagedHeapAddress * ManagedHeap =
let addr = heap.FirstAvailableAddress
let heap =
@@ -68,7 +79,7 @@ type ManagedHeap =
ManagedHeapAddress addr, heap
static member AllocateString (len : int) (heap : ManagedHeap) : int * ManagedHeap =
let allocateString (len : int) (heap : ManagedHeap) : int * ManagedHeap =
let addr = heap.StringArrayData.Length
let heap =
@@ -80,7 +91,7 @@ type ManagedHeap =
addr, heap
static member SetStringData (addr : int) (contents : string) (heap : ManagedHeap) : ManagedHeap =
let setStringData (addr : int) (contents : string) (heap : ManagedHeap) : ManagedHeap =
let newArr =
(heap.StringArrayData, seq { 0 .. contents.Length - 1 })
||> Seq.fold (fun data count -> data.SetItem (addr + count, contents.[count]))
@@ -92,11 +103,7 @@ type ManagedHeap =
heap
static member AllocateNonArray
(ty : AllocatedNonArrayObject)
(heap : ManagedHeap)
: ManagedHeapAddress * ManagedHeap
=
let allocateNonArray (ty : AllocatedNonArrayObject) (heap : ManagedHeap) : ManagedHeapAddress * ManagedHeap =
let addr = heap.FirstAvailableAddress
let heap =
@@ -109,7 +116,7 @@ type ManagedHeap =
ManagedHeapAddress addr, heap
static member GetArrayValue (alloc : ManagedHeapAddress) (offset : int) (heap : ManagedHeap) : CliType =
let getArrayValue (alloc : ManagedHeapAddress) (offset : int) (heap : ManagedHeap) : CliType =
match heap.Arrays.TryGetValue alloc with
| false, _ -> failwith "TODO: array not on heap"
| true, arr ->
@@ -119,13 +126,17 @@ type ManagedHeap =
arr.Elements.[offset]
static member SetArrayValue
(alloc : ManagedHeapAddress)
(offset : int)
(v : CliType)
(heap : ManagedHeap)
: ManagedHeap
=
let get (alloc : ManagedHeapAddress) (heap : ManagedHeap) : AllocatedNonArrayObject =
// TODO: arrays too
heap.NonArrayObjects.[alloc]
let set (alloc : ManagedHeapAddress) (v : AllocatedNonArrayObject) (heap : ManagedHeap) : ManagedHeap =
// TODO: arrays too
{ heap with
NonArrayObjects = heap.NonArrayObjects |> Map.add alloc v
}
let setArrayValue (alloc : ManagedHeapAddress) (offset : int) (v : CliType) (heap : ManagedHeap) : ManagedHeap =
let newArrs =
heap.Arrays
|> Map.change

View File

@@ -137,7 +137,7 @@ and MethodState =
/// If `method` is static, `args` must be of length numParams.
static member Empty
(concreteTypes : AllConcreteTypes)
(corelib : BaseClassTypes<DumpedAssembly>)
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
(loadedAssemblies : ImmutableDictionary<string, DumpedAssembly>)
(containingAssembly : DumpedAssembly)
(method : WoofWare.PawPrint.MethodInfo<ConcreteTypeHandle, ConcreteTypeHandle, ConcreteTypeHandle>)
@@ -172,7 +172,7 @@ and MethodState =
// Note: This assumes all types have already been concretized
// If this fails with "ConcreteTypeHandle not found", it means
// we need to ensure types are concretized before creating the MethodState
let zero, _ = CliType.zeroOf concreteTypes loadedAssemblies corelib var
let zero, _ = CliType.zeroOf concreteTypes loadedAssemblies baseClassTypes var
result.Add zero
result.ToImmutable ()

View File

@@ -1,5 +1,6 @@
namespace WoofWare.PawPrint
open System
open Microsoft.Extensions.Logging
[<RequireQualifiedAccess>]
@@ -37,17 +38,6 @@ module NullaryIlOp =
| LdindR4 -> CliType.Numeric (CliNumericType.Float32 0.0f)
| LdindR8 -> CliType.Numeric (CliNumericType.Float64 0.0)
/// Retrieve a value from a pointer
let private loadFromPointerSource (state : IlMachineState) (src : ManagedPointerSource) : CliType =
match src with
| ManagedPointerSource.Null -> failwith "unexpected null pointer in Ldind operation"
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables.[int<uint16> whichVar]
| ManagedPointerSource.Heap managedHeapAddress -> failwith "TODO: Heap pointer dereferencing not implemented"
| ManagedPointerSource.ArrayIndex _ -> failwith "TODO: array index pointer dereferencing not implemented"
// Unified Ldind implementation
let private executeLdind
(targetType : LdindTargetType)
@@ -59,11 +49,11 @@ module NullaryIlOp =
let loadedValue =
match popped with
| EvalStackValue.ManagedPointer src -> loadFromPointerSource state src
| EvalStackValue.ManagedPointer src -> IlMachineState.dereferencePointer state src
| EvalStackValue.NativeInt nativeIntSource ->
failwith $"TODO: Native int pointer dereferencing not implemented for {targetType}"
| EvalStackValue.ObjectRef managedHeapAddress ->
failwith "TODO: Object reference dereferencing not implemented"
IlMachineState.dereferencePointer state (ManagedPointerSource.Heap managedHeapAddress)
| other -> failwith $"Unexpected eval stack value for Ldind operation: {other}"
let loadedValue = loadedValue |> EvalStackValue.ofCliType
@@ -92,6 +82,7 @@ module NullaryIlOp =
| EvalStackValue.ManagedPointer src ->
match src with
| ManagedPointerSource.Null -> failwith "TODO: throw NullReferenceException"
| ManagedPointerSource.InterpretedAsType (src, ty) -> failwith "TODO"
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
failwith "unexpected - can we really write to an argument?"
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
@@ -123,6 +114,7 @@ module NullaryIlOp =
}
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
| ManagedPointerSource.ArrayIndex _ -> failwith "todo"
| ManagedPointerSource.Field (managedPointerSource, fieldName) -> failwith "todo"
| EvalStackValue.ObjectRef managedHeapAddress -> failwith "todo"
let internal ldElem
@@ -471,7 +463,25 @@ module NullaryIlOp =
|> IlMachineState.advanceProgramCounter currentThread
|> Tuple.withRight WhatWeDid.Executed
|> ExecutionResult.Stepped
| Mul_ovf -> failwith "TODO: Mul_ovf unimplemented"
| Mul_ovf ->
let val1, state = IlMachineState.popEvalStack currentThread state
let val2, state = IlMachineState.popEvalStack currentThread state
let result =
try
BinaryArithmetic.execute ArithmeticOperation.mulOvf val1 val2 |> Ok
with :? OverflowException as e ->
Error e
let state =
match result with
| Ok result -> state |> IlMachineState.pushToEvalStack' result currentThread
| Error excToThrow -> failwith "TODO: throw OverflowException"
state
|> IlMachineState.advanceProgramCounter currentThread
|> Tuple.withRight WhatWeDid.Executed
|> ExecutionResult.Stepped
| Mul_ovf_un -> failwith "TODO: Mul_ovf_un unimplemented"
| Div -> failwith "TODO: Div unimplemented"
| Div_un -> failwith "TODO: Div_un unimplemented"
@@ -682,6 +692,7 @@ module NullaryIlOp =
let popped =
match popped with
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> failwith "TODO: throw NRE"
| EvalStackValue.ObjectRef addr
| EvalStackValue.ManagedPointer (ManagedPointerSource.Heap addr) -> addr
| _ -> failwith $"can't get len of {popped}"
@@ -775,7 +786,7 @@ module NullaryIlOp =
match
ExceptionHandling.findExceptionHandler
currentMethodState.IlOpIndex
heapObject.Type
heapObject.ConcreteType
currentMethodState.ExecutingMethod
state._LoadedAssemblies
with
@@ -889,16 +900,7 @@ module NullaryIlOp =
let referenced =
match addr with
| EvalStackValue.ManagedPointer src ->
match src with
| ManagedPointerSource.Null -> failwith "TODO: throw NRE"
| ManagedPointerSource.LocalVariable (sourceThread, methodFrame, whichVar) ->
state.ThreadState.[sourceThread].MethodStates.[methodFrame].LocalVariables
.[int<uint16> whichVar]
| ManagedPointerSource.Argument (sourceThread, methodFrame, whichVar) ->
state.ThreadState.[sourceThread].MethodStates.[methodFrame].Arguments.[int<uint16> whichVar]
| ManagedPointerSource.Heap managedHeapAddress -> failwith "todo"
| ManagedPointerSource.ArrayIndex _ -> failwith "todo"
| EvalStackValue.ManagedPointer src -> IlMachineState.dereferencePointer state src
| a -> failwith $"TODO: {a}"
let state =
@@ -926,6 +928,8 @@ module NullaryIlOp =
arr
(EvalStackValue.toCliTypeCoerced (CliType.ObjectRef None) value)
index
| ManagedPointerSource.Field _ -> failwith "TODO"
| ManagedPointerSource.InterpretedAsType (src, ty) -> failwith "TODO"
| addr -> failwith $"TODO: {addr}"
let state = state |> IlMachineState.advanceProgramCounter currentThread

View File

@@ -3,21 +3,33 @@ namespace WoofWare.PawPrint
open System
open System.Collections.Immutable
open System.IO
open System.Reflection.Metadata
open Microsoft.Extensions.Logging
[<RequireQualifiedAccess>]
module Program =
/// Returns the pointer to the resulting array on the heap.
let allocateArgs
(loggerFactory : ILoggerFactory)
(args : string list)
(corelib : BaseClassTypes<DumpedAssembly>)
(state : IlMachineState)
: ManagedHeapAddress * IlMachineState
=
let state, stringType =
DumpedAssembly.typeInfoToTypeDefn' corelib state._LoadedAssemblies corelib.String
|> IlMachineState.concretizeType
loggerFactory
corelib
state
corelib.Corelib.Name
ImmutableArray.Empty
ImmutableArray.Empty
let argsAllocations, state =
(state, args)
||> Seq.mapFold (fun state arg ->
IlMachineState.allocateManagedObject corelib.String (failwith "TODO: assert fields and populate") state
IlMachineState.allocateManagedObject stringType (failwith "TODO: assert fields and populate") state
// TODO: set the char values in memory
)
@@ -28,7 +40,7 @@ module Program =
((state, 0), argsAllocations)
||> Seq.fold (fun (state, i) arg ->
let state =
IlMachineState.setArrayValue arrayAllocation (CliType.OfManagedObject arg) i state
IlMachineState.setArrayValue arrayAllocation (CliType.ofManagedObject arg) i state
state, i + 1
)
@@ -94,7 +106,7 @@ module Program =
(currentAssembly : DumpedAssembly)
(continueWithGeneric :
IlMachineState
-> TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>
-> TypeInfo<GenericParamFromMetadata, TypeDefn>
-> DumpedAssembly
-> IlMachineState * BaseClassTypes<DumpedAssembly> option)
(continueWithResolved :
@@ -112,7 +124,7 @@ module Program =
let rec go state =
// Resolve the type reference to find which assembly it's in
match
Assembly.resolveTypeRef state._LoadedAssemblies currentAssembly typeRef ImmutableArray.Empty
Assembly.resolveTypeRef state._LoadedAssemblies currentAssembly ImmutableArray.Empty typeRef
with
| TypeResolutionResult.FirstLoadAssy assyRef ->
// Need to load this assembly first
@@ -145,7 +157,7 @@ module Program =
let rec findCoreLibraryAssemblyFromGeneric
(state : IlMachineState)
(currentType : TypeInfo<WoofWare.PawPrint.GenericParameter, TypeDefn>)
(currentType : TypeInfo<GenericParamFromMetadata, TypeDefn>)
(currentAssembly : DumpedAssembly)
=
match currentType.BaseType with
@@ -186,7 +198,7 @@ module Program =
// Use the original method from metadata, but convert FakeUnit to TypeDefn
let rawMainMethod =
mainMethodFromMetadata
|> MethodInfo.mapTypeGenerics (fun i _ -> TypeDefn.GenericTypeParameter i)
|> MethodInfo.mapTypeGenerics (fun (i, _) -> TypeDefn.GenericTypeParameter i.SequenceNumber)
let state, concretizedMainMethod, _ =
IlMachineState.concretizeMethodWithTypeGenerics
@@ -233,7 +245,7 @@ module Program =
| Some baseTypes ->
let rawMainMethod =
mainMethodFromMetadata
|> MethodInfo.mapTypeGenerics (fun i _ -> TypeDefn.GenericTypeParameter i)
|> MethodInfo.mapTypeGenerics (fun (i, _) -> TypeDefn.GenericTypeParameter i.SequenceNumber)
IlMachineState.concretizeMethodWithTypeGenerics
loggerFactory
@@ -249,7 +261,11 @@ module Program =
let rec loadInitialState (state : IlMachineState) =
match
state
|> IlMachineState.loadClass loggerFactory (Option.toObj baseClassTypes) mainTypeHandle mainThread
|> IlMachineStateExecution.loadClass
loggerFactory
(Option.toObj baseClassTypes)
mainTypeHandle
mainThread
with
| StateLoadResult.NothingToDo ilMachineState -> ilMachineState
| StateLoadResult.FirstLoadThis ilMachineState -> loadInitialState ilMachineState
@@ -271,7 +287,7 @@ module Program =
let arrayAllocation, state =
match mainMethodFromMetadata.Signature.ParameterTypes |> Seq.toList with
| [ TypeDefn.OneDimensionalArrayLowerBoundZero (TypeDefn.PrimitiveType PrimitiveType.String) ] ->
allocateArgs argv baseClassTypes state
allocateArgs loggerFactory argv baseClassTypes state
| _ -> failwith "Main method must take an array of strings; other signatures not yet implemented"
match mainMethodFromMetadata.Signature.ReturnType with
@@ -297,7 +313,7 @@ module Program =
dumped
concretizedMainMethod
ImmutableArray.Empty
(ImmutableArray.Create (CliType.OfManagedObject arrayAllocation))
(ImmutableArray.Create (CliType.ofManagedObject arrayAllocation))
None
with
| Ok s -> s
@@ -313,7 +329,7 @@ module Program =
{ state with
ThreadState = state.ThreadState |> Map.add mainThread threadState
}
|> IlMachineState.ensureTypeInitialised loggerFactory baseClassTypes mainThread mainTypeHandle
|> IlMachineStateExecution.ensureTypeInitialised loggerFactory baseClassTypes mainThread mainTypeHandle
match init with
| WhatWeDid.SuspendedForClassInit -> failwith "TODO: suspended for class init"

View File

@@ -1,18 +1,10 @@
namespace WoofWare.PawPrint
type CanonicalTypeIdentity =
{
AssemblyFullName : string
FullyQualifiedTypeName : string
Generics : CanonicalTypeIdentity list
}
type TypeHandleRegistry =
private
{
TypeHandleToType : Map<int64<typeHandle>, CanonicalTypeIdentity>
TypeToHandle : Map<CanonicalTypeIdentity, int64<typeHandle> * ManagedHeapAddress>
NextHandle : int64<typeHandle>
TypeHandleToType : Map<ManagedHeapAddress, ConcreteTypeHandle>
TypeToHandle : Map<ConcreteTypeHandle, ManagedHeapAddress>
}
[<RequireQualifiedAccess>]
@@ -21,44 +13,59 @@ module TypeHandleRegistry =
{
TypeHandleToType = Map.empty
TypeToHandle = Map.empty
NextHandle = 1L<typeHandle>
}
/// Returns an allocated System.RuntimeType as well.
let getOrAllocate
(allocState : 'allocState)
(allocate : (string * CliType) list -> 'allocState -> ManagedHeapAddress * 'allocState)
(def : CanonicalTypeIdentity)
(allocate : CliValueType -> 'allocState -> ManagedHeapAddress * 'allocState)
(def : ConcreteTypeHandle)
(reg : TypeHandleRegistry)
: (int64<typeHandle> * ManagedHeapAddress) * TypeHandleRegistry * 'allocState
: ManagedHeapAddress * TypeHandleRegistry * 'allocState
=
match Map.tryFind def reg.TypeToHandle with
| Some v -> v, reg, allocState
| None ->
let handle = reg.NextHandle
// Here follows the class System.RuntimeType, which is an internal class type with a constructor
// whose only purpose is to throw.
// https://github.com/dotnet/runtime/blob/2b21c73fa2c32fa0195e4a411a435dda185efd08/src/libraries/System.Private.CoreLib/src/System/RuntimeType.cs#L14
// and https://github.com/dotnet/runtime/blob/f0168ee80ba9aca18a7e7140b2bb436defda623c/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs#L44
let fields =
[
// for the GC, I think?
"m_keepalive", CliType.ObjectRef None
{
Name = "m_keepalive"
Contents = CliType.ObjectRef None
Offset = None
}
// TODO: this is actually a System.IntPtr https://github.com/dotnet/runtime/blob/ec11903827fc28847d775ba17e0cd1ff56cfbc2e/src/coreclr/nativeaot/Runtime.Base/src/System/Primitives.cs#L339
"m_cache", CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.Verbatim 0L))
"m_handle", CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.TypeHandlePtr handle))
{
Name = "m_cache"
Contents = CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.Verbatim 0L))
Offset = None
}
{
Name = "m_handle"
Contents = CliType.Numeric (CliNumericType.NativeInt (NativeIntSource.TypeHandlePtr def))
Offset = None
}
// This is the const -1, apparently?!
// https://github.com/dotnet/runtime/blob/f0168ee80ba9aca18a7e7140b2bb436defda623c/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs#L2496
"GenericParameterCountAny", CliType.Numeric (CliNumericType.Int32 -1)
{
Name = "GenericParameterCountAny"
Contents = CliType.Numeric (CliNumericType.Int32 -1)
Offset = None
}
]
|> CliValueType.OfFields Layout.Default
let alloc, state = allocate fields allocState
let reg =
{
NextHandle = handle + 1L<typeHandle>
TypeHandleToType = reg.TypeHandleToType |> Map.add handle def
TypeToHandle = reg.TypeToHandle |> Map.add def (handle, alloc)
TypeHandleToType = reg.TypeHandleToType |> Map.add alloc def
TypeToHandle = reg.TypeToHandle |> Map.add def alloc
}
(handle, alloc), reg, state
alloc, reg, state

View File

@@ -107,8 +107,8 @@ module internal UnaryConstIlOp =
| EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i)
| EvalStackValue.Float f -> failwith "TODO: Brfalse_s float semantics undocumented"
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false
| EvalStackValue.ObjectRef _
| EvalStackValue.ManagedPointer _ -> true
| EvalStackValue.ObjectRef _ -> failwith "TODO: Brfalse_s ObjectRef comparison unimplemented"
| EvalStackValue.UserDefinedValueType _ ->
failwith "TODO: Brfalse_s UserDefinedValueType comparison unimplemented"
@@ -129,8 +129,8 @@ module internal UnaryConstIlOp =
| EvalStackValue.NativeInt i -> not (NativeIntSource.isZero i)
| EvalStackValue.Float f -> failwith "TODO: Brtrue_s float semantics undocumented"
| EvalStackValue.ManagedPointer ManagedPointerSource.Null -> false
| EvalStackValue.ObjectRef _
| EvalStackValue.ManagedPointer _ -> true
| EvalStackValue.ObjectRef _ -> failwith "TODO: Brtrue_s ObjectRef comparison unimplemented"
| EvalStackValue.UserDefinedValueType _ ->
failwith "TODO: Brtrue_s UserDefinedValueType comparison unimplemented"
@@ -212,7 +212,33 @@ module internal UnaryConstIlOp =
else
id
|> Tuple.withRight WhatWeDid.Executed
| Blt_s b -> failwith "TODO: Blt_s unimplemented"
| Blt_s b ->
let value2, state = IlMachineState.popEvalStack currentThread state
let value1, state = IlMachineState.popEvalStack currentThread state
let isLessThan =
match value1, value2 with
| EvalStackValue.Int32 v1, EvalStackValue.Int32 v2 -> v1 < v2
| EvalStackValue.Int32 i, EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
| EvalStackValue.Int32 i, _ -> failwith $"invalid comparison, {i} with {value2}"
| EvalStackValue.Int64 v1, EvalStackValue.Int64 v2 -> v1 < v2
| EvalStackValue.Int64 i, _ -> failwith $"invalid comparison, {i} with {value2}"
| EvalStackValue.NativeInt nativeIntSource, _ -> failwith "todo"
| EvalStackValue.Float v1, EvalStackValue.Float v2 -> failwith "todo"
| EvalStackValue.Float f, _ -> failwith $"invalid comparison, {f} with {value2}"
| EvalStackValue.ManagedPointer v1, EvalStackValue.ManagedPointer v2 -> failwith "todo"
| EvalStackValue.ManagedPointer v1, _ -> failwith $"invalid comparison, {v1} with {value2}"
| EvalStackValue.ObjectRef _, _ -> failwith "todo"
| EvalStackValue.UserDefinedValueType _, _ ->
failwith "unexpectedly tried to compare user-defined value type"
state
|> IlMachineState.advanceProgramCounter currentThread
|> if isLessThan then
IlMachineState.jumpProgramCounter currentThread (int<int8> b)
else
id
|> Tuple.withRight WhatWeDid.Executed
| Ble_s b ->
let value2, state = IlMachineState.popEvalStack currentThread state
let value1, state = IlMachineState.popEvalStack currentThread state
@@ -240,7 +266,33 @@ module internal UnaryConstIlOp =
else
id
|> Tuple.withRight WhatWeDid.Executed
| Bgt_s b -> failwith "TODO: Bgt_s unimplemented"
| Bgt_s b ->
let value2, state = IlMachineState.popEvalStack currentThread state
let value1, state = IlMachineState.popEvalStack currentThread state
let isGreaterThan =
match value1, value2 with
| EvalStackValue.Int32 v1, EvalStackValue.Int32 v2 -> v1 > v2
| EvalStackValue.Int32 i, EvalStackValue.NativeInt nativeIntSource -> failwith "todo"
| EvalStackValue.Int32 i, _ -> failwith $"invalid comparison, {i} with {value2}"
| EvalStackValue.Int64 v1, EvalStackValue.Int64 v2 -> v1 > v2
| EvalStackValue.Int64 i, _ -> failwith $"invalid comparison, {i} with {value2}"
| EvalStackValue.NativeInt nativeIntSource, _ -> failwith "todo"
| EvalStackValue.Float v1, EvalStackValue.Float v2 -> failwith "todo"
| EvalStackValue.Float f, _ -> failwith $"invalid comparison, {f} with {value2}"
| EvalStackValue.ManagedPointer v1, EvalStackValue.ManagedPointer v2 -> failwith "todo"
| EvalStackValue.ManagedPointer v1, _ -> failwith $"invalid comparison, {v1} with {value2}"
| EvalStackValue.ObjectRef _, _ -> failwith "todo"
| EvalStackValue.UserDefinedValueType _, _ ->
failwith "unexpectedly tried to compare user-defined value type"
state
|> IlMachineState.advanceProgramCounter currentThread
|> if isGreaterThan then
IlMachineState.jumpProgramCounter currentThread (int<int8> b)
else
id
|> Tuple.withRight WhatWeDid.Executed
| Bge_s b ->
let value2, state = IlMachineState.popEvalStack currentThread state
let value1, state = IlMachineState.popEvalStack currentThread state
@@ -351,7 +403,35 @@ module internal UnaryConstIlOp =
else
id
|> Tuple.withRight WhatWeDid.Executed
| Bne_un_s b -> failwith "TODO: Bne_un_s unimplemented"
| Bne_un_s b ->
// Table III.4
let value2, state = IlMachineState.popEvalStack currentThread state
let value1, state = IlMachineState.popEvalStack currentThread state
let isNotEqual =
match value1, value2 with
| EvalStackValue.Int32 v1, EvalStackValue.Int32 v2 -> v1 <> v2
| EvalStackValue.Int32 v1, EvalStackValue.NativeInt v2 -> failwith "TODO"
| EvalStackValue.Int32 v1, _ -> failwith $"invalid comparison, {v1} with {value2}"
| _, EvalStackValue.Int32 v2 -> failwith $"invalid comparison, {value1} with {v2}"
| EvalStackValue.Int64 v1, EvalStackValue.Int64 v2 -> v1 <> v2
| EvalStackValue.Int64 v1, _ -> failwith $"invalid comparison, {v1} with {value2}"
| _, EvalStackValue.Int64 v2 -> failwith $"invalid comparison, {value1} with {v2}"
| EvalStackValue.Float v1, EvalStackValue.Float v2 -> v1 <> v2
| _, EvalStackValue.Float v2 -> failwith $"invalid comparison, {value1} with {v2}"
| EvalStackValue.Float v1, _ -> failwith $"invalid comparison, {v1} with {value2}"
| EvalStackValue.NativeInt v1, EvalStackValue.NativeInt v2 -> v1 <> v2
| EvalStackValue.ManagedPointer ptr1, EvalStackValue.ManagedPointer ptr2 -> ptr1 <> ptr2
| EvalStackValue.ObjectRef ptr1, EvalStackValue.ObjectRef ptr2 -> ptr1 <> ptr2
| _, _ -> failwith $"TODO {value1} {value2} (see table III.4)"
state
|> IlMachineState.advanceProgramCounter currentThread
|> if isNotEqual then
IlMachineState.jumpProgramCounter currentThread (int b)
else
id
|> Tuple.withRight WhatWeDid.Executed
| Bge_un_s b ->
let value2, state = IlMachineState.popEvalStack currentThread state
let value1, state = IlMachineState.popEvalStack currentThread state

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,15 @@
namespace WoofWare.PawPrint
open System.Collections.Immutable
open System.Reflection
open Microsoft.Extensions.Logging
[<RequireQualifiedAccess>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module internal UnaryStringTokenIlOp =
let execute
(baseClassTypes : BaseClassTypes<'a>)
(loggerFactory : ILoggerFactory)
(baseClassTypes : BaseClassTypes<DumpedAssembly>)
(op : UnaryStringTokenIlOp)
(sh : StringToken)
(state : IlMachineState)
@@ -25,6 +28,8 @@ module internal UnaryStringTokenIlOp =
let state = state |> IlMachineState.setStringData dataAddr stringToAllocate
// String type is:
// https://github.com/dotnet/runtime/blob/f0168ee80ba9aca18a7e7140b2bb436defda623c/src/libraries/System.Private.CoreLib/src/System/String.cs#L26
let stringInstanceFields =
baseClassTypes.String.Fields
|> List.choose (fun field ->
@@ -47,12 +52,30 @@ module internal UnaryStringTokenIlOp =
let fields =
[
"_firstChar", CliType.OfChar state.ManagedHeap.StringArrayData.[dataAddr]
"_stringLength", CliType.Numeric (CliNumericType.Int32 stringToAllocate.Length)
{
Name = "_firstChar"
Contents = CliType.ofChar state.ManagedHeap.StringArrayData.[dataAddr]
Offset = None
}
{
Name = "_stringLength"
Contents = CliType.Numeric (CliNumericType.Int32 stringToAllocate.Length)
Offset = None
}
]
|> CliValueType.OfFields Layout.Default
let addr, state =
IlMachineState.allocateManagedObject baseClassTypes.String fields state
let state, stringType =
DumpedAssembly.typeInfoToTypeDefn' baseClassTypes state._LoadedAssemblies baseClassTypes.String
|> IlMachineState.concretizeType
loggerFactory
baseClassTypes
state
baseClassTypes.Corelib.Name
ImmutableArray.Empty
ImmutableArray.Empty
let addr, state = IlMachineState.allocateManagedObject stringType fields state
addr,
{ state with

View File

@@ -7,11 +7,15 @@
<ItemGroup>
<Compile Include="Tuple.fs" />
<Compile Include="List.fs" />
<Compile Include="ImmutableArray.fs" />
<Compile Include="Result.fs" />
<Compile Include="Constants.fs" />
<Compile Include="Corelib.fs" />
<Compile Include="AbstractMachineDomain.fs" />
<Compile Include="BasicCliType.fs" />
<Compile Include="TypeHandleRegistry.fs" />
<Compile Include="FieldHandleRegistry.fs" />
<Compile Include="ManagedHeap.fs" />
<Compile Include="TypeInitialisation.fs" />
<Compile Include="Exceptions.fs" />
@@ -21,6 +25,8 @@
<Compile Include="MethodState.fs" />
<Compile Include="ThreadState.fs" />
<Compile Include="IlMachineState.fs" />
<Compile Include="Intrinsics.fs" />
<Compile Include="IlMachineStateExecution.fs" />
<Compile Include="NullaryIlOp.fs" />
<Compile Include="UnaryMetadataIlOp.fs" />
<Compile Include="UnaryStringTokenIlOp.fs" />

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1750836778,
"narHash": "sha256-sRLyRiC7TezRbbjGJwUFOgb2xMbSr3wQ0oJKfYlQ6s0=",
"lastModified": 1755736253,
"narHash": "sha256-jlIQRypNhB1PcB1BE+expE4xZeJxzoAGr1iUbHQta8s=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d7bb1922f0bb3d0c990f56f9cdb767fdb20a5f22",
"rev": "596312aae91421d6923f18cecce934a7d3bfd6b8",
"type": "github"
},
"original": {

View File

@@ -21,8 +21,8 @@
},
{
"pname": "FSharp.Core",
"version": "9.0.202",
"hash": "sha256-64Gub0qemmCoMa1tDus6TeTuB1+5sHfE6KD2j4o84mA="
"version": "9.0.303",
"hash": "sha256-AxR6wqodeU23KOTgkUfIgbavgbcSuzD4UBP+tiFydgA="
},
{
"pname": "FsUnit",
@@ -31,33 +31,33 @@
},
{
"pname": "Microsoft.ApplicationInsights",
"version": "2.22.0",
"hash": "sha256-mUQ63atpT00r49ca50uZu2YCiLg3yd6r3HzTryqcuEA="
"version": "2.23.0",
"hash": "sha256-5sf3bg7CZZjHseK+F3foOchEhmVeioePxMZVvS6Rjb0="
},
{
"pname": "Microsoft.AspNetCore.App.Ref",
"version": "8.0.17",
"hash": "sha256-NNGXfUV5RVt1VqLI99NlHoBkt2Vv/Hg3TAHzm8nGM8M="
"version": "8.0.19",
"hash": "sha256-QySX2bih1UvwmLcn9cy1j+RuvZZwbcFKggL5Y/WcTnw="
},
{
"pname": "Microsoft.AspNetCore.App.Runtime.linux-arm64",
"version": "8.0.17",
"hash": "sha256-Eunz3nZF5r8a9nqwdeorQPgqd5G+Z4ddofMeAk6VmnA="
"version": "8.0.19",
"hash": "sha256-69S+Ywyc5U8PDsVkkCVvZdHOgWb6ZZ3+f4UA0MLOLFI="
},
{
"pname": "Microsoft.AspNetCore.App.Runtime.linux-x64",
"version": "8.0.17",
"hash": "sha256-SWdah72tC5i2CQL4mRUYfHC0Kh8+C2jiskIIeC74smY="
"version": "8.0.19",
"hash": "sha256-u50rdLuoADSDCthx2Fg+AnT192TalHhFrzFCfMgmTn4="
},
{
"pname": "Microsoft.AspNetCore.App.Runtime.osx-arm64",
"version": "8.0.17",
"hash": "sha256-y55EGfQ2FzrY2X5+Ne5N3dqi5WNHkFTGVW1hEMrh6OI="
"version": "8.0.19",
"hash": "sha256-QAKu2xD4UQ4+gX79ynNQ0aA07D+EW6Ke0jRiTZne8CY="
},
{
"pname": "Microsoft.AspNetCore.App.Runtime.osx-x64",
"version": "8.0.17",
"hash": "sha256-uRCCNPevPemvKIuUxy/VtQlgskChbiAauMWVK/xhoc0="
"version": "8.0.19",
"hash": "sha256-v5lzESMpodrH2grgk8ojA6BLDUfyxX5r6YY5Pgq61tA="
},
{
"pname": "Microsoft.CodeAnalysis.Analyzers",
@@ -106,48 +106,48 @@
},
{
"pname": "Microsoft.NETCore.App.Host.linux-arm64",
"version": "8.0.17",
"hash": "sha256-pzOqFCd+UrIXmWGDfds5GxkI+Asjx30yFtLIuHFu/h4="
"version": "8.0.19",
"hash": "sha256-R86Kqzi3FUuPZlgj3zNOObLAvXtnGrS2mxsBAxWIZrY="
},
{
"pname": "Microsoft.NETCore.App.Host.linux-x64",
"version": "8.0.17",
"hash": "sha256-AGnEGHcO2hfvChG3xEGOTA6dX4MiYPB7FoBkmWz3dc8="
"version": "8.0.19",
"hash": "sha256-a9t/bX+WIKOu9q2R52b/hPGwOpkAgpYuP42SW2QXTak="
},
{
"pname": "Microsoft.NETCore.App.Host.osx-arm64",
"version": "8.0.17",
"hash": "sha256-fpMzkOWaA3OFNtHsqOk9s9xKVrcrqOyKHxE7jk8hebg="
"version": "8.0.19",
"hash": "sha256-VaeGPR+6ApGNtQpEaky2rdUKd4X/Pp3xFGaSgUfGNiE="
},
{
"pname": "Microsoft.NETCore.App.Host.osx-x64",
"version": "8.0.17",
"hash": "sha256-Hrn01x+S+gnGEEHhr6mN6bPyqVAhp5u3CqgWwQbh4To="
"version": "8.0.19",
"hash": "sha256-hw7WMpTq7o544uSNvWUCIr6IRt5xZOo+eERMnwAbYyk="
},
{
"pname": "Microsoft.NETCore.App.Ref",
"version": "8.0.17",
"hash": "sha256-tKawpjkMjV0ysNIWWrgHTiLxncZJDRNiDkQBwl255l4="
"version": "8.0.19",
"hash": "sha256-4ymel0R1c0HrX0plAWubJPzev52y0Fsx1esyQ1R7bXc="
},
{
"pname": "Microsoft.NETCore.App.Runtime.linux-arm64",
"version": "8.0.17",
"hash": "sha256-FutphE4bEjd8s6ZqpFXrD1zuCDkNCJ7Vnl0pBm86HBA="
"version": "8.0.19",
"hash": "sha256-hqhpd8yT8bv05DhFTuMhfsaSISpLs3t4oM+R/ZkJH80="
},
{
"pname": "Microsoft.NETCore.App.Runtime.linux-x64",
"version": "8.0.17",
"hash": "sha256-6YVEXiJ3b2gZAYri8iSRBdi/J+0DEl7FcwBX6h1Unkg="
"version": "8.0.19",
"hash": "sha256-Ou51zUFTPESAAzP/z0+sLDAAXC54+oDlESBBT12M2lM="
},
{
"pname": "Microsoft.NETCore.App.Runtime.osx-arm64",
"version": "8.0.17",
"hash": "sha256-J3dfDial8GHyKQMFuBNFtOMD/mOK58vjrK2ZtrYObZg="
"version": "8.0.19",
"hash": "sha256-IC/e8AmT9twcXwzFmXAelf4ctMbg4ancKPGrPLFMNn8="
},
{
"pname": "Microsoft.NETCore.App.Runtime.osx-x64",
"version": "8.0.17",
"hash": "sha256-WnkJyhSBHMw/VtLHWy0AFwzzkbIC1YQugFuj3Adg+Ks="
"version": "8.0.19",
"hash": "sha256-Rb0z0PT/FHyk/Fgjj9W3WDpkDMKJoXR9DgHB1cJeZSA="
},
{
"pname": "Microsoft.NETCore.Platforms",
@@ -171,33 +171,38 @@
},
{
"pname": "Microsoft.Testing.Extensions.Telemetry",
"version": "1.5.3",
"hash": "sha256-bIXwPSa3jkr2b6xINOqMUs6/uj/r4oVFM7xq3uVIZDU="
"version": "1.7.3",
"hash": "sha256-Z6WsY2FCUbNnT5HJd7IOrfOvqknVXp6PWzTVeb0idVg="
},
{
"pname": "Microsoft.Testing.Extensions.TrxReport.Abstractions",
"version": "1.5.3",
"hash": "sha256-IfMRfcyaIKEMRtx326ICKtinDBEfGw/Sv8ZHawJ96Yc="
"version": "1.7.3",
"hash": "sha256-PTee04FHyTHx/gF5NLckXuVje807G51MzkPrZ1gkgCw="
},
{
"pname": "Microsoft.Testing.Extensions.VSTestBridge",
"version": "1.5.3",
"hash": "sha256-XpM/yFjhLSsuzyDV+xKubs4V1zVVYiV05E0+N4S1h0g="
"version": "1.7.3",
"hash": "sha256-8d+wZmucfSO7PsviHjVxYB4q6NcjgxvnCUpLePq35sM="
},
{
"pname": "Microsoft.Testing.Platform",
"version": "1.5.3",
"hash": "sha256-y61Iih6w5D79dmrj2V675mcaeIiHoj1HSa1FRit2BLM="
"version": "1.7.3",
"hash": "sha256-cavX11P5o9rooqC3ZHw5h002OKRg2ZNR/VaRwpNTQYA="
},
{
"pname": "Microsoft.Testing.Platform.MSBuild",
"version": "1.5.3",
"hash": "sha256-YspvjE5Jfi587TAfsvfDVJXNrFOkx1B3y1CKV6m7YLY="
"version": "1.7.3",
"hash": "sha256-cREl529UQ/c5atT8KimMgrgNdy6MrAd0sBGT8sXRRPM="
},
{
"pname": "Microsoft.TestPlatform.AdapterUtilities",
"version": "17.13.0",
"hash": "sha256-Vr+3Tad/h/nk7f/5HMExn3HvCGFCarehFAzJSfCBaOc="
},
{
"pname": "Microsoft.TestPlatform.ObjectModel",
"version": "17.12.0",
"hash": "sha256-3XBHBSuCxggAIlHXmKNQNlPqMqwFlM952Av6RrLw1/w="
"version": "17.13.0",
"hash": "sha256-6S0fjfj8vA+h6dJVNwLi6oZhYDO/I/6hBZaq2VTW+Uk="
},
{
"pname": "Microsoft.TestPlatform.ObjectModel",
@@ -231,13 +236,13 @@
},
{
"pname": "NUnit",
"version": "4.3.2",
"hash": "sha256-0RWe8uFoxYp6qhPlDDEghOMcKJgyw2ybvEoAqBLebeE="
"version": "4.4.0",
"hash": "sha256-5geF5QOF+X/WkuCEgkPVKH4AdKx4U0olpU07S8+G3nU="
},
{
"pname": "NUnit3TestAdapter",
"version": "5.0.0",
"hash": "sha256-7jZM4qAbIzne3AcdFfMbvbgogqpxvVe6q2S7Ls8xQy0="
"version": "5.1.0",
"hash": "sha256-5z470sFjV67wGHaw8KfmSloIAYe81Dokp0f8I6zXsDc="
},
{
"pname": "runtime.any.System.Runtime",