├── out └── .gitignore ├── docs ├── content │ └── exout │ │ ├── .gitignore │ │ └── example.runtimeconfig.json ├── Dependencies.fsx ├── style │ └── global.css ├── FSharpIL.Documentation │ ├── FSharpIL.Documentation.fsproj │ ├── Html.fs │ ├── Article.fs │ └── Generator.fs └── FSharpIL.Examples │ ├── FSharpIL.Examples.fsproj │ └── ExampleHelpers.fs ├── README.md ├── global.json ├── .gitattributes ├── test ├── ExpectoEntryPoint.fs ├── FSharpIL.Benchmarks │ ├── Benchmark.fs │ └── FSharpIL.Benchmarks.fsproj ├── FSharpIL.Examples.Tests │ ├── FactorialTests.fs │ ├── CollectionTests.fs │ ├── FractionTests.fs │ ├── FSharpIL.Examples.Tests.fsproj │ └── DelegatesTests.fs ├── FSharpIL.Benchmarks.Memory │ ├── Test.fs │ └── FSharpIL.Benchmarks.Memory.fsproj └── FSharpIL.Properties │ ├── ValidationExpect.fs │ ├── FSharpIL.Properties.fsproj │ ├── Properties.fs │ └── Generate.fs ├── Directory.Build.props ├── src ├── FSharpIL │ ├── Utilities │ │ ├── Result.fs │ │ ├── ObjectPatterns.fs │ │ ├── Round.fs │ │ ├── Flags.fs │ │ ├── Fail.fs │ │ ├── Collections │ │ │ ├── CollectionFail.fs │ │ │ ├── LateInitCollection.fs │ │ │ └── HybridHashSet.fs │ │ ├── Convert.fs │ │ ├── Span.fs │ │ ├── Bytes.fs │ │ └── Compare.fs │ ├── Reading │ │ ├── ReadResult.fs │ │ ├── ByteParser.fs │ │ ├── ErrorHandler.fs │ │ ├── StructureReader.fs │ │ ├── ParsedBlobStream.fs │ │ ├── ParsedUserStringStream.fs │ │ ├── ParseBlob.fsi │ │ ├── ReadPE.fsi │ │ ├── ParsedGuidStream.fs │ │ ├── LengthEncodedStream.fs │ │ ├── ParsedTableRowCounts.fs │ │ ├── ReadCli.fsi │ │ ├── MetadataReader.fs │ │ ├── PEFileReader.fs │ │ ├── ParsedStringsStream.fs │ │ ├── ParsedHeaders.fs │ │ ├── ReadState.fs │ │ └── MetadataTablesReader.fs │ ├── Omitted.fs │ ├── Metadata │ │ ├── Blobs │ │ │ ├── CallingConventionFlags.fs │ │ │ ├── ElemType.fs │ │ │ ├── ElementType.fs │ │ │ └── Offsets.fs │ │ ├── PublicKeyOrToken.fs │ │ ├── FieldMetadataToken.fs │ │ ├── MethodMetadataToken.fs │ │ ├── Tables │ │ │ ├── TableIndex.fs │ │ │ ├── TableFlags.fs │ │ │ ├── TablesHeader.fs │ │ │ └── CodedIndexPatterns.fs │ │ ├── Magic.fs │ │ ├── Headers.fs │ │ ├── TypeMetadataToken.fs │ │ ├── Signatures │ │ │ ├── CustomMod.fs │ │ │ ├── CustomAttrib.fs │ │ │ └── LocalVarSig.fs │ │ ├── MetadataToken.fs │ │ ├── Cil │ │ │ ├── LocalVarIndex.fs │ │ │ └── MethodBody.fs │ │ ├── EntryPointToken.fs │ │ ├── StreamOffsets.fs │ │ ├── MetadataVersion.fs │ │ └── Strings.fs │ ├── Writing │ │ ├── IStreamBuilder.fs │ │ ├── WriteCli.fsi │ │ ├── BuildErrors.fs │ │ ├── SectionBuilder.fs │ │ ├── SectionBuilder.fsi │ │ ├── BuildCli.fsi │ │ ├── WriteIndex.fs │ │ ├── GuidStreamBuilder.fs │ │ ├── StringHelpers.fs │ │ ├── BuildCli.fs │ │ ├── WritePE.fsi │ │ ├── CliMetadataBuilder.fs │ │ ├── StringsStreamBuilder.fs │ │ ├── BlobWriter.fsi │ │ ├── BuildPE.fsi │ │ ├── CoreAssemblyReference.fsi │ │ ├── CoreAssemblyReference.fs │ │ └── UserStringStreamBuilder.fs │ ├── Cli │ │ ├── CustomAttribute.fs │ │ ├── Event.fs │ │ ├── Assembly.fsi │ │ ├── CustomAttribute.fsi │ │ ├── Event.fsi │ │ ├── Property.fsi │ │ ├── Parameter.fs │ │ ├── Parameter.fsi │ │ ├── Property.fs │ │ ├── Assembly.fs │ │ ├── MemberVisibility.fs │ │ ├── Constant.fs │ │ ├── Field.fsi │ │ ├── MemberTok.fsi │ │ ├── Attributes.fs │ │ └── Field.fs │ ├── PortableExecutable │ │ ├── SectionOffset.fs │ │ ├── Alignment.fs │ │ ├── DataDirectories.fs │ │ ├── Rva.fs │ │ ├── FileOffset.fs │ │ ├── PEFile.fs │ │ ├── DefaultHeaders.fs │ │ ├── SectionName.fs │ │ ├── SectionHeader.fs │ │ └── Magic.fs │ ├── IByteWriter.fs │ └── ChunkedMemoryStream.fs └── ilinfo │ ├── Flags.fs │ ├── ilinfo.fsproj │ ├── IndentedTextWriter.fs │ ├── Print.fs │ ├── VisibilityFilter.fs │ └── Program.fs └── .config └── dotnet-tools.json /out/.gitignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | *.nupkg 3 | -------------------------------------------------------------------------------- /docs/content/exout/.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.netmodule 3 | *.xml 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davnavr/FSharpIL/HEAD/README.md -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "5.0.100" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /test/ExpectoEntryPoint.fs: -------------------------------------------------------------------------------- 1 | module FSharpIL.Test 2 | 3 | open Expecto 4 | 5 | [] 6 | let main argv = runTestsInAssemblyWithCLIArgs Seq.empty argv 7 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5.0 5 | $(MSBuildThisFileDirectory) 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/Dependencies.fsx: -------------------------------------------------------------------------------- 1 | // References the scripts and assemblies needed by the documentation files. 2 | #r "nuget: System.Collections.Immutable" 3 | #r "../src/FSharpIL/bin/Release/netstandard2.1/FSharpIL.dll" 4 | -------------------------------------------------------------------------------- /docs/style/global.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | font-family: Arial, Helvetica, sans-serif; 4 | margin: 0; 5 | } 6 | 7 | nav { 8 | /*Flexbox*/ 9 | } 10 | 11 | main { 12 | 13 | } 14 | article { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/FSharpIL/Utilities/Result.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module internal FSharpIL.Result 3 | 4 | let inline any (result: Result<'T, 'T>) = 5 | match result with 6 | | Ok i 7 | | Error i -> i 8 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "fake-cli": { 6 | "version": "5.20.3", 7 | "commands": [ 8 | "fake" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/FSharpIL/Utilities/ObjectPatterns.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module internal FSharpIL.Utilities.ObjectPatterns 3 | 4 | let inline (|ToString|) (value: 'T) = value.ToString() 5 | 6 | let inline (|HashCodeOf|) (value: 'T) = value.GetHashCode() 7 | -------------------------------------------------------------------------------- /docs/content/exout/example.runtimeconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "runtimeOptions": { 3 | "tfm": "net5.0", 4 | "framework": { 5 | "name": "Microsoft.NETCore.App", 6 | "version": "5.0.0" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/ReadResult.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | [] 4 | type internal ReadResult<'State, 'T, 'Error when 'State :> IReadState and 'State : struct> = 5 | | Success of 'T * 'State 6 | | Failure of 'Error 7 | | End 8 | -------------------------------------------------------------------------------- /src/FSharpIL/Utilities/Round.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module internal FSharpIL.Utilities.Round 3 | 4 | /// Rounds up the specified number up to a multiple of another number. 5 | let inline upTo (multiple: ^T) (num: ^T): ^T = 6 | multiple * ((num + multiple - LanguagePrimitives.GenericOne<'T>) / multiple) 7 | -------------------------------------------------------------------------------- /src/FSharpIL/Omitted.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL 2 | 3 | /// Indicates that the value of a field cannot be set. 4 | type [] Omitted = struct end 5 | 6 | (* 7 | [] 8 | type Omitted = | Omitted 9 | *) 10 | 11 | [] 12 | module Omitted = let Omitted = Omitted() 13 | -------------------------------------------------------------------------------- /src/FSharpIL/Utilities/Flags.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module FSharpIL.Utilities.Flags 3 | 4 | /// Determines whether the specified flags are set. 5 | let inline set (flags: 'Enum) (value: 'Enum) = value &&& flags = flags 6 | 7 | let inline anyOfSet (flags: 'Enum) (value: 'Enum) = value &&& flags > Unchecked.defaultof<'Enum> 8 | -------------------------------------------------------------------------------- /test/FSharpIL.Benchmarks/Benchmark.fs: -------------------------------------------------------------------------------- 1 | module FSharpIL.Benchmark 2 | 3 | open BenchmarkDotNet.Running 4 | 5 | [] 6 | let main argv = 7 | let assm = System.Reflection.Assembly.GetExecutingAssembly() 8 | BenchmarkSwitcher 9 | .FromAssembly(assm) 10 | .Run(args = argv) 11 | |> ignore 12 | 0 13 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Blobs/CallingConventionFlags.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata.Blobs 2 | 3 | /// Describes how arguments are passed to a method (II.15.3). 4 | [] 5 | type CallConvFlags = 6 | | HasThis = 0x20uy 7 | | ExplicitThis = 0x40uy 8 | | Default = 0uy 9 | | VarArg = 0x5uy 10 | | Generic = 0x10uy 11 | -------------------------------------------------------------------------------- /src/FSharpIL/Utilities/Fail.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module FSharpIL.Utilities.Fail 3 | 4 | open System 5 | 6 | let inline noImpl msg = raise(NotImplementedException msg) 7 | let inline notSupported msg = raise(NotSupportedException msg) 8 | let inline argOutOfRange argumentName argument message = raise(ArgumentOutOfRangeException(argumentName, argument, message)) 9 | -------------------------------------------------------------------------------- /src/ilinfo/Flags.fs: -------------------------------------------------------------------------------- 1 | namespace ILInfo 2 | 3 | open System.Runtime.CompilerServices 4 | 5 | [] 6 | type IncludeHeaders = 7 | | NoHeaders 8 | | IncludeHeaders 9 | 10 | [] 11 | type IncludeMetadata = 12 | | IncludeMetadata 13 | | NoMetadata 14 | 15 | [] 16 | type OutputKind = 17 | | Console 18 | | File of string 19 | -------------------------------------------------------------------------------- /test/FSharpIL.Examples.Tests/FactorialTests.fs: -------------------------------------------------------------------------------- 1 | module FSharpIL.FactorialTests 2 | 3 | open Expecto 4 | 5 | open Swensen.Unquote 6 | 7 | open Factorial 8 | 9 | [] 10 | let tests = 11 | testList "fraction" [ 12 | testCase "3 factorial is 6" <| fun() -> test <@ CachedFactorial.Calculate 3u = 6u @> 13 | // TODO: Use a property test can be used to test factorial calculation. 14 | ] 15 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/IStreamBuilder.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Writing 2 | 3 | open FSharpIL 4 | open FSharpIL.PortableExecutable 5 | 6 | type internal IStreamBuilder = interface 7 | abstract StreamLength: uint32 voption 8 | abstract StreamName: System.Collections.Immutable.ImmutableArray 9 | abstract Serialize: builder: byref * methodBodiesRva: Rva * embeddedDataRva: Rva -> unit 10 | end 11 | -------------------------------------------------------------------------------- /src/FSharpIL/Utilities/Collections/CollectionFail.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module FSharpIL.Utilities.Collections.CollectionFail 3 | 4 | open FSharpIL.Utilities 5 | 6 | let enumeratorNotStarted() = invalidOp "Cannot retrieve current item, the enumerator must first be started by calling MoveNext" 7 | let enumeratorReachedEnd() = invalidOp "Cannot retrieve current item, the end of the collection has been reached" 8 | let enumeratorCannotReset() = noImpl "Cannot reset enumerator" 9 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/ByteParser.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open FSharpIL 4 | 5 | type IByteParser<'T when 'T : struct> = interface 6 | abstract Parse : buffer: ChunkedMemory -> 'T 7 | abstract Length : uint32 8 | end 9 | 10 | [] 11 | module ByteParser = 12 | let inline length (parser: #IByteParser<_>) = parser.Length 13 | let parse offset (chunks: inref) (parser: #IByteParser<'Result>) = 14 | parser.Parse(chunks.Slice(offset, parser.Length)) 15 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/ErrorHandler.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open FSharpIL.PortableExecutable 4 | 5 | type ErrorHandler<'State> = IReadState -> ReadError -> FileOffset -> 'State -> 'State 6 | 7 | [] 8 | module ErrorHandler = 9 | let inline handle (rstate: #IReadState) error offset ustate (handler: ErrorHandler<_>) = handler rstate error offset ustate 10 | let inline throwOnError (state: IReadState) error offset (_: 'State): 'State = ReadException(state, error, offset) |> raise 11 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/StructureReader.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open FSharpIL.PortableExecutable 4 | 5 | type StructureReader<'Structure, 'State> = ('Structure -> FileOffset -> 'State -> 'State voption) voption 6 | 7 | [] 8 | module internal StructureReader = 9 | let inline read (reader: StructureReader<_, _>) arg offset ustate next = 10 | match reader with 11 | | ValueSome reader' -> 12 | match reader' arg offset ustate with 13 | | ValueSome ustate' -> Success(ustate', next) 14 | | ValueNone -> End 15 | | ValueNone -> Success(ustate, next) 16 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/WriteCli.fsi: -------------------------------------------------------------------------------- 1 | [] 2 | module FSharpIL.Writing.WriteCli 3 | 4 | open FSharpIL 5 | 6 | /// Writes the CLI metadata to the specified section. 7 | /// A builder positioned at the start of the CLI metadata. 8 | /// A relative virtual address pointing to the start of the CLI metadata. 9 | /// The CLI metadata to write to the section. 10 | val metadata : 11 | section: byref -> 12 | cliHeaderRva: FSharpIL.PortableExecutable.Rva -> 13 | builder: CliMetadataBuilder -> 14 | unit 15 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/BuildErrors.fs: -------------------------------------------------------------------------------- 1 | module FSharpIL.Writing.BuildErrors 2 | 3 | open FSharpIL.Metadata 4 | 5 | open FSharpIL.Cli 6 | 7 | /// Error used when an entry point method is added to a generic type. 8 | [] 9 | type GenericTypeCannotHaveEntryPoint = 10 | { Owner: DefinedType 11 | Method: EntryPointMethod } 12 | 13 | override this.ToString() = 14 | sprintf 15 | "Cannot add entry point method %O to the generic type %O, only non-generic types can contain the entry point of the \ 16 | assembly" 17 | this.Method 18 | this.Owner 19 | 20 | interface IValidationError 21 | -------------------------------------------------------------------------------- /test/FSharpIL.Benchmarks.Memory/Test.fs: -------------------------------------------------------------------------------- 1 | module FSharpIL.Test 2 | 3 | open System 4 | 5 | let private (|SelectExample|_|) (success: bool, selection: uint32) = 6 | match selection with 7 | | _ when not success -> None 8 | | 0u -> Some HelloWorld.example 9 | | 1u -> Some CustomCollections.example 10 | | _ -> None 11 | 12 | [] 13 | let main _ = 14 | let input = stdin.ReadLine() 15 | 16 | match UInt32.TryParse input with 17 | | SelectExample example -> 18 | System.Diagnostics.Debugger.Break() 19 | example() |> ignore 20 | 0 21 | | _ -> 22 | eprintfn "Invalid input: %s" input 23 | -1 24 | -------------------------------------------------------------------------------- /docs/FSharpIL.Documentation/FSharpIL.Documentation.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FSharpIL.Documentation 5 | Exe 6 | net5.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/FSharpIL/Utilities/Convert.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module internal FSharpIL.Utilities.Convert 3 | 4 | open System.Runtime.CompilerServices 5 | 6 | let inline (|I4|) num = int32 num 7 | let inline (|U1|) num = uint8 num 8 | let inline (|U2|) num = uint16 num 9 | let inline (|U4|) num = uint32 num 10 | let inline (|U8|) num = uint64 num 11 | 12 | let inline unsafeTo<'From, 'To>(source: 'From) = Unsafe.As<'From, 'To>(&Unsafe.AsRef &source) 13 | 14 | let inline unsafeValueOption<'From, 'To when 'From : not struct and 'To : not struct>(source: 'From voption) = 15 | match source with 16 | | ValueSome o -> ValueSome(Unsafe.As<'To> o) 17 | | ValueNone -> ValueNone 18 | -------------------------------------------------------------------------------- /test/FSharpIL.Benchmarks.Memory/FSharpIL.Benchmarks.Memory.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FSharpIL.Benchmarks.Memory 5 | Exe 6 | net5.0 7 | BENCHMARK 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Blobs/ElemType.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata.Blobs 2 | 3 | [] 4 | [] 5 | type PrimitiveElemType = 6 | | Bool 7 | | Char 8 | | R4 9 | | R8 10 | | I1 11 | | I2 12 | | I4 13 | | I8 14 | | U1 15 | | U2 16 | | U4 17 | | U8 18 | | String 19 | | Type 20 | //| Boxed // of PrimitiveElemType 21 | 22 | /// Represents the type of a custom attribute argument (II.23.3). 23 | [] 24 | type ElemType = 25 | | Primitive of PrimitiveElemType 26 | | SZArray of PrimitiveElemType 27 | // TODO: How to represent enum types? 28 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/ParsedBlobStream.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open FSharpIL 4 | open FSharpIL.Metadata 5 | 6 | /// Represents the #Blob metadata heap (II.24.2.4). 7 | [] 8 | type ParsedBlobStream internal (stream: LengthEncodedStream) = 9 | // TODO: Have lookups for various signature types. 10 | //let 11 | 12 | new (stream) = ParsedBlobStream(LengthEncodedStream stream) 13 | new () = ParsedBlobStream ChunkedMemory.empty 14 | 15 | member _.Size = stream.contents.Length 16 | 17 | /// Returns the contents of the blob at the specified offset. 18 | member _.TryGetBytes { BlobOffset = offset } = stream.TryReadBytes offset 19 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/PublicKeyOrToken.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata 2 | 3 | open System.Collections.Immutable 4 | 5 | type PublicKeyToken = byte * byte * byte * byte * byte * byte * byte * byte 6 | 7 | type PublicKeyOrToken = 8 | | NoPublicKeyOrToken 9 | | PublicKey of ImmutableArray 10 | | PublicKeyToken of PublicKeyToken 11 | 12 | [] 13 | module PublicKeyOrToken = 14 | let toBlock publicKeyOrToken = 15 | match publicKeyOrToken with 16 | | NoPublicKeyOrToken -> ImmutableArray.Empty 17 | | PublicKey publicKey -> publicKey 18 | | PublicKeyToken(b0, b1, b2, b3, b4, b5, b6, b7) -> 19 | let mutable token = [| b0; b1; b2; b3; b4; b5; b6; b7 |] 20 | System.Runtime.CompilerServices.Unsafe.As &token 21 | -------------------------------------------------------------------------------- /test/FSharpIL.Properties/ValidationExpect.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module FSharpIL.Metadata.ValidationExpect 3 | 4 | open Expecto 5 | 6 | let wantError (x: ValidationResult<_>) msg = 7 | match x with 8 | | ValidationSuccess(result, checks) -> 9 | failtestf "%s. Expected ValidationError, was ValidationSuccess(%A, %A)" msg result checks 10 | | ValidationWarning(result, checks, warnings) -> 11 | failtestf "%s. Expected ValidationError, was ValidationWarning(%A, %A, %A)" msg result checks warnings 12 | | ValidationError err -> err 13 | 14 | let isError x msg = wantError x msg |> ignore 15 | 16 | let isSpecificError (actual: ValidationResult<_>) (expected: ValidationError) msg = 17 | let result = wantError actual msg 18 | Expect.equal expected result msg 19 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/FieldMetadataToken.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata 2 | 3 | open FSharpIL.Metadata.Tables 4 | 5 | [] 6 | type FieldMetadataToken private (token: MetadataToken) = struct 7 | internal new (tag, index) = FieldMetadataToken(MetadataToken(tag, index)) 8 | member _.Type = token.Type 9 | member _.Token = token 10 | override _.ToString() = token.ToString() 11 | static member inline op_Implicit(token: FieldMetadataToken) = uint32 token.Token 12 | end 13 | 14 | [] 15 | module FieldMetadataToken = 16 | let Def ({ TableIndex = i }: TableIndex) = FieldMetadataToken(MetadataTokenType.Field, i) 17 | let Ref ({ TableIndex = i }: TableIndex) = FieldMetadataToken(MetadataTokenType.MemberRef, i) 18 | -------------------------------------------------------------------------------- /src/ilinfo/ilinfo.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ilinfo 5 | true 6 | Exe 7 | ILInfo 8 | net5.0 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/FSharpIL.Examples.Tests/CollectionTests.fs: -------------------------------------------------------------------------------- 1 | module FSharpIL.CollectionTests 2 | 3 | open Expecto 4 | open FsCheck 5 | 6 | open Swensen.Unquote 7 | 8 | open System 9 | 10 | open Example 11 | 12 | [] 13 | let tests = 14 | testList "fraction" [ 15 | testProperty "adding elements to empty list is successful" <| fun(PositiveInt capacity, NonNull items) -> 16 | let sut = MyCollection capacity 17 | Array.iter sut.Add items 18 | test <@ sut.ToArray() = items @> 19 | 20 | fun(PositiveInt capacity, NonNull(strings: string[])) -> 21 | let sut = MyCollection capacity 22 | Array.iter sut.Add strings 23 | test <@ sut.Cast().ToArray() = strings @> 24 | |> testProperty "casting object collection to string collection is successful" 25 | ] 26 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/ParsedUserStringStream.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open FSharpIL 4 | open FSharpIL.Metadata 5 | 6 | /// Represents the #US metadata heap, which contains UTF-16 strings (II.24.2.4). 7 | type ParsedUserStringStream internal (stream: LengthEncodedStream) = 8 | let lookup = System.Collections.Generic.Dictionary() 9 | 10 | new (stream) = new ParsedUserStringStream(LengthEncodedStream stream) 11 | new () = ParsedUserStringStream ChunkedMemory.empty 12 | 13 | member _.Size = stream.contents.Length 14 | 15 | member _.TryGetString { UserStringOffset = offset } = 16 | match lookup.TryGetValue offset with 17 | | true, existing -> Ok existing 18 | | false, _ -> 19 | stream.TryReadBytes offset 20 | |> Utilities.Fail.noImpl "TODO: Read the blob" 21 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/ParseBlob.fsi: -------------------------------------------------------------------------------- 1 | module FSharpIL.Reading.ParseBlob 2 | 3 | open FSharpIL 4 | open FSharpIL.Metadata.Blobs 5 | open FSharpIL.Metadata.Signatures 6 | 7 | val internal compressedUnsigned : chunk: byref -> Result 8 | 9 | val fieldSig : chunk: inref -> Result 10 | 11 | val methodDefSig : chunk: inref -> Result 12 | 13 | //val methodRefSig : chunk: inref -> Result 14 | 15 | val propertySig : chunk: inref -> Result 16 | 17 | val typeSpec : chunk: inref -> Result 18 | 19 | val customAttrib : 20 | fixedArgTypes: System.Collections.Immutable.ImmutableArray -> 21 | chunk: inref -> 22 | Result 23 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/CustomAttribute.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open System.Collections.Immutable 4 | 5 | open FSharpIL.Metadata 6 | open FSharpIL.Metadata.Blobs 7 | open FSharpIL.Metadata.Signatures 8 | 9 | [] 10 | type CustomAttributeCtor = 11 | val Constructor : MethodTok 12 | new (token) = { Constructor = token } 13 | 14 | module CustomAttributeCtor = 15 | type Constructor = MethodReference 16 | 17 | let Referenced (target: MethodTok, Constructor> when 'Kind :> TypeKinds.IHasConstructors) = 18 | CustomAttributeCtor target.Token 19 | 20 | type FixedArgSource = int32 -> Identifier voption -> ElemType -> Result 21 | 22 | type CustomAttribute = 23 | { Constructor: CustomAttributeCtor 24 | FixedArguments: FixedArgSource 25 | NamedArguments: ImmutableArray } 26 | -------------------------------------------------------------------------------- /src/ilinfo/IndentedTextWriter.fs: -------------------------------------------------------------------------------- 1 | namespace ILInfo 2 | 3 | open System.IO 4 | 5 | [] 6 | type IndentedTextWriter (writer: TextWriter, indentation: string) = 7 | inherit TextWriter() 8 | let mutable written, level = false, 0u 9 | member private _.WriteIndentation() = 10 | if not written then 11 | let mutable i = level 12 | while i > 0u do 13 | writer.Write indentation 14 | i <- i - 1u 15 | written <- true 16 | member _.Indent() = level <- level + 1u 17 | member _.Dedent() = if level > 0u then level <- level - 1u 18 | override this.Write(value: char) = 19 | this.WriteIndentation() 20 | writer.Write value 21 | override _.WriteLine() = 22 | writer.WriteLine() 23 | written <- false 24 | override _.Encoding = writer.Encoding 25 | override _.Close() = writer.Close() 26 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/ReadPE.fsi: -------------------------------------------------------------------------------- 1 | /// Contains functions for reading Portable Executable files (II.25). 2 | [] 3 | module FSharpIL.Reading.ReadPE 4 | 5 | val fromArray<'State> : file: byte[] -> state: 'State -> PEFileReader<'State> -> 'State 6 | 7 | val fromBlock<'State> : 8 | file: System.Collections.Immutable.ImmutableArray -> 9 | state: 'State -> 10 | PEFileReader<'State> -> 11 | 'State 12 | 13 | val fromChunkedMemory<'State> : file: FSharpIL.ChunkedMemory -> state: 'State -> PEFileReader<'State> -> 'State 14 | 15 | val fromMemory<'State> : file: System.ReadOnlyMemory -> state: 'State -> PEFileReader<'State> -> 'State 16 | 17 | /// Reads a Portable Executable file from the specified stream. 18 | val fromStream<'Stream, 'State when 'Stream :> System.IO.Stream> : 19 | stream: 'Stream -> 20 | state: 'State -> 21 | PEFileReader<'State> -> 22 | 'State 23 | -------------------------------------------------------------------------------- /docs/FSharpIL.Examples/FSharpIL.Examples.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FSharpIL.Examples 5 | Exe 6 | net5.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/ParsedGuidStream.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open System 4 | 5 | open Microsoft.FSharp.Core.Operators.Checked 6 | 7 | open FSharpIL.Utilities 8 | 9 | open FSharpIL 10 | open FSharpIL.Metadata 11 | 12 | [] 13 | type ParsedGuidStream (chunk: ChunkedMemory) = 14 | member _.Count = chunk.Length / 16u 15 | 16 | member _.IsValidOffset offset = chunk.IsValidOffset(offset + 15u) 17 | 18 | member _.TryGetGuid index = 19 | match index with 20 | | { GuidIndex = 0u } -> Ok Guid.Empty 21 | | { GuidIndex = i } -> 22 | let buffer = Span.stackalloc sizeof 23 | let offset = (i - 1u) * uint32 buffer.Length 24 | if chunk.TryCopyTo(offset, buffer) 25 | then Ok(Guid(Span.asReadOnly buffer)) 26 | else Error(InvalidGuidIndex(index, { GuidIndex = chunk.Length / uint32 sizeof })) 27 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/MethodMetadataToken.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata 2 | 3 | open FSharpIL.Metadata.Tables 4 | 5 | [] 6 | type MethodMetadataToken private (token: MetadataToken) = struct 7 | internal new (tag, index) = MethodMetadataToken(MetadataToken(tag, index)) 8 | member _.Type = token.Type 9 | member _.Token = token 10 | override _.ToString() = token.ToString() 11 | static member inline op_Implicit(token: MethodMetadataToken) = uint32 token.Token 12 | end 13 | 14 | [] 15 | module MethodMetadataToken = 16 | let Def ({ TableIndex = i }: TableIndex) = MethodMetadataToken(MetadataTokenType.MethodDef, i) 17 | let Ref ({ TableIndex = i }: TableIndex) = MethodMetadataToken(MetadataTokenType.MemberRef, i) 18 | let Spec ({ TableIndex = i }: TableIndex) = MethodMetadataToken(MetadataTokenType.MethodSpec, i) 19 | -------------------------------------------------------------------------------- /test/FSharpIL.Benchmarks/FSharpIL.Benchmarks.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FSharpIL.Benchmarks 5 | Exe 6 | net5.0 7 | BENCHMARK 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/LengthEncodedStream.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open FSharpIL 4 | 5 | /// Represents a metadata stream containing blobs, whose lengths are included before the corresponding blob's content (II.24.2.4). 6 | [] 7 | type internal LengthEncodedStream = struct 8 | val contents: ChunkedMemory 9 | new (contents) = { contents = contents } 10 | member this.TryReadBytes offset = 11 | match this.contents.TrySlice offset with 12 | | true, chunk' -> 13 | let mutable chunk' = chunk' 14 | match ParseBlob.compressedUnsigned &chunk' with 15 | | Ok(_, size: uint32) -> 16 | match chunk'.TrySlice(0u, size) with 17 | | true, blob -> Ok blob 18 | | false, _ -> Error(BlobOutsideOfHeap(offset, size)) 19 | | Error err -> Error err 20 | | false, _ -> Error(InvalidBlobOffset(offset, this.contents.Length - 1u)) 21 | end 22 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/Event.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open System 4 | 5 | open FSharpIL.Metadata 6 | 7 | open FSharpIL.Utilities.Compare 8 | 9 | [] 10 | type Event (name, etype, add, remove, raise, other) = 11 | member _.Flags = FSharpIL.Metadata.Tables.EventFlags.None 12 | member _.Name: Identifier = name 13 | member _.Type: TypeTok = etype 14 | member _.Add: DefinedMethod = add 15 | member _.Remove: DefinedMethod = remove 16 | member _.Raise: DefinedMethod voption = raise 17 | member _.Other: DefinedMethod list = other 18 | 19 | override _.GetHashCode() = HashCode.Combine(name, add, remove) 20 | 21 | interface IEquatable with 22 | member _.Equals other = name === other.Name 23 | 24 | override this.Equals obj = 25 | match obj with 26 | | :? Event as other -> this === other 27 | | _ -> false 28 | 29 | module EventPatterns = 30 | let inline (|EventMethods|) (event: Event) = struct(event.Add, event.Remove, event.Raise, event.Other) 31 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/ParsedTableRowCounts.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open System.Collections.Generic 4 | 5 | open FSharpIL.Metadata.Tables 6 | 7 | [] 8 | type ParsedTableRowCounts internal (lookup: IReadOnlyDictionary) = 9 | member _.Count = lookup.Count 10 | member _.GetEnumerator() = lookup.GetEnumerator() :> IEnumerator<_> 11 | interface IReadOnlyDictionary with 12 | member _.ContainsKey key = lookup.ContainsKey key 13 | member this.Count = this.Count 14 | member _.GetEnumerator() = lookup.GetEnumerator() :> IEnumerator<_> 15 | member _.GetEnumerator() = lookup.GetEnumerator() :> System.Collections.IEnumerator 16 | member _.Item with get key = lookup.[key] 17 | member _.Keys = lookup.Keys 18 | member _.TryGetValue(key, value) = lookup.TryGetValue(key, &value) 19 | member _.Values = lookup.Values 20 | interface ITableRowCounts with member _.RowCount table = lookup.GetValueOrDefault table 21 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/SectionBuilder.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Writing 2 | 3 | open FSharpIL.PortableExecutable 4 | 5 | type SectionContentWriter = delegate of byref -> unit 6 | 7 | [] 8 | [] 9 | type SectionContent = 10 | | WriteContent of SectionContentWriter 11 | | WriteMetadata of CliMetadataBuilder 12 | | SetCliHeader 13 | 14 | type SectionContentBuilder<'State> = Rva -> FileOffset -> uint32 -> 'State -> struct(SectionContent * 'State) voption 15 | 16 | type SectionBuilder<'State> = Rva -> FileOffset -> SectionName * SectionCharacteristics * 'State * SectionContentBuilder<'State> 17 | 18 | [] 19 | module SectionBuilder = 20 | let sectionListBuilder _ _ _ remaining = 21 | match remaining with 22 | | [] -> ValueNone 23 | | head :: tail -> ValueSome(struct(head, tail)) 24 | 25 | let ofList name flags content: SectionBuilder = fun _ _ -> name, flags, content, sectionListBuilder 26 | -------------------------------------------------------------------------------- /src/FSharpIL/PortableExecutable/SectionOffset.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.PortableExecutable 2 | 3 | open FSharpIL 4 | 5 | /// An offset from the start of a section. 6 | [] 7 | type SectionOffset = 8 | internal { SectionOffset: uint32 } 9 | override this.ToString() = sprintf "0x%08X" this.SectionOffset 10 | static member op_Implicit { SectionOffset = offset } = offset 11 | static member (+) (rva: Rva, { SectionOffset = offset }) = Rva(uint32 rva + offset) 12 | static member (+) ({ SectionOffset = soffset }, offset: uint32) = { SectionOffset = soffset + offset } 13 | static member (+) ({ SectionOffset = left }, { SectionOffset = right }) = { SectionOffset = left + right } 14 | static member (-) ({ SectionOffset = left }, { SectionOffset = right }) = { SectionOffset = left - right } 15 | static member (*) ({ SectionOffset = offset }, multiplier) = { SectionOffset = offset * multiplier } 16 | static member inline (*) (multiplier: uint32, offset: SectionOffset) = offset * multiplier 17 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/SectionBuilder.fsi: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Writing 2 | 3 | open FSharpIL.PortableExecutable 4 | 5 | type SectionContentWriter = delegate of byref -> unit 6 | 7 | [] 8 | [] 9 | type SectionContent = 10 | | WriteContent of SectionContentWriter 11 | | WriteMetadata of CliMetadataBuilder 12 | /// Sets the RVA and size of the CLI header data directory. 13 | | SetCliHeader 14 | //| SetDataDirectory of DataDirectory * RvaAndSize 15 | 16 | type SectionContentBuilder<'State> = Rva -> FileOffset -> uint32 -> 'State -> struct(SectionContent * 'State) voption 17 | 18 | /// Creates a section in a Portable Executable file. 19 | type SectionBuilder<'State> = Rva -> FileOffset -> SectionName * SectionCharacteristics * 'State * SectionContentBuilder<'State> 20 | 21 | [] 22 | module SectionBuilder = 23 | val ofList: name: SectionName -> flags: SectionCharacteristics -> content: SectionContent list -> SectionBuilder 24 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Tables/TableIndex.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata.Tables 2 | 3 | /// Marker interface used to indicate that a type represents a row in a metadata table. 4 | type ITableRow = interface end 5 | 6 | [] 7 | type TableIndex<'Row when 'Row :> ITableRow> = 8 | internal { TableIndex: uint32 } 9 | member this.IsNull = this.TableIndex = 0u 10 | static member op_Implicit { TableIndex = index } = index 11 | override this.ToString() = sprintf "%s (0x%08X)" typeof<'Row>.Name this.TableIndex 12 | 13 | type ITableRowCounts = interface 14 | abstract RowCount: table: ValidTableFlags -> uint32 15 | end 16 | 17 | [] 18 | module TableIndex = 19 | let [] internal MaxSmallIndex = 0xFFFFu 20 | 21 | let isLarge table (counts: #ITableRowCounts) = counts.RowCount table > MaxSmallIndex 22 | 23 | // TODO: Prevent index from exceeding maximum 3-byte integer. 24 | let ofIntUnsafe<'Row when 'Row :> ITableRow> index = { TableIndex = index }: TableIndex<'Row> 25 | -------------------------------------------------------------------------------- /test/FSharpIL.Properties/FSharpIL.Properties.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FSharpIL.Properties 5 | Exe 6 | net5.0;netcoreapp3.1 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Magic.fs: -------------------------------------------------------------------------------- 1 | /// Contains various magic numbers used throughout the CLI metadata. 2 | [] 3 | module FSharpIL.Metadata.Magic 4 | 5 | open System.Collections.Immutable 6 | 7 | open FSharpIL.Utilities 8 | 9 | /// The size of the CLI header, in bytes, stored in the Cb field of the CLI header (II.25.3.3). 10 | let CliHeaderSize = 0x48u 11 | 12 | /// The value of the Signature field of the CLI metadata root (II.24.2.1). 13 | let metadataRootSignature = Convert.unsafeTo<_, ImmutableArray> [| 0x42uy; 0x53uy; 0x4Auy; 0x42uy; |] 14 | 15 | [] 16 | module StreamNames = 17 | let metadata = Convert.unsafeTo<_, ImmutableArray> "#~\000\000"B 18 | let strings = Convert.unsafeTo<_, ImmutableArray> "#Strings\000\000\000\000"B 19 | let us = Convert.unsafeTo<_, ImmutableArray> "#US\000"B 20 | let guid = Convert.unsafeTo<_, ImmutableArray> "#GUID\000\000\000"B 21 | let blob = Convert.unsafeTo<_, ImmutableArray> "#Blob\000\000\000"B 22 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/Assembly.fsi: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open System 4 | open System.Collections.Immutable 5 | 6 | open FSharpIL.Metadata 7 | open FSharpIL.Metadata.Tables 8 | 9 | [] 10 | type ReferencedAssembly = 11 | { Version: AssemblyVersion 12 | PublicKeyOrToken: PublicKeyOrToken 13 | Name: FileName 14 | Culture: Identifier voption 15 | HashValue: ImmutableArray } 16 | 17 | member Flags: AssemblyFlags 18 | 19 | override ToString: unit -> string 20 | override GetHashCode: unit -> int32 21 | override Equals: obj -> bool 22 | 23 | interface IEquatable 24 | 25 | [] 26 | type DefinedAssembly = 27 | { Version: AssemblyVersion 28 | //Flags: AssemblyFlags 29 | PublicKey: ImmutableArray 30 | Name: FileName 31 | Culture: Identifier voption } 32 | 33 | override ToString: unit -> string 34 | override GetHashCode: unit -> int32 35 | override Equals: obj -> bool 36 | 37 | interface IEquatable 38 | -------------------------------------------------------------------------------- /src/FSharpIL/PortableExecutable/Alignment.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.PortableExecutable 2 | 3 | [] 4 | type Alignment = 5 | private { Section: uint16; File: uint16 } 6 | static member Default = { Section = 0x2000us; File = 0x200us; } 7 | /// Always greater than the FileAlignment. 8 | member this.SectionAlignment = uint32 this.Section 9 | // TODO: Specification says this should always be 0x200, should this be a fixed value? 10 | member this.FileAlignment = uint32 this.File 11 | 12 | [] 13 | module Alignment = 14 | let private (|Valid|_|) alignment = 15 | match alignment with 16 | | 512u 17 | | 1024u 18 | | 2048u 19 | | 4096u 20 | | 8192u 21 | | 16384u 22 | | 32768u -> uint16 alignment |> Some 23 | | _ -> None 24 | 25 | let tryCreate salignment falignment = 26 | match (salignment, falignment) with 27 | | Valid sa, Valid fa when salignment >= falignment -> ValueSome { Section = sa; File = fa } 28 | | _ -> ValueNone 29 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/CustomAttribute.fsi: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open System.Collections.Immutable 4 | 5 | open FSharpIL.Metadata 6 | open FSharpIL.Metadata.Blobs 7 | open FSharpIL.Metadata.Signatures 8 | 9 | [] 10 | type CustomAttributeCtor = 11 | val Constructor: MethodTok 12 | 13 | interface System.IEquatable 14 | 15 | [] 16 | module CustomAttributeCtor = 17 | type Constructor = MethodReference 18 | 19 | val Referenced : 20 | target: MethodTok, Constructor> -> 21 | CustomAttributeCtor when 'Kind :> TypeKinds.IHasConstructors 22 | 23 | type FixedArgSource = int32 -> Identifier voption -> ElemType -> Result 24 | 25 | [] 26 | type CustomAttribute = 27 | { Constructor: CustomAttributeCtor 28 | FixedArguments: FixedArgSource 29 | // TODO: How to ensure that the fields and properties actually exist? 30 | NamedArguments: ImmutableArray } 31 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Blobs/ElementType.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata.Blobs 2 | 3 | /// Represents an element type used in a signature (II.23.1.16). 4 | type ElementType = 5 | | End = 0uy 6 | | Void = 0x1uy 7 | | Boolean = 0x2uy 8 | | Char = 0x3uy 9 | | I1 = 0x4uy 10 | | U1 = 0x5uy 11 | | I2 = 0x6uy 12 | | U2 = 0x7uy 13 | | I4 = 0x8uy 14 | | U4 = 0x9uy 15 | | I8 = 0xAuy 16 | | U8 = 0xBuy 17 | | R4 = 0xCuy 18 | | R8 = 0xDuy 19 | | String = 0xEuy 20 | | Ptr = 0xFuy 21 | | ByRef = 0x10uy 22 | | ValueType = 0x11uy 23 | | Class = 0x12uy 24 | | Var = 0x13uy 25 | | Array = 0x14uy 26 | | GenericInst = 0x15uy 27 | | TypedByRef = 0x16uy 28 | | I = 0x18uy 29 | | U = 0x19uy 30 | | FnPtr = 0x1Buy 31 | | Object = 0x1Cuy 32 | | SZArray = 0x1Duy 33 | | MVar = 0x1Euy 34 | | CModReqd = 0x1Fuy 35 | | CModOpt = 0x20uy 36 | | Internal = 0x21uy 37 | | Modifier = 0x40uy 38 | | Sentinel = 0x41uy 39 | | Pinned = 0x45uy 40 | | Type = 50uy 41 | | Boxed = 0x51uy 42 | | Field = 0x53uy 43 | | Property = 0x54uy 44 | | Enum = 0x55uy 45 | -------------------------------------------------------------------------------- /src/FSharpIL/PortableExecutable/DataDirectories.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.PortableExecutable 2 | 3 | open System.Runtime.CompilerServices 4 | 5 | type [] CliHeaderDirectory = internal { Directory: RvaAndSize } 6 | // TODO: Add other special types for other data directories. 7 | 8 | [] 9 | module DataDirectory = 10 | let (|CliHeader|) { CliHeaderDirectory.Directory = data } = data 11 | 12 | /// Represents the data directories of a PE file (II.25.2.3.3). 13 | [] 14 | type DataDirectories = 15 | { ExportTable: RvaAndSize 16 | ImportTable: RvaAndSize 17 | ResourceTable: RvaAndSize 18 | ExceptionTable: RvaAndSize 19 | CertificateTable: RvaAndSize 20 | BaseRelocationTable: RvaAndSize 21 | DebugTable: RvaAndSize 22 | CopyrightTable: RvaAndSize 23 | GlobalPointerTable: RvaAndSize 24 | TLSTable: RvaAndSize 25 | LoadConfigTable: RvaAndSize 26 | BoundImportTable: RvaAndSize 27 | ImportAddressTable: RvaAndSize 28 | DelayImportDescriptor: RvaAndSize 29 | CliHeader: CliHeaderDirectory 30 | Reserved: RvaAndSize } 31 | -------------------------------------------------------------------------------- /test/FSharpIL.Examples.Tests/FractionTests.fs: -------------------------------------------------------------------------------- 1 | module FSharpIL.FractionTests 2 | 3 | open Expecto 4 | open FsCheck 5 | 6 | open Swensen.Unquote 7 | 8 | open CustomNumbers 9 | 10 | [] 11 | let tests = 12 | testList "fraction" [ 13 | testCase "one-fourth is less than one-half" <| fun() -> 14 | test <@ Fraction(1, 4).CompareTo(Fraction(1, 2)) < 0 @> 15 | 16 | testCase "one-fourth times one-half is equal to one-eigth" <| fun() -> 17 | test <@ Fraction(1, 4) * Fraction(1, 2) = Fraction(1, 8) @> 18 | 19 | testProperty "string representation contains numerator and denominator" <| fun(num, den) -> 20 | let str = Fraction(num, den).ToString() 21 | test <@ str.Contains(string num) && str.Contains(string den) @> 22 | 23 | //testProperty "numerators and denominators match" <| fun(num: int32, den: int32) -> 24 | // failtest "Figure out why generated properties cause compiler errors" 25 | // <@ 26 | // let fraction = Fraction(num, den) 27 | // fraction.Numerator = num && fraction.Denominator = den 28 | // @> 29 | // |> test 30 | ] 31 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Headers.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata 2 | 3 | open FSharpIL 4 | 5 | /// The CLI metadata root (II.24.2.1). 6 | type CliMetadataRoot<'Signature, 'Streams> = 7 | { Signature: 'Signature 8 | MajorVersion: uint16 9 | MinorVersion: uint16 10 | Reserved: uint32 11 | Version: MetadataVersion 12 | /// Reserved value set to 0 on writing. 13 | Flags: uint16 14 | /// Specifies the number of stream headers after the CLI metadata root (II.24.2.1). 15 | Streams: 'Streams } 16 | 17 | [] 18 | module CliMetadataRoot = 19 | let defaultFields = 20 | { Signature = Omitted 21 | MajorVersion = 1us 22 | MinorVersion = 1us 23 | Reserved = 0u 24 | Version = MetadataVersion.defaultLatest 25 | Flags = 0us 26 | Streams = Omitted } 27 | 28 | /// Flags that describe the runtime image (II.25.3.3.1). 29 | [] 30 | type CorFlags = 31 | | None = 0u 32 | | ILOnly = 1u 33 | | Requires32Bit = 2u 34 | /// The image has a strong name signature. 35 | | StrongNameSigned = 0x8u 36 | | NativeEntryPoint = 0x10u 37 | | TrackDebugData = 0x10000u 38 | -------------------------------------------------------------------------------- /test/FSharpIL.Examples.Tests/FSharpIL.Examples.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FSharpIL.Tests.Examples 5 | Exe 6 | net5.0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/FSharpIL/PortableExecutable/Rva.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.PortableExecutable 2 | 3 | open System.Runtime.CompilerServices 4 | 5 | /// Represents a relative virtual address. 6 | [] 7 | [] 8 | type Rva = struct 9 | val private value: uint32 10 | new (value) = { value = value } 11 | static member inline Zero = Rva 0u 12 | static member op_Implicit(rva: Rva) = uint64 rva.value 13 | static member op_Implicit(rva: Rva) = rva.value 14 | static member (+) (rva: Rva, value: uint32) = Rva(rva.value + value) 15 | static member inline (+) (value: uint32, rva: Rva) = rva + value 16 | static member (+) (left: Rva, right: Rva) = Rva(left.value + right.value) 17 | static member (*) (rva: Rva, value: uint32) = Rva(rva.value * value) 18 | static member (/) (rva: Rva, value: uint32) = Rva(rva.value / value) 19 | static member (-) (rva: Rva, value: uint32) = Rva(rva.value - value) 20 | static member (-) (left: Rva, right: Rva) = Rva(left.value - right.value) 21 | override this.ToString() = sprintf "0x%08X" this.value 22 | end 23 | 24 | [] 25 | type RvaAndSize = 26 | { Rva: Rva; Size: uint32 } 27 | static member inline Zero = { Rva = Rva.Zero; Size = 0u } 28 | -------------------------------------------------------------------------------- /src/FSharpIL/PortableExecutable/FileOffset.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.PortableExecutable 2 | 3 | open Microsoft.FSharp.Core.Operators.Checked 4 | 5 | [] 6 | type FileOffset = 7 | internal { FileOffset: uint32 } 8 | override this.ToString() = sprintf "0x%08X" this.FileOffset 9 | static member op_Implicit { FileOffset = offset } = offset 10 | static member Zero = { FileOffset = 0u } 11 | static member (+) ({ FileOffset = offset }, number) = { FileOffset = offset + number } 12 | static member inline (+) (number: uint32, offset: FileOffset) = offset + number 13 | static member (+) ({ FileOffset = o1 }, { FileOffset = o2 }) = { FileOffset = o1 + o2 } 14 | static member (-) ({ FileOffset = offset }, number) = { FileOffset = offset - number } 15 | static member (-) ({ FileOffset = o1 }, { FileOffset = o2 }) = { FileOffset = o1 - o2 } 16 | static member (*) ({ FileOffset = offset }, number) = { FileOffset = offset * number } 17 | static member inline (*) (number: uint32, offset: FileOffset) = offset * number 18 | static member (*) ({ FileOffset = o1 }, { FileOffset = o2 }) = { FileOffset = o1 * o2 } 19 | static member (/) ({ FileOffset = offset }, number) = { FileOffset = offset / number } 20 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/Event.fsi: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open FSharpIL.Metadata 4 | 5 | /// Represents an event (I.8.11.4 and II.18). 6 | [] 7 | type Event = 8 | member Flags: FSharpIL.Metadata.Tables.EventFlags 9 | member Name: Identifier 10 | member Type: TypeTok 11 | /// The add_ method used to add a handler for an event. 12 | member Add: DefinedMethod 13 | /// The remove_ method used to remove a handler for an event. 14 | member Remove: DefinedMethod 15 | /// The optional raise_ method used to indicate that an event has occured. 16 | member Raise: DefinedMethod voption 17 | member Other: DefinedMethod list 18 | 19 | internal new: 20 | name: Identifier * 21 | etype: TypeTok * 22 | add: DefinedMethod * 23 | remove: DefinedMethod * 24 | raise: DefinedMethod voption * 25 | other: DefinedMethod list -> Event 26 | 27 | interface System.IEquatable 28 | 29 | override GetHashCode: unit -> int32 30 | override Equals: obj -> bool 31 | 32 | [] 33 | module EventPatterns = 34 | val inline (|EventMethods|) : 35 | event: Event -> struct(DefinedMethod * DefinedMethod * DefinedMethod voption * DefinedMethod list) 36 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/TypeMetadataToken.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata 2 | 3 | open FSharpIL.Metadata.Tables 4 | 5 | [] 6 | type TypeMetadataToken private (token: MetadataToken) = struct 7 | internal new (tag, index) = TypeMetadataToken(MetadataToken(tag, index)) 8 | member _.Type = token.Type 9 | member _.Token = token 10 | override _.ToString() = token.ToString() 11 | static member inline op_Implicit(token: TypeMetadataToken) = uint32 token.Token 12 | end 13 | 14 | [] 15 | module TypeMetadataToken = 16 | let Def ({ TableIndex = i }: TableIndex) = TypeMetadataToken(MetadataTokenType.TypeDef, i) 17 | let Ref ({ TableIndex = i }: TableIndex) = TypeMetadataToken(MetadataTokenType.TypeRef, i) 18 | let Spec ({ TableIndex = i }: TableIndex) = TypeMetadataToken(MetadataTokenType.TypeSpec, i) 19 | 20 | let ofCodedIndex (index: TypeDefOrRef) = 21 | let tag = 22 | match index.Tag with 23 | | TypeDefOrRefTag.TypeDef -> MetadataTokenType.TypeDef 24 | | TypeDefOrRefTag.TypeRef -> MetadataTokenType.TypeRef 25 | | TypeDefOrRefTag.TypeSpec 26 | | _ -> MetadataTokenType.TypeSpec 27 | TypeMetadataToken(tag, index.Index) 28 | -------------------------------------------------------------------------------- /src/FSharpIL/IByteWriter.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL 2 | 3 | open System 4 | open System.Collections.Immutable 5 | open System.Runtime.CompilerServices 6 | 7 | open FSharpIL.Utilities 8 | 9 | type internal IByteWriter = abstract Write: ReadOnlySpan -> unit 10 | 11 | [] 12 | type internal ByteWriterExtensions = 13 | [] static member inline Write(this: byref<#IByteWriter>, data: byte[]) = this.Write(ReadOnlySpan data) 14 | [] static member inline Write(this: byref<#IByteWriter>, data: ImmutableArray) = this.Write(data.AsSpan()) 15 | [] static member inline Write(this: byref<#IByteWriter>, data: Span) = this.Write(Span.asReadOnly data) 16 | 17 | [] 18 | static member WriteLE(this: byref<#IByteWriter>, value) = 19 | let span = Span.stackalloc 2 20 | this.Write(Span.asReadOnly(Bytes.ofU2 span value)) 21 | 22 | [] 23 | static member WriteLE(this: byref<#IByteWriter>, value) = 24 | let span = Span.stackalloc 4 25 | this.Write(Span.asReadOnly(Bytes.ofU4 span value)) 26 | 27 | [] 28 | static member WriteLE(this: byref<#IByteWriter>, value) = 29 | let span = Span.stackalloc 8 30 | this.Write(Span.asReadOnly(Bytes.ofU8 span value)) 31 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/ReadCli.fsi: -------------------------------------------------------------------------------- 1 | /// Contains functions for reading CLI metadata (II.24). 2 | [] 3 | module FSharpIL.Reading.ReadCli 4 | 5 | open FSharpIL 6 | open FSharpIL.PortableExecutable 7 | 8 | /// Reads the CLI metadata contained within the specified . 9 | /// A managed poitner to the contents of the section. 10 | /// 11 | /// A relative virtual address pointing to the first byte of the section data, corresponds to the VirtualAddress field of 12 | /// the section header. 13 | /// 14 | /// 15 | /// Offset from the start of the Portable Executable file to the first byte of the section data, corresponds to the 16 | /// PointerToRawData field of the section header. 17 | /// 18 | /// Offset from the start of the section to the first byte of the CLI header. 19 | /// The initial state. 20 | /// The reader used to process the CLI metadata. 21 | val fromChunkedMemory<'State> : 22 | section: inref -> 23 | sectionRva: Rva -> 24 | sectionOffset: FileOffset -> 25 | cliHeaderOffset: SectionOffset -> 26 | state: 'State -> 27 | reader: MetadataReader<'State> -> 28 | 'State 29 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/BuildCli.fsi: -------------------------------------------------------------------------------- 1 | /// Contains types and modules for building CLI metadata modules (I.9). 2 | module FSharpIL.Writing.BuildCli 3 | 4 | open FSharpIL 5 | open FSharpIL.Metadata 6 | 7 | open FSharpIL.Cli 8 | 9 | [] 10 | [] 11 | type ModuleUpdate = 12 | internal 13 | | AddDefinedType of TypeDefinition 14 | | Finish 15 | 16 | [] 17 | module ModuleUpdate = 18 | val finish : ModuleUpdate 19 | 20 | //type SomeDispatchThing = 21 | // member _.SendSomeUpdate<'T when 'T : struct>() = ... 22 | 23 | [] 24 | type ModuleBuilder<'State> = 25 | { Update: 'State -> ModuleUpdate // Update: 'State -> SomeDispatchThing -> SomeReturnType 26 | Warning: ('State -> IValidationWarning -> 'State) option 27 | ReferenceType: 'State -> TypeReference -> 'State 28 | DefineType: 'State -> TypeDefinition -> 'State } 29 | 30 | [] 31 | module ModuleBuilder = 32 | [] 33 | val ignored<'State> : ModuleBuilder<'State> 34 | 35 | val run<'State> : 36 | header: CliHeader -> 37 | root: CliMetadataRoot -> 38 | name: Identifier -> 39 | mvid: System.Guid -> 40 | builder: ModuleBuilder<'State> -> 41 | state: 'State -> ValidationResult 42 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/MetadataReader.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open System.Collections.Immutable 4 | 5 | /// Collection of functions used to read CLI metadata (II.24). 6 | [] 7 | type MetadataReader<'State> = 8 | { ReadCliHeader: StructureReader 9 | ReadMetadataRoot: StructureReader 10 | ReadStreamHeaders: StructureReader, 'State> 11 | ReadStringsStream: StructureReader 12 | ReadGuidStream: StructureReader 13 | ReadUserStringStream: StructureReader 14 | ReadBlobStream: StructureReader 15 | ReadTables: MetadataTablesReader<'State> voption 16 | HandleError: ErrorHandler<'State> } 17 | 18 | [] 19 | module MetadataReader = 20 | let defaultReader<'State> = 21 | { ReadCliHeader = ValueNone 22 | ReadMetadataRoot = ValueNone 23 | ReadStreamHeaders = ValueNone 24 | ReadStringsStream = ValueNone 25 | ReadGuidStream = ValueNone 26 | ReadUserStringStream = ValueNone 27 | ReadBlobStream = ValueNone 28 | ReadTables = ValueNone 29 | HandleError = ErrorHandler.throwOnError } 30 | : MetadataReader<'State> 31 | -------------------------------------------------------------------------------- /test/FSharpIL.Examples.Tests/DelegatesTests.fs: -------------------------------------------------------------------------------- 1 | module FSharpIL.DelegatesTests 2 | 3 | open Expecto 4 | open FsCheck 5 | 6 | open Swensen.Unquote 7 | 8 | type MyInstance = 9 | { Function: string -> int32 -> string } 10 | member this.MyMethod(arg1: string, arg2: int32) = this.Function arg1 arg2 11 | 12 | [] 13 | let tests = 14 | testList "delegates example" [ 15 | testList "using static method" [ 16 | testProperty "works with F# function" <| fun(arg1, arg2, f) -> 17 | let sut = MyDelegate f 18 | test <@ sut.Invoke(arg1, arg2) = f arg1 arg2 @> 19 | 20 | testProperty "duplicate string returns string with correct length" <| fun(NonNull str, PositiveInt times) -> 21 | let sut = MyDelegate(fun arg1 arg2 -> MyClass.DuplicateString(arg1, arg2)) 22 | test <@ sut.Invoke(str, times).Length = str.Length * times @> 23 | ] 24 | 25 | testProperty "works with instance method" <| fun (instance: MyInstance, arg1, arg2) -> 26 | let sut = MyDelegate(fun arg1 arg2 -> instance.MyMethod(arg1, arg2)) 27 | test <@ sut.Invoke(arg1, arg2) = instance.MyMethod(arg1, arg2) @> 28 | 29 | testProperty "example #2 uses indexof" <| fun(NonNull str, NonNull sub) -> 30 | let sut = MyClass.Example2 str 31 | test <@ sut.Invoke sub = str.IndexOf sub @> 32 | ] 33 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/Property.fsi: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open FSharpIL.Metadata 4 | 5 | /// Represents a property, which is a grouping of methods that access or modify a value (I.8.11.3 and II.17). 6 | [] 7 | type Property = 8 | member Flags: FSharpIL.Metadata.Tables.PropertyFlags 9 | member Name: Identifier 10 | member Getter: DefinedMethod voption 11 | member Setter: DefinedMethod voption 12 | member Other: DefinedMethod list 13 | 14 | internal new: 15 | name: Identifier * 16 | getter: DefinedMethod voption * 17 | setter: DefinedMethod voption * 18 | other: DefinedMethod list -> Property 19 | 20 | override GetHashCode: unit -> int32 21 | override Equals: obj -> bool 22 | 23 | interface System.IEquatable 24 | 25 | [] 26 | module PropertyPatterns = 27 | val inline (|PropertyMethods|) : 28 | property: Property -> struct(DefinedMethod voption * DefinedMethod voption * DefinedMethod list) 29 | 30 | [] 31 | type Property<'Kind when 'Kind :> MethodKinds.IKind and 'Kind : struct> = 32 | member Property: Property 33 | member Getter: MethodDefinition<'Kind> voption 34 | member Setter: MethodDefinition<'Kind> voption 35 | 36 | internal new: Property -> Property<'Kind> 37 | 38 | interface System.IEquatable> 39 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Tables/TableFlags.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata.Tables 2 | 3 | open System 4 | 5 | /// Bitmask used to refer to various metadata tables (II.24.2.6). 6 | [] 7 | type ValidTableFlags = 8 | | None = 0UL 9 | | Module = 1UL 10 | | TypeRef = 2UL 11 | | TypeDef = 4UL 12 | | Field = 0x10UL // (1UL <<< 4) 13 | | MethodDef = 0x40UL 14 | | Param = 0x100UL 15 | | InterfaceImpl = 0x200UL 16 | | MemberRef = 0x400UL 17 | | Constant = 0x800UL 18 | | CustomAttribute = 0x1000UL 19 | 20 | | ClassLayout = 0x8000UL 21 | 22 | | StandAloneSig = 0x2_0000UL 23 | | EventMap = 0x4_0000UL 24 | | Event = 0x10_0000UL 25 | | PropertyMap = 0x20_0000UL 26 | | Property = 0x80_0000UL 27 | | MethodSemantics = 0x100_0000UL 28 | | MethodImpl = 0x200_0000UL 29 | | ModuleRef = 0x400_0000UL 30 | | TypeSpec = 0x800_0000UL 31 | 32 | | FieldRva = 0x2000_0000UL 33 | | Assembly = 0x1_0000_0000UL 34 | | AssemblyRef = 0x8_0000_0000UL 35 | 36 | | File = 0x40_0000_0000UL 37 | | ExportedType = 0x80_0000_0000UL 38 | 39 | | ManifestResource = 0x100_0000_0000UL 40 | | NestedClass = 0x200_0000_0000UL 41 | | GenericParam = 0x400_0000_0000UL 42 | | MethodSpec = 0x800_0000_0000UL 43 | | GenericParamConstraint = 0x1000_0000_0000UL 44 | 45 | | Document = 0x1_0000_0000_0000UL 46 | | MethodDebugInformation = 0x2_0000_0000_0000UL 47 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Signatures/CustomMod.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata.Signatures 2 | 3 | /// Represents an index into the TypeDef, TypeRef or TypeSpec table (II.23.2.8). 4 | type TypeDefOrRefOrSpecEncoded = FSharpIL.Metadata.Tables.TypeDefOrRef 5 | 6 | /// Represents a custom modifier (II.7.1.1 and II.23.2.7). 7 | [] 8 | type CustomMod = { Required: bool; ModifierType: TypeDefOrRefOrSpecEncoded } 9 | 10 | (* 11 | type CustomMod = 12 | | ModOpt of ModifierType: TypeDefOrRefOrSpecEncoded 13 | | ModReq of ModifierType: TypeDefOrRefOrSpecEncoded 14 | *) 15 | 16 | [] 17 | type CustomModifiers = System.Collections.Immutable.ImmutableArray 18 | 19 | [] 20 | module CustomModPatterns = 21 | let inline (|ModOpt|ModReq|) { Required = req; ModifierType = mtype } = 22 | if req then ModReq mtype else ModOpt mtype 23 | 24 | let inline (|NoRequiredModifiers|HasRequiredModifiers|) modifiers = 25 | let rec inner = 26 | function 27 | | [] -> NoRequiredModifiers 28 | | { CustomMod.Required = true } :: _ -> HasRequiredModifiers 29 | | _ :: remaining -> inner remaining 30 | inner modifiers 31 | 32 | let inline ModOpt mtype = { Required = false; ModifierType = mtype } 33 | let inline ModReq mtype = { Required = true; ModifierType = mtype } 34 | -------------------------------------------------------------------------------- /docs/FSharpIL.Examples/ExampleHelpers.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module FSharpIL.ExampleHelpers 3 | 4 | open Expecto 5 | 6 | open System.Diagnostics 7 | open System.IO 8 | 9 | open FSharpIL.Writing 10 | 11 | open Mono.Cecil 12 | 13 | [] 14 | module PEFile = 15 | let inline toCecilModule pe = ModuleDefinition.ReadModule(WritePE.stream pe) 16 | 17 | let private testExec testf (file: System.Lazy<_>) (name: string) dir path fileName (test: Process -> _ -> unit): Test = 18 | fun() -> 19 | let output = Path.Combine(dir, path) 20 | let executable = Path.Combine(output, fileName) 21 | 22 | WritePE.toPath executable file.Value 23 | 24 | let config = Path.Combine(output, "example.runtimeconfig.json") 25 | 26 | use dotnet = 27 | ProcessStartInfo ( 28 | fileName = "dotnet", 29 | arguments = sprintf "exec --runtimeconfig %s %s" config executable, 30 | RedirectStandardOutput = true, 31 | RedirectStandardError = true, 32 | UseShellExecute = false 33 | ) 34 | |> Process.Start 35 | 36 | let test' = test dotnet 37 | dotnet.WaitForExit() 38 | test'() 39 | |> testf name 40 | 41 | /// 42 | /// Builds a test case that writes the Portable Executable to disk and executes it with the dotnet exec command. 43 | /// 44 | let testCaseExec file = testExec testCase file 45 | let ftestCaseExec file = testExec ftestCase file 46 | -------------------------------------------------------------------------------- /src/FSharpIL/Utilities/Span.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module internal FSharpIL.Utilities.Span 3 | 4 | open System 5 | open System.Runtime.CompilerServices 6 | 7 | open Microsoft.FSharp.NativeInterop 8 | 9 | #nowarn "9" // Uses of this construct may result in the generation of unverifiable .NET IL code. 10 | 11 | /// Creates a from a region of memory allocated on the stack. 12 | [] 13 | let inline stackalloc<'T when 'T : unmanaged> length = Span<'T>(NativePtr.toVoidPtr(NativePtr.stackalloc<'T> length), length) 14 | 15 | /// Creates a from an array allocated in the heap. 16 | [] 17 | let inline heapalloc<'T> length = Span<'T>(Array.zeroCreate<'T> length) 18 | 19 | let inline asReadOnly span = Span<'T>.op_Implicit(span): ReadOnlySpan<'T> 20 | 21 | let inline toBlock (span: Span<'T>) = 22 | let mutable data = Array.zeroCreate<'T> span.Length 23 | span.CopyTo(Span data) 24 | Unsafe.As<_, System.Collections.Immutable.ImmutableArray<'T>> &data 25 | 26 | let readOnlyEqual (left: ReadOnlySpan<'T>) (right: ReadOnlySpan<'T>) = 27 | if left.Length = right.Length then 28 | let mutable equal, i = true, 0 29 | while equal && i < left.Length do 30 | if left.[i] <> right.[i] 31 | then equal <- false 32 | else i <- i + 1 33 | equal 34 | else false 35 | 36 | let inline fromEnd source endi length = Span<'T>(source, endi - length + 1, length) 37 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/WriteIndex.fs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Contains functions for writing simple indices into tables (II.24.2.6), coded indices that can point into one of many 3 | /// possible tables (II.24.2.6), or offsets into metadata streams (II.24.2.6). 4 | /// 5 | [] 6 | module private FSharpIL.Writing.WriteIndex 7 | 8 | open FSharpIL.Metadata 9 | open FSharpIL.Metadata.Tables 10 | 11 | let table (wr: byref) counts table { TableIndex = i } = 12 | if TableIndex.isLarge table counts 13 | then wr.WriteLE i 14 | else wr.WriteLE(uint16 i) 15 | 16 | let coded<'Tag when 'Tag : enum and 'Tag : comparison> 17 | (wr: byref) 18 | counts 19 | (kind: inref>) 20 | (index: CodedIndex<'Tag>) 21 | = 22 | let i = (index.Index <<< kind.NumEncodingBits) ||| uint32(LanguagePrimitives.EnumToValue index.Tag) 23 | if kind.IsLarge counts 24 | then wr.WriteLE i 25 | else wr.WriteLE(uint16 i) 26 | 27 | let private offset (wr: byref) size (offset: uint32) = 28 | match size with 29 | | 2u -> wr.WriteLE(uint16 offset) 30 | | 4u 31 | | _ -> wr.WriteLE offset 32 | 33 | let string (wr: byref<_>) (sizes: HeapSizes) { StringOffset = str } = 34 | offset &wr sizes.StringSize str 35 | 36 | let guid (wr: byref<_>) (sizes: HeapSizes) { GuidIndex = index } = 37 | offset &wr sizes.GuidSize index 38 | 39 | let blob (wr: byref<_>) (sizes: HeapSizes) { BlobOffset = blob } = 40 | offset &wr sizes.GuidSize blob 41 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/MetadataToken.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata 2 | 3 | open FSharpIL.Utilities 4 | 5 | type MetadataTokenType = 6 | | Module = 0uy 7 | | TypeRef = 1uy 8 | | TypeDef = 2uy 9 | | Field = 4uy 10 | | MethodDef = 6uy 11 | | MemberRef = 0xAuy 12 | | StandaloneSig = 0x11uy 13 | | TypeSpec = 0x1Buy 14 | | File = 0x26uy 15 | | MethodSpec = 0x2Buy 16 | | UserStringHeap = 0x70uy 17 | 18 | /// 19 | /// Represents a metadata token (III.1.9). 20 | /// 21 | [] 22 | [] 23 | type MetadataToken internal (value: uint32) = 24 | static let [] IndexMask = 0xFFFFFFu 25 | 26 | internal new (tag: MetadataTokenType, index) = 27 | if index > IndexMask then argOutOfRange "index" index "The index must be able to fit into 3 bytes" 28 | MetadataToken((uint32 tag <<< 24) ||| index) 29 | 30 | member _.Index = value &&& IndexMask 31 | member _.Type = LanguagePrimitives.EnumOfValue<_, MetadataTokenType>(uint8(value >>> 24)) 32 | member _.IsNull = value = 0u 33 | member _.Value = value 34 | 35 | static member op_Implicit(token: MetadataToken) = token.Value 36 | 37 | override this.ToString() = 38 | if this.IsNull 39 | then System.String.Empty 40 | else sprintf "%A (0x%08X)" this.Type this.Index 41 | 42 | [] 43 | module MetadataToken = 44 | let inline (|Token|Null|) (token: MetadataToken) = 45 | if token.IsNull 46 | then Null 47 | else Token(struct(token.Type, token.Index)) 48 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/Parameter.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open System.Collections.Immutable 4 | 5 | open FSharpIL.Metadata 6 | open FSharpIL.Metadata.Tables 7 | 8 | [] 9 | type ParameterKind = 10 | | Default 11 | | InRef 12 | | OutRef 13 | 14 | [] 15 | type ParameterType = 16 | | T of CliType 17 | | ByRef of modifiers: ImmutableArray * CliType 18 | | TypedByRef of modifiers: ImmutableArray 19 | 20 | [] 21 | type Parameter = 22 | { Kind: ParameterKind 23 | DefaultValue: Constant voption 24 | ParamName: Identifier voption } 25 | 26 | module ParameterType = 27 | let TypedByRef' = ParameterType.TypedByRef ImmutableArray.Empty 28 | 29 | type ParameterList = int32 -> ParameterType -> Parameter 30 | 31 | [] 32 | module Parameter = 33 | let flags (parameter: inref<_>) = 34 | let mutable value = 35 | match parameter.Kind with // TODO: Are In AND Out flags ever both set at the same time? 36 | | ParameterKind.Default -> ParamFlags.None 37 | | ParameterKind.InRef -> ParamFlags.In 38 | | ParameterKind.OutRef -> ParamFlags.Out 39 | 40 | // TODO: Set flag if parameter is Optional 41 | 42 | if parameter.DefaultValue.IsSome then value <- value ||| ParamFlags.HasDefault 43 | 44 | value 45 | 46 | let named name = 47 | { Kind = ParameterKind.Default 48 | DefaultValue = ValueNone 49 | ParamName = ValueSome name } 50 | 51 | let emptyList: ParameterList = fun _ _ -> Unchecked.defaultof 52 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/Parameter.fsi: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open System 4 | open System.Collections.Immutable 5 | open System.Runtime.CompilerServices 6 | 7 | open FSharpIL.Metadata 8 | 9 | [] 10 | type ParameterKind = 11 | | Default 12 | | InRef 13 | | OutRef 14 | 15 | interface IEquatable 16 | 17 | [] 18 | type ParameterType = 19 | | T of CliType 20 | | ByRef of modifiers: ImmutableArray * CliType 21 | | TypedByRef of modifiers: ImmutableArray 22 | 23 | interface IEquatable 24 | 25 | [] 26 | module ParameterType = 27 | val TypedByRef' : ParameterType 28 | 29 | [] 30 | type Parameter = 31 | { Kind: ParameterKind 32 | DefaultValue: Constant voption 33 | ParamName: Identifier voption } // TODO: Have field that allows setting of Optional flag. 34 | 35 | type ParameterList = int32 -> ParameterType -> Parameter 36 | 37 | [] 38 | module Parameter = 39 | val flags: parameter: inref -> FSharpIL.Metadata.Tables.ParamFlags 40 | 41 | val named: name: Identifier -> Parameter 42 | 43 | val emptyList: ParameterList 44 | 45 | // TODO: Allow more efficient ways of generating parameter list 46 | (* 47 | type IParameterList = interface 48 | abstract GetParameter: index: int32 * parameterType: inref 49 | end 50 | 51 | type ParameterList = delegate of int32 * inref -> Parameter voption 52 | *) 53 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/PEFileReader.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open System.Collections.Immutable 4 | 5 | open FSharpIL.PortableExecutable 6 | 7 | type ParsedCoffHeader = CoffHeader 8 | /// Represents a PE file optional header that has been parsed (II.25.2.3). 9 | type ParsedOptionalHeader = OptionalHeader 10 | /// Represents the data directories of a PE File that have been parsed (II.25.2.3.3). 11 | type ParsedDataDirectories = DataDirectories voption * ImmutableArray 12 | type ParsedSectionHeaders = ImmutableArray 13 | 14 | /// Collection of functions used to read a Portable Executable file (II.25). 15 | [] 16 | type PEFileReader<'State> = 17 | { ReadLfanew: StructureReader 18 | ReadCoffHeader: StructureReader 19 | ReadOptionalHeader: StructureReader 20 | ReadDataDirectories: StructureReader 21 | ReadSectionHeaders: StructureReader 22 | ReadCliMetadata: MetadataReader<'State> voption 23 | HandleError: ErrorHandler<'State> } 24 | 25 | [] 26 | module PEFileReader = 27 | let defaultReader<'State> = 28 | { ReadLfanew = ValueNone 29 | ReadCoffHeader = ValueNone 30 | ReadOptionalHeader = ValueNone 31 | ReadDataDirectories = ValueNone 32 | ReadSectionHeaders = ValueNone 33 | ReadCliMetadata = ValueNone 34 | HandleError = ErrorHandler.throwOnError } 35 | : PEFileReader<'State> 36 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Tables/TablesHeader.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata.Tables 2 | 3 | open FSharpIL.Utilities 4 | 5 | /// 6 | /// Specifies the sizes of offsets into the #Strings, #GUID, and #Blob streams (II.24.2.6). 7 | /// 8 | [] 9 | type HeapSizes = 10 | | None = 0uy 11 | /// Specifies that offsets into the #Strings stream should be 4 bytes wide. 12 | | String = 0x1uy 13 | /// Specifies that offsets into the #GUID stream should be 4 bytes wide. 14 | | Guid = 0x2uy 15 | /// Specifies that offsets into the #Blob stream should be 4 bytes wide. 16 | | Blob = 0x4uy 17 | 18 | [] 19 | module HeapSizes = 20 | let inline private isLarge (flags: HeapSizes) heap = if Flags.set heap flags then 4u else 2u 21 | type HeapSizes with 22 | member this.StringSize = isLarge this HeapSizes.String 23 | member this.GuidSize = isLarge this HeapSizes.Guid 24 | member this.BlobSize = isLarge this HeapSizes.Blob 25 | 26 | /// 27 | /// Represents the fields of the #~ stream, which contain information about the metadata tables (II.24.2.6). 28 | /// 29 | type TablesHeader<'RowCounts when 'RowCounts :> ITableRowCounts> = 30 | { Reserved1: uint32 31 | MajorVersion: uint8 32 | MinorVersion: uint8 33 | HeapSizes: HeapSizes 34 | Reserved2: uint8 35 | /// Specifies which metadata tables are present. 36 | Valid: ValidTableFlags 37 | Sorted: ValidTableFlags 38 | /// Specifies the number of rows in each present metadata table. 39 | Rows: 'RowCounts } 40 | -------------------------------------------------------------------------------- /src/FSharpIL/PortableExecutable/PEFile.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.PortableExecutable 2 | 3 | open System.Collections.Immutable 4 | 5 | open FSharpIL 6 | open FSharpIL.Utilities 7 | 8 | [] 9 | type Section = struct 10 | val Header: SectionHeader 11 | val Data: ChunkedMemory 12 | internal new (header, data) = { Header = header; Data = data } 13 | end 14 | 15 | [] 16 | type PEFile internal 17 | ( 18 | fileHeader: CoffHeader, 19 | optionalHeader: OptionalHeader, 20 | dataDirectories: DataDirectories, 21 | sections: ImmutableArray
, 22 | sizeOfHeaders: uint32 23 | ) 24 | = 25 | member _.FileHeader = fileHeader 26 | member _.OptionalHeader = optionalHeader 27 | member _.DataDirectories = dataDirectories 28 | member _.Sections = sections 29 | member _.SizeOfHeaders = sizeOfHeaders 30 | 31 | [] 32 | module PEFile = 33 | /// 34 | /// Calculates the value of the SizeOfHeaders field in the optional header, rounded up to a multiple of 35 | /// FileAlignment (II.25.2.3.2). 36 | /// 37 | let internal calculateHeadersSize optionalHeader numOfSections falignment = 38 | let optionalHeaderSize = 39 | match optionalHeader with 40 | | PE32 _ -> Magic.OptionalHeaderSize 41 | | PE32Plus _ -> Magic.OptionalHeaderPlusSize 42 | uint32 Magic.msDosStub.Length 43 | + uint32 Magic.portableExecutableSignature.Length 44 | + uint32 optionalHeaderSize 45 | + (Magic.SectionHeaderSize * uint32 numOfSections) 46 | |> Round.upTo falignment 47 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/Property.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open System 4 | 5 | open FSharpIL.Metadata 6 | 7 | open FSharpIL.Utilities 8 | open FSharpIL.Utilities.Compare 9 | 10 | [] 11 | type Property (name, getter, setter, other) = 12 | member _.Flags = FSharpIL.Metadata.Tables.PropertyFlags.None 13 | member _.Name: Identifier = name 14 | member _.Getter: DefinedMethod voption = getter 15 | member _.Setter: DefinedMethod voption = setter 16 | member _.Other: DefinedMethod list = other 17 | 18 | override _.GetHashCode() = HashCode.Combine(name, getter, setter) 19 | 20 | interface IEquatable with 21 | member _.Equals other = 22 | let inline propMethodsEqual a b = 23 | match a, b with 24 | | ValueSome a', ValueSome b' -> Method.signatureComparer.Equals(a', b') 25 | | ValueNone, ValueNone -> true 26 | | _ -> false 27 | 28 | name === other.Name && 29 | propMethodsEqual getter other.Getter && 30 | propMethodsEqual setter other.Setter 31 | 32 | override this.Equals obj = 33 | match obj with 34 | | :? Property as other -> this === other 35 | | _ -> false 36 | 37 | module PropertyPatterns = 38 | let inline (|PropertyMethods|) (property: Property) = struct(property.Getter, property.Setter, property.Other) 39 | 40 | [] 41 | type Property<'Kind when 'Kind :> MethodKinds.IKind and 'Kind : struct> (property: Property) = 42 | member _.Property = property 43 | member _.Getter = Convert.unsafeValueOption<_, MethodDefinition<'Kind>> property.Getter 44 | member _.Setter = Convert.unsafeValueOption<_, MethodDefinition<'Kind>> property.Setter 45 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Cil/LocalVarIndex.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata.Cil 2 | 3 | open FSharpIL.Utilities 4 | 5 | [] 6 | type LocalVarIndex = 7 | private { Index: uint16 } 8 | override this.ToString() = string this.Index 9 | static member MinValue = { Index = 0us } 10 | static member MaxValue = { Index = FSharpIL.Metadata.Signatures.LocalVariable.maxLocalCount - 1us } 11 | static member op_Implicit { Index = i } = int32 i 12 | static member op_Implicit { Index = i } = uint32 i 13 | static member op_Implicit { Index = i } = int64 i 14 | static member op_Implicit { Index = i } = uint64 i 15 | static member op_Implicit { Index = i } = i 16 | static member op_Explicit(index: uint8) = { Index = uint16 index } 17 | static member op_Explicit(index: uint16) = 18 | let index' = { Index = index } 19 | if index' > LocalVarIndex.MaxValue then 20 | argOutOfRange (nameof index) index (sprintf "Local variable indices cannot exceed %O" LocalVarIndex.MaxValue) 21 | index' 22 | 23 | [] 24 | module LocalVarIndex = 25 | let inline (|LocalVarIndex|) (index: LocalVarIndex) = uint16 index 26 | 27 | /// Converts an integer into a local variable index. 28 | /// 29 | /// Thrown when an overflow occurs when the is converted into an unsigned 16-bit integer. 30 | /// 31 | /// 32 | /// Thrown when the exceeds the maximum valid local variable index. 33 | /// 34 | let inline locali index = LocalVarIndex.op_Explicit(Checked.uint16 index) 35 | -------------------------------------------------------------------------------- /src/FSharpIL/Utilities/Collections/LateInitCollection.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Utilities.Collections 2 | 3 | open System.Collections.Generic 4 | 5 | type internal LateInitCollection<'Item, 'Inner when 'Inner :> ICollection<'Item> and 'Inner : (new: unit -> 'Inner) and 'Inner : null> = struct 6 | val mutable internal inner: 'Inner 7 | 8 | new (inner) = { inner = inner } 9 | 10 | member this.IsInitialized = not(isNull this.inner) 11 | member this.Count = if this.IsInitialized then this.inner.Count else 0 12 | member this.IsEmpty = this.Count = 0 13 | 14 | member this.Inner = 15 | if isNull this.inner then this.inner <- new 'Inner() 16 | this.inner 17 | 18 | member this.Add item = this.Inner.Add item 19 | 20 | member this.GetEnumerator() = if this.IsInitialized then this.Inner.GetEnumerator() else Seq.empty.GetEnumerator() 21 | end 22 | 23 | type internal LateInitDictionary<'Key, 'Value when 'Key : equality> = struct 24 | val mutable inner: LateInitCollection, Dictionary<'Key, 'Value>> 25 | 26 | new (inner) = { inner = inner } 27 | 28 | member this.Item 29 | with get key = 30 | if this.inner.IsInitialized 31 | then this.inner.inner.[key] 32 | else invalidOp "The dictionary was not yet initialized" 33 | and set key value = this.inner.Inner.[key] <- value 34 | 35 | member this.TryGetValue(key, value: outref<_>) = 36 | if this.IsInitialized 37 | then this.inner.inner.TryGetValue(key, &value) 38 | else false 39 | 40 | member this.Count = this.inner.Count 41 | member this.Inner = this.inner.Inner 42 | member this.IsInitialized = this.inner.IsInitialized 43 | 44 | member this.GetEnumerator() = this.inner.GetEnumerator() 45 | end 46 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/Assembly.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open System 4 | open System.Collections.Immutable 5 | 6 | open FSharpIL.Metadata 7 | open FSharpIL.Metadata.Tables 8 | 9 | open FSharpIL.Utilities.Compare 10 | 11 | [] 12 | type ReferencedAssembly = 13 | { Version: AssemblyVersion 14 | PublicKeyOrToken: PublicKeyOrToken 15 | Name: FileName 16 | Culture: Identifier voption 17 | HashValue: ImmutableArray } 18 | 19 | member this.Flags = 20 | match this.PublicKeyOrToken with 21 | | NoPublicKeyOrToken 22 | | PublicKeyToken _ -> AssemblyFlags.None 23 | | PublicKey _ -> AssemblyFlags.PublicKey 24 | 25 | override this.ToString() = this.Name.ToString() 26 | 27 | override this.GetHashCode() = HashCode.Combine(this.Name, this.Version) 28 | 29 | interface IEquatable with 30 | member this.Equals other = this.Version === other.Version && this.Name === other.Name 31 | 32 | override this.Equals obj = 33 | match obj with 34 | | :? ReferencedAssembly as other -> this === other 35 | | _ -> false 36 | 37 | [] 38 | type DefinedAssembly = 39 | { Version: AssemblyVersion 40 | //Flags: AssemblyFlags 41 | PublicKey: ImmutableArray 42 | Name: FileName 43 | Culture: Identifier voption } 44 | 45 | override this.ToString() = this.Name.ToString() 46 | 47 | override this.GetHashCode() = HashCode.Combine(this.Name, this.Version) 48 | 49 | interface IEquatable with 50 | member this.Equals other = this.Version === other.Version && this.Name === other.Name 51 | 52 | override this.Equals obj = 53 | match obj with 54 | | :? DefinedAssembly as other -> this === other 55 | | _ -> false 56 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/GuidStreamBuilder.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Writing 2 | 3 | open System 4 | open System.Collections.Generic 5 | open System.Collections.Immutable 6 | 7 | open FSharpIL.Metadata 8 | 9 | open FSharpIL.Utilities 10 | 11 | /// Builds the #GUID metadata stream (II.24.2.5). 12 | [] 13 | type GuidStreamBuilder (capacity: int32) = 14 | static let empty = Guid.Empty 15 | let guids = ImmutableArray.CreateBuilder capacity 16 | let lookup = Dictionary capacity 17 | do guids.Add empty // First GUID is at index 1, so 0 is treated as null. 18 | do lookup.[Guid.Empty] <- GuidIndex.Zero 19 | member _.IsEmpty = guids.Count = 1 20 | /// Gets the number of GUIDs stored in this stream. 21 | member _.Count = guids.Count - 1 22 | /// The length of the #GUID metadata stream, in bytes. 23 | member this.StreamLength = uint32 this.Count * 16u 24 | 25 | member _.Add guid = 26 | match lookup.TryGetValue guid with 27 | | true, existing -> existing 28 | | false, _ -> 29 | let i = { GuidIndex = uint32 guids.Count } 30 | lookup.[guid] <- i 31 | guids.Add guid 32 | i 33 | 34 | /// Generates a new GUID and adds it to the stream. 35 | member inline this.AddNew() = this.Add(Guid.NewGuid()) 36 | 37 | interface IStreamBuilder with 38 | member this.StreamLength = ValueSome this.StreamLength 39 | member _.StreamName = Magic.StreamNames.guid 40 | member this.Serialize(builder, _, _) = 41 | let mutable buffer = Span.stackalloc sizeof 42 | for i = 1 to this.Count do 43 | if not (guids.ItemRef(i).TryWriteBytes buffer) then failwithf "Could not write GUID at index %i" i 44 | builder.Write buffer 45 | -------------------------------------------------------------------------------- /src/FSharpIL/PortableExecutable/DefaultHeaders.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module FSharpIL.PortableExecutable.DefaultHeaders 3 | 4 | open FSharpIL 5 | 6 | /// Default PE file header indicating that the file is a .dll file. 7 | let coffHeader = 8 | { Machine = MachineFlags.I386 9 | NumberOfSections = Omitted 10 | TimeDateStamp = 0u 11 | SymbolTablePointer = 0u 12 | SymbolCount = 0u 13 | OptionalHeaderSize = Omitted 14 | Characteristics = FileCharacteristics.IsDll } 15 | 16 | let standardFields = 17 | { Magic = Omitted 18 | LMajor = 8uy 19 | LMinor = 0uy 20 | CodeSize = Omitted 21 | InitializedDataSize = Omitted 22 | UninitializedDataSize = Omitted 23 | EntryPointRva = Omitted 24 | BaseOfCode = Omitted 25 | BaseOfData = Omitted } 26 | 27 | let ntSpecificFields = 28 | { ImageBase = ImageBase.Default 29 | Alignment = Alignment.Default 30 | // NOTE: OSMajor and SubSysMajor both have values of 0x04 in some assemblies 31 | OSMajor = 0x05us 32 | OSMinor = 0us 33 | UserMajor = 0us 34 | UserMinor = 0us 35 | SubSysMajor = 0x05us 36 | SubSysMinor = 0us 37 | Win32VersionValue = 0u 38 | ImageSize = Omitted 39 | HeadersSize = Omitted 40 | FileChecksum = 0u 41 | Subsystem = ImageSubsystem.WindowsCui 42 | DllFlags = 43 | PEFileFlags.DynamicBase ||| 44 | PEFileFlags.NoSEH ||| 45 | PEFileFlags.NXCompatible 46 | StackReserveSize = 0x100000u 47 | StackCommitSize = 0x1000u 48 | HeapReserveSize = 0x100000u 49 | HeapCommitSize = 0x1000u 50 | LoaderFlags = 0u 51 | NumberOfDataDirectories = Omitted } 52 | 53 | /// Default PE32 optional header. 54 | let optionalHeader = OptionalHeader.PE32(standardFields, ntSpecificFields) 55 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/EntryPointToken.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata 2 | 3 | open FSharpIL.Metadata.Tables 4 | 5 | /// 6 | /// Represents an EntryPointToken, which specifies the MethodDef or File that is the entry point 7 | /// (II.25.3.3). 8 | /// 9 | [] 10 | type EntryPointToken = struct 11 | val Token: MetadataToken 12 | 13 | internal new (token) = { Token = token } 14 | new (index: TableIndex) = { Token = MetadataToken(MetadataTokenType.MethodDef, index.TableIndex) } 15 | new (index: TableIndex) = { Token = MetadataToken(MetadataTokenType.File, index.TableIndex) } 16 | 17 | member this.IsMethodDef = this.Token.Type = MetadataTokenType.MethodDef 18 | member this.IsFile = this.Token.Type = MetadataTokenType.File 19 | member this.IsNull = this.Token.IsNull 20 | 21 | static member op_Implicit(token: EntryPointToken) = uint32 token.Token 22 | end 23 | 24 | (* 25 | [] 26 | [] 27 | type EntryPointToken = 28 | | MethodDef of TableIndex 29 | | File of TableIndex 30 | | Null 31 | *) 32 | 33 | [] 34 | module EntryPointToken = 35 | let inline MethodDef (index: TableIndex) = EntryPointToken index 36 | let inline File (index: TableIndex) = EntryPointToken index 37 | let Null = EntryPointToken() 38 | 39 | let tryOfToken token = 40 | match token with 41 | | MetadataToken.Null -> Ok Null 42 | | MetadataToken.Token(MetadataTokenType.MethodDef, _) 43 | | MetadataToken.Token(MetadataTokenType.File, _) -> Ok(EntryPointToken token) 44 | | MetadataToken.Token(table, _) -> Error table 45 | 46 | let tryOfInt value = tryOfToken(MetadataToken value) 47 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/StreamOffsets.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata 2 | 3 | open System.Runtime.CompilerServices 4 | 5 | /// Represents an offset into the #Strings metadata stream (II.24.2.3). 6 | [] 7 | type StringOffset = 8 | internal { StringOffset: uint32 } 9 | 10 | override this.ToString() = sprintf "0x%08X" this.StringOffset 11 | 12 | static member op_Implicit { StringOffset = offset } = offset 13 | 14 | /// Represents an index into the #GUID metadata stream (II.24.2.5). 15 | [] 16 | type GuidIndex = 17 | internal { GuidIndex: uint32 } 18 | member this.IsNull = this.GuidIndex = 0u 19 | 20 | override this.ToString() = sprintf "0x%X" this.GuidIndex 21 | 22 | static member op_Implicit { GuidIndex = offset } = offset 23 | static member Zero = { GuidIndex = 0u } 24 | 25 | /// Represents an index into the #US metadata stream (II.24.2.4). 26 | [] 27 | type UserStringOffset = 28 | internal { UserStringOffset: uint32 } 29 | 30 | member this.IsZero = this.UserStringOffset = 0u 31 | 32 | override this.ToString() = sprintf "0x%08X" this.UserStringOffset 33 | 34 | static member op_Implicit { UserStringOffset = offset } = offset 35 | 36 | /// Represents an offset into the #Blob metadata stream (II.24.2.4). 37 | [] 38 | type BlobOffset = 39 | internal { BlobOffset: uint32 } 40 | 41 | member this.IsZero = this.BlobOffset = 0u 42 | 43 | override this.ToString() = sprintf "0x%08X" this.BlobOffset 44 | 45 | static member op_Implicit { BlobOffset = offset } = offset 46 | 47 | [] 48 | module BlobOffset = 49 | let inline (|IsZero|NonZero|) (offset: BlobOffset) = if offset.IsZero then IsZero else NonZero(uint32 offset) 50 | -------------------------------------------------------------------------------- /docs/FSharpIL.Documentation/Html.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module internal FSharpIL.Documentation.Html 3 | 4 | open System.IO 5 | 6 | type Element = StreamWriter -> unit 7 | 8 | let inline (!^) (text: string) = 9 | fun (writer: StreamWriter) -> writer.Write text 10 | 11 | let tag (name: string) attributes content (writer: StreamWriter) = 12 | writer.Write '<' 13 | writer.Write name 14 | for (attr: string, value: string) in attributes do 15 | writer.Write ' ' 16 | writer.Write attr 17 | writer.Write "=\"" 18 | writer.Write value 19 | writer.Write '"' 20 | if Seq.isEmpty content 21 | then writer.Write "/>" 22 | else 23 | writer.Write '>' 24 | for (elem: Element) in content do elem writer 25 | writer.Write "' 28 | 29 | let comment (content: seq) (writer: StreamWriter) = 30 | writer.Write "" 33 | 34 | let html (stream: Stream) attributes content = 35 | use writer = new StreamWriter(stream) 36 | writer.WriteLine("") 37 | tag "html" attributes content writer 38 | 39 | let head content: Element = tag "head" Seq.empty content 40 | let link rel href: Element = tag "link" [| "rel", rel; "href", href |] Seq.empty 41 | let meta attributes: Element = tag "meta" attributes Seq.empty 42 | let title name: Element = tag "title" Seq.empty [| !^name |] 43 | 44 | let body attributes content: Element = tag "body" attributes content 45 | let a attributes href content: Element = 46 | let attributes' = seq { yield! attributes; "href", href } 47 | tag "a" attributes' content 48 | let h2 attributes text: Element = tag "h2" attributes [ !^text ] 49 | let li attributes content: Element = tag "li" attributes content 50 | let ul attributes content: Element = tag "ul" attributes content 51 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/StringHelpers.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module internal FSharpIL.Writing.StringHelpers 3 | 4 | open System 5 | 6 | open FSharpIL 7 | open FSharpIL.Utilities 8 | 9 | let comparer = 10 | { new System.Collections.Generic.IEqualityComparer> with 11 | member _.Equals(x, y) = MemoryExtensions.Equals(x.Span, y.Span, StringComparison.Ordinal) 12 | member _.GetHashCode str = str.GetHashCode() } 13 | 14 | type IStringSerializer<'String when 'String : struct> = interface 15 | abstract WriteBefore: inref<'String> * byref -> unit 16 | abstract GetChars: inref<'String> -> inref> 17 | abstract WriteAfter: inref<'String> * byref -> unit 18 | end 19 | 20 | let serializeStringHeap<'Serializer, 'String when 'Serializer :> IStringSerializer<'String> and 'Serializer : struct> 21 | (encoding: System.Text.Encoding) 22 | (wr: byref) 23 | (strings: System.Collections.Immutable.ImmutableArray<'String>.Builder) 24 | = 25 | let encoder = encoding.GetEncoder() 26 | let buffer = Span.stackalloc 256 27 | let mutable chars = ReadOnlySpan() 28 | for i = 0 to strings.Count - 1 do 29 | let string = &strings.ItemRef i 30 | Unchecked.defaultof<'Serializer>.WriteBefore(&string, &wr) 31 | 32 | chars <- Unchecked.defaultof<'Serializer>.GetChars(&string).Span 33 | 34 | if chars.Length > 0 then 35 | let mutable charsUsed, bytesUsed, completed = 0, 0, false 36 | 37 | while not completed || charsUsed > 0 do 38 | encoder.Convert(chars, buffer, chars.IsEmpty, &charsUsed, &bytesUsed, completed = &completed) 39 | wr.Write(buffer.Slice(0, bytesUsed)) 40 | chars <- chars.Slice charsUsed 41 | 42 | Unchecked.defaultof<'Serializer>.WriteAfter(&string, &wr) 43 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/BuildCli.fs: -------------------------------------------------------------------------------- 1 | module FSharpIL.Writing.BuildCli 2 | 3 | open System.Collections.Generic 4 | 5 | open FSharpIL.Metadata 6 | 7 | open FSharpIL.Cli 8 | 9 | [] 10 | type ModuleUpdate = 11 | | AddDefinedType of TypeDefinition 12 | | Finish 13 | 14 | [] 15 | module ModuleUpdate = 16 | let finish = ModuleUpdate.Finish 17 | 18 | [] 19 | type ModuleBuilder<'State> = 20 | { Update: 'State -> ModuleUpdate 21 | Warning: ('State -> IValidationWarning -> 'State) option 22 | ReferenceType: 'State -> TypeReference -> 'State 23 | DefineType: 'State -> TypeDefinition -> 'State } 24 | 25 | [] 26 | module ModuleBuilder = 27 | type CachedBuilder<'State> private () = 28 | static member val Ignored: ModuleBuilder<'State> = 29 | let inline ignore1 state _ = state 30 | { Update = fun _ -> Finish 31 | Warning = None 32 | ReferenceType = ignore1 33 | DefineType = ignore1 } 34 | 35 | let ignored<'State> = CachedBuilder<'State>.Ignored 36 | 37 | let run header root name mvid builder state = 38 | let builder' = 39 | let warnings = 40 | match builder.Warning with 41 | | Some _ -> Some(LinkedList<_>() :> ICollection<_>) 42 | | None -> None 43 | 44 | CliModuleBuilder ( 45 | name = name, 46 | mvid = mvid, 47 | cliMetadataHeader = header, 48 | cliMetadataRoot = root, 49 | ?warnings = warnings 50 | ) 51 | 52 | let rec inner state = 53 | match builder.Update state with 54 | | ModuleUpdate.Finish -> ValidationResult.Ok(builder', state) 55 | | _ -> FSharpIL.Utilities.Fail.noImpl "what next?" 56 | 57 | inner state 58 | -------------------------------------------------------------------------------- /src/FSharpIL/PortableExecutable/SectionName.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.PortableExecutable 2 | 3 | open System.Runtime.CompilerServices 4 | 5 | open FSharpIL.Utilities 6 | 7 | /// Specifies the name of a section in its section header (II.25.3). 8 | [] 9 | [] 10 | type SectionName = 11 | internal { SectionName: byte[] } 12 | /// The length of the section name, including null padding. 13 | member this.Length = this.SectionName.Length 14 | static member internal Encoding = System.Text.Encoding.ASCII 15 | override this.ToString() = SectionName.Encoding.GetString(this.SectionName).TrimEnd '\000' 16 | 17 | [] 18 | module SectionName = 19 | // TODO: Figure out if null padding bytes are included or not 20 | let text = { SectionName = ".text\000\000\000"B } 21 | let rsrc = { SectionName = ".rsrc\000\000\000"B } 22 | let reloc = { SectionName = ".reloc\000\000"B } 23 | 24 | let asSpan { SectionName = bytes } = System.ReadOnlySpan bytes 25 | 26 | let tryOfBytes (bytes: byte[]) = 27 | if bytes.Length <= 8 then 28 | let name = Array.zeroCreate 8 29 | for i = 0 to name.Length - 1 do 30 | name.[i] <- 31 | if i >= bytes.Length 32 | then 0uy 33 | else bytes.[i] 34 | ValueSome { SectionName = name } 35 | else ValueNone 36 | 37 | let tryOfStr (str: string) = tryOfBytes(SectionName.Encoding.GetBytes str) 38 | 39 | let ofStr str = 40 | match tryOfStr str with 41 | | ValueSome name -> name 42 | | ValueNone -> 43 | invalidArg 44 | (nameof str) 45 | (sprintf "The section name \"%s\" must not be longer than 8 bytes and cannot contain any null characters" str) 46 | 47 | let toBlock { SectionName = bytes } = Convert.unsafeTo bytes 48 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/ParsedStringsStream.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open System 4 | open System.Collections.Generic 5 | open System.Text 6 | 7 | open FSharpIL.Utilities 8 | 9 | open FSharpIL 10 | open FSharpIL.Metadata 11 | 12 | /// Represents the #Strings metadata stream, which contains null-terminated UTF-8 strings (II.24.2.3). 13 | [] 14 | type ParsedStringsStream (stream: ChunkedMemory) = 15 | let mutable lookup = Dictionary() 16 | let mutable buffer = Array.zeroCreate 32 17 | 18 | new () = ParsedStringsStream ChunkedMemory.empty 19 | 20 | member _.IsEmpty = stream.IsEmpty 21 | member _.Size = stream.Length 22 | 23 | member _.TryGetString ({ StringOffset = offset' } as offset) = 24 | if offset' < stream.Length then 25 | match lookup.TryGetValue offset' with 26 | | true, existing -> Ok existing 27 | | false, _ -> 28 | let mutable i, cont = 0, true 29 | 30 | while cont && uint32 i < stream.Length do 31 | let offset'' = uint32 i + offset' 32 | if i >= buffer.Length then Array.Resize(&buffer, buffer.Length * 2) 33 | match stream.[offset''] with 34 | | 0uy -> cont <- false 35 | | value -> 36 | buffer.[i] <- value 37 | i <- i + 1 38 | 39 | let entry = Encoding.UTF8.GetString((ReadOnlySpan buffer).Slice(0, i)) 40 | lookup.[offset'] <- entry 41 | Ok entry 42 | else Error(InvalidStringOffset(offset, { StringOffset = stream.Length - 1u })) 43 | 44 | [] 45 | module ParsedStringsStream = 46 | let tryCreate (stream: ChunkedMemory) = 47 | if stream.IsEmpty || stream.[stream.Length - 1u] = 0uy 48 | then Ok(ParsedStringsStream stream) 49 | else Error MissingStringStreamTerminator 50 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/WritePE.fsi: -------------------------------------------------------------------------------- 1 | [] 2 | module FSharpIL.Writing.WritePE 3 | 4 | open System.Collections.Immutable 5 | open System.IO 6 | 7 | open FSharpIL 8 | open FSharpIL.PortableExecutable 9 | 10 | val internal write<'Writer when 'Writer :> IByteWriter> : PEFile -> 'Writer -> 'Writer 11 | 12 | /// Writes the Portable Executable file to an immutable byte array. 13 | val block : PEFile -> ImmutableArray 14 | 15 | val chunkedMemory : PEFile -> ChunkedMemory 16 | 17 | /// 18 | /// Creates a read-only used to read over the Portable Executable file. 19 | /// 20 | val stream : PEFile -> ChunkedMemoryStream 21 | 22 | val toArray : PEFile -> byte[] -> unit 23 | 24 | /// 25 | /// Writes the Portable Executable to the specified file. 26 | /// 27 | /// The is . 28 | val toFile : file: FileInfo -> PEFile -> unit 29 | 30 | /// 31 | /// Writes the Portable Executable to a file specified by the path. 32 | /// 33 | /// The is . 34 | val toPath : path: string -> PEFile -> unit 35 | 36 | /// 37 | /// Writes the Portable Executable file to the specified and leaves it open. 38 | /// 39 | /// Thrown when the does not support writing. 40 | /// 41 | /// Thrown when the is . 42 | /// 43 | /// 44 | /// Thrown when the was already disposed. 45 | /// 46 | val toStream : stream: #Stream -> PEFile -> unit 47 | 48 | //val toChunkedBuilder : byref -> PEFile -> unit 49 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/ParsedHeaders.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open System.Collections.Immutable 4 | open System.Runtime.CompilerServices 5 | 6 | open FSharpIL.Metadata 7 | open FSharpIL.PortableExecutable 8 | 9 | /// 10 | /// Describes the location of the CLI metadata root, resources, the strong name signature, and other miscellaneous information 11 | /// (II.25.3.3). 12 | /// 13 | type ParsedCliHeader = 14 | { /// The size of the CLI header, in bytes 15 | Cb: uint32 16 | MajorRuntimeVersion: uint16 17 | MinorRuntimeVersion: uint16 18 | Metadata: RvaAndSize 19 | Flags: CorFlags 20 | EntryPointToken: EntryPointToken 21 | Resources: RvaAndSize 22 | StrongNameSignature: RvaAndSize 23 | CodeManagerTable: RvaAndSize 24 | VTableFixups: RvaAndSize 25 | ExportAddressTableJumps: RvaAndSize 26 | ManagedNativeHeader: RvaAndSize } 27 | 28 | type ParsedCliMetadataRoot = CliMetadataRoot 29 | 30 | // TODO: Move these two types to Metadata namespace instead, and just have writing code use an immutable array of parsed stream headers? 31 | /// Offset from the start of the CLI metadata root, used to specify where a metadata stream begins (II.24.2.2). 32 | [] 33 | type MetadataRootOffset = struct 34 | val private offset: uint32 35 | new (offset) = { offset = offset } 36 | static member op_Implicit(offset: MetadataRootOffset) = offset.offset 37 | override this.ToString() = sprintf "0x%08X" this.offset 38 | end 39 | 40 | /// Describes the location, size, and name of a metadata stream (II.24.2.2). 41 | [] 42 | type ParsedStreamHeader = 43 | { Offset: MetadataRootOffset 44 | /// The size of this metadata stream, rounded up to a multiple of four. 45 | Size: uint32 46 | /// The name of the stream in ASCII encoding, including padding null bytes. 47 | StreamName: ImmutableArray } // TODO: Create new type for stream name. 48 | 49 | member this.PrintedName = System.Text.Encoding.ASCII.GetString(this.StreamName.AsSpan()).TrimEnd '\000' 50 | -------------------------------------------------------------------------------- /src/FSharpIL/PortableExecutable/SectionHeader.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.PortableExecutable 2 | 3 | open FSharpIL.Utilities.Compare 4 | 5 | /// Flags describing the characteristics of a PE file section (II.25.3). 6 | [] 7 | type SectionCharacteristics = 8 | | TypeNoPad = 0x8u 9 | /// The section contains executable code. 10 | | CntCode = 0x20u 11 | /// The section contains initialized data. 12 | | CntInitializedData = 0x40u 13 | /// The section contains uninitialized data. 14 | | CntUninitializedData = 0x80u 15 | | LnkOther = 0x100u 16 | | LnkInfo = 0x200u 17 | | LnkRemove = 0x800u 18 | | LnkComDat = 0x1000u 19 | | GpRel = 0x8000u 20 | | LnkNRelocOvfl = 0x100_0000u 21 | | MemDiscardable = 0x200_0000u 22 | | MemNotCached = 0x400_0000u 23 | | MemNotPaged = 0x800_0000u 24 | | MemShared = 0x1000_0000u 25 | /// The section can be executed as code. 26 | | MemExecute = 0x2000_0000u 27 | /// The section can be read. 28 | | MemRead = 0x4000_0000u 29 | /// The section can be written to. 30 | | MemWrite = 0x8000_0000u 31 | 32 | /// Describes the location, size, and characteristics of a section (II.25.3). 33 | type SectionHeader = 34 | { SectionName: SectionName 35 | VirtualSize: uint32 36 | VirtualAddress: Rva 37 | RawDataSize: uint32 38 | RawDataPointer: FileOffset 39 | /// Reserved value that should be set to zero. 40 | PointerToRelocations: uint32 41 | PointerToLineNumbers: uint32 42 | NumberOfRelocations: uint16 43 | NumberOfLineNumbers: uint16 44 | Characteristics: SectionCharacteristics } 45 | 46 | [] 47 | module SectionCharacteristics = 48 | let text = SectionCharacteristics.CntCode ||| SectionCharacteristics.MemExecute ||| SectionCharacteristics.MemRead 49 | 50 | [] 51 | module SectionHeader = 52 | /// Determines whether the specified Relative Virtual Address is contain within the specfied section. 53 | let contains (rva: Rva) header = rva .>= header.VirtualAddress && rva <. (header.VirtualAddress + header.VirtualSize) 54 | -------------------------------------------------------------------------------- /src/ilinfo/Print.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module ILInfo.Print 3 | 4 | open System 5 | open System.Collections.Generic 6 | open System.IO 7 | open System.Reflection 8 | 9 | open Microsoft.FSharp.Core.Printf 10 | 11 | open FSharpIL.Metadata 12 | open FSharpIL.PortableExecutable 13 | 14 | [] 15 | type private Enumeration<'Enum when 'Enum : struct and 'Enum :> Enum and 'Enum : equality> () = 16 | static member val Cache = 17 | lazy 18 | let fields = typeof<'Enum>.GetFields(BindingFlags.Public ||| BindingFlags.Static) 19 | let cache = Dictionary<'Enum, string>() 20 | for field in fields do 21 | let value = field.GetRawConstantValue() 22 | // If value is multiple by two, then add it 23 | // TODO: Check if value is multiple by two 24 | // TODO: Check if it is a signed or unsigned integer. 25 | match (value :?> IConvertible).ToUInt64 null with 26 | | 0UL -> () 27 | | _ -> cache.[value :?> 'Enum] <- field.Name 28 | cache 29 | 30 | let inline integer wr (value: 'Integer) = fprintf wr "0x%0*X" (2 * sizeof<'Integer>) value 31 | 32 | // TODO: List enum flags that are set vertically 33 | let inline enumeration wr (value: 'Flag when 'Flag : enum<'Integer>) = 34 | fprintf wr "0x%0*X (%O)" sizeof<'Flag> (LanguagePrimitives.EnumToValue<_, 'Integer> value) value 35 | 36 | let bitfield (wr: #TextWriter) (value: 'Enum when 'Enum :> Enum) = 37 | fprintf wr "0x%s " (value.ToString "X") 38 | 39 | Seq.choose 40 | (fun (KeyValue(flag, name)) -> 41 | if value.HasFlag flag 42 | then Some name 43 | else None) 44 | Enumeration<'Enum>.Cache.Value 45 | |> String.concat ", " 46 | |> fprintf wr "[ %s ]" 47 | 48 | let inline uint32 wr int = integer wr (uint32 int) 49 | 50 | let rvaAndSize wr { Rva = rva; Size = size } = fprintf wr "(RVA = %O, Size = 0x%08X)" rva size 51 | 52 | let metadataToken wr (token: MetadataToken) = 53 | fprintf wr "0x%08X" token.Value 54 | if not token.IsNull then 55 | wr.Write " [ " 56 | wr.Write token 57 | wr.Write " ]" 58 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Cil/MethodBody.fs: -------------------------------------------------------------------------------- 1 | // TODO: Turn this namespace into a module and rename it to FSharpIL.Metadata.InstructionSet or FSharpIL.InstructionSet 2 | namespace FSharpIL.Metadata.Cil 3 | 4 | open System 5 | open System.Runtime.CompilerServices 6 | 7 | [] 8 | type InitLocals = | SkipInitLocals | InitLocals 9 | 10 | /// Represents the maximum number of items "that can be pushed onto the CIL evaluation stack" for a method body (III.1.7.4). 11 | [] 12 | type MaxStack = 13 | val private value: uint16 14 | new (value) = { value = value } 15 | override this.ToString() = sprintf ".maxstack %i" this.value 16 | static member Zero = MaxStack 0us 17 | static member MaxValue = MaxStack UInt16.MaxValue 18 | static member op_Implicit(value: MaxStack) = int64 value.value 19 | static member op_Implicit(value: MaxStack) = uint64 value.value 20 | static member op_Implicit(value: MaxStack) = int32 value.value 21 | static member op_Implicit(value: MaxStack) = uint32 value.value 22 | static member op_Implicit(value: MaxStack) = value.value 23 | /// Increments the MaxStack value by the given . 24 | /// Thrown if an overflow occurs. 25 | static member (+) (value: MaxStack, amount: uint16) = MaxStack(Checked.(+) value.value amount) 26 | 27 | /// Specifies the type of the method header and additional information (II.25.4.4). 28 | [] 29 | type ILMethodFlags = 30 | | None = 0us 31 | | TinyFormat = 0x2us 32 | | FatFormat = 0x3us 33 | | MoreSects = 0x8us 34 | | InitLocals = 0x10us 35 | 36 | /// Describes a method body data section (II.25.4.5). 37 | [] 38 | type ILSectionFlags = 39 | | None = 0uy 40 | | EHTable = 0x1uy 41 | | OptILTable = 0x2uy 42 | /// Indicates that the size of the method body data section takes up 3 bytes instead of 1 byte. 43 | | FatFormat = 0x40uy 44 | | MoreSects = 0x80uy 45 | | FatEHTable = 0x65uy 46 | 47 | [] 48 | type ExceptionClauseFlags = 49 | | Exception = 0u 50 | | Filter = 1u 51 | | Finally = 2u 52 | | Fault = 4u 53 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/MetadataVersion.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata 2 | 3 | open System.Collections.Immutable 4 | open System.Runtime.CompilerServices 5 | open System.Text 6 | 7 | open FSharpIL.Utilities 8 | 9 | open FSharpIL 10 | 11 | /// Specifies what version of the CLI that the file is intended to execute on (II.24.2.1). 12 | [] 13 | type MetadataVersion = // TODO: Rename to RuntimeVersion 14 | internal { RoundedLength: uint8; MetadataVersion: ImmutableArray } 15 | override this.ToString() = Encoding.UTF8.GetString(this.MetadataVersion.AsSpan()).TrimEnd '\000' 16 | /// The length of the version string including the null terminator, rounded up to a multiple of 4. 17 | member this.Length = uint32 this.RoundedLength 18 | 19 | [] 20 | module MetadataVersion = 21 | let tryOfStr (str: string) = 22 | if str.Length > 255 || str.Contains '\000' 23 | then ValueNone 24 | else 25 | ValueSome 26 | { RoundedLength = Round.upTo 4uy (uint8 str.Length) 27 | MetadataVersion = Convert.unsafeTo(Encoding.UTF8.GetBytes str) } 28 | 29 | let ofStr (str: string) = 30 | match tryOfStr str with 31 | | ValueSome ver -> ver 32 | | ValueNone -> 33 | invalidArg "str" "The version string cannot contain a null character or have a length greater than 255 bytes" 34 | 35 | let asSpan { MetadataVersion = bytes } = bytes.AsSpan() 36 | let toBlock { MetadataVersion = bytes } = bytes 37 | let toArray { MetadataVersion = bytes } = bytes.AsSpan().ToArray() 38 | 39 | /// Latest runtime version for files intended to be executed on Microsoft-specific implementations of the CLI such as .NET 40 | /// Framework, .NET Core, or .NET. 41 | let defaultLatest = ofStr "v4.0.30319" 42 | /// Latest runtime version for files "intended to be executed on any conforming implementation of the CLI" (II.24.2.1). 43 | let standardLatest = ofStr "Standard CLI 2005" 44 | 45 | let write (builder: byref) { RoundedLength = Convert.I4 len; MetadataVersion = version } = 46 | let padding = Span.stackalloc(len - version.Length) 47 | builder.Write version 48 | builder.Write padding 49 | -------------------------------------------------------------------------------- /docs/FSharpIL.Documentation/Article.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Documentation 2 | 3 | open System.Collections.Generic 4 | open System.IO 5 | 6 | open FSharp.Formatting.CodeFormat 7 | open FSharp.Formatting.Literate 8 | open FSharp.Formatting.Literate.Evaluation 9 | open FSharp.Formatting.Markdown 10 | 11 | type Article = 12 | { mutable Title: string 13 | Sections: List } 14 | 15 | [] 16 | module internal Article = 17 | let inline private (|Header|_|) size = 18 | function 19 | | Heading(size', [ Literal(text, _) ], _) when size' = size -> Some text 20 | | _ -> None 21 | 22 | let write (article: FileInfo) evaluator (output: Stream) = 23 | let doc = Literate.ParseAndCheckScriptFile(article.FullName, fsiEvaluator = evaluator) 24 | let info = 25 | { Title = null 26 | Sections = List<_>() } 27 | 28 | for paragraph in doc.Paragraphs do // TODO: Use customizeDocument to set the values. 29 | match paragraph with 30 | | Header 1 title when info.Title = null -> info.Title <- title 31 | | Header 2 section -> info.Sections.Add section 32 | | _ -> () 33 | 34 | html output [ "lang", "us" ] [ 35 | head [ 36 | meta [ "charset", "utf-8" ] 37 | meta [ "name", "viewport"; "content", "width=device-width" ] 38 | title info.Title 39 | link "stylesheet" "./style/global.css" 40 | ] 41 | body [] [ 42 | tag "nav" [] [ 43 | // TODO: Figure out how to get list of all other pages. 44 | ] 45 | tag "main" [] [ 46 | fun writer -> Literate.WriteHtml(doc, writer, lineNumbers = true, generateAnchors = true) 47 | ] 48 | tag "article" [] [ 49 | h2 [] "Table of Contents" 50 | ul [] [ 51 | for section in info.Sections do 52 | let href = section.Replace(' ', '-') |> sprintf "#%s" 53 | let link = a [] href [ !^section ] 54 | li [] [ link ] 55 | ] 56 | ] 57 | ] 58 | ] 59 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Signatures/CustomAttrib.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata.Signatures 2 | 3 | open System.Runtime.CompilerServices 4 | open System.Collections.Immutable 5 | 6 | open FSharpIL.Metadata.Blobs 7 | 8 | /// Represents an Elem item, which is an argument in a custom attribute (II.23.3). 9 | type Elem = 10 | | ValBool of bool 11 | | ValChar of char 12 | | ValR4 of System.Single 13 | | ValR8 of System.Double 14 | | ValI1 of int8 15 | | ValU1 of uint8 16 | | ValI2 of int16 17 | | ValU2 of uint16 18 | | ValI4 of int32 19 | | ValU4 of uint32 20 | | ValI8 of int64 21 | | ValU8 of uint64 22 | /// Represents a string used as an argument in a custom attribute. 23 | /// Empty strings and strings are allowed values. 24 | | SerString of string 25 | // | SerStringType // of SomehowGetTheCanonicalNameOfType. 26 | // | BoxedObject of // underlying value. 27 | 28 | /// 29 | /// Represents a FixedArg item, which stores the arguments for a custom attribute's constructor method (II.23.3). 30 | /// 31 | [] 32 | type FixedArg = 33 | | Elem of Elem 34 | | SZArray of ImmutableArray voption 35 | 36 | [] 37 | type NamedArg = 38 | { IsProperty: bool 39 | Name: string 40 | Type: ElemType // FieldOrPropType 41 | Value: FixedArg } 42 | 43 | [] 44 | module NamedArg = 45 | let inline (|Field|Prop|) arg = 46 | let value() = struct(arg.Name, arg.Value) 47 | if arg.IsProperty then Prop(value()) else Field(value()) 48 | let inline Field (name, namedArgType, value) = { IsProperty = false; Name = name; Type = namedArgType; Value = value } 49 | let inline Prop (name, namedArgType, value) = { IsProperty = true; Name = name; Type = namedArgType; Value = value } 50 | 51 | /// 52 | /// Represents a CustomAttrib item, which stores the arguments provided to a custom attribute's constructor, as well as 53 | /// any values assigned to its fields or properties. (II.23.3). 54 | /// 55 | [] 56 | type CustomAttrib = 57 | { FixedArgs: ImmutableArray 58 | NamedArgs: ImmutableArray } 59 | -------------------------------------------------------------------------------- /src/FSharpIL/Utilities/Bytes.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module internal FSharpIL.Utilities.Bytes 3 | 4 | open System 5 | 6 | /// Writes the bytes of the 16-bit integer in little endian order. 7 | let ofU2 (bytes: Span) (value: uint16) = 8 | bytes.[0] <- value &&& 0xFFus |> byte // LSB 9 | bytes.[1] <- (value >>> 8) &&& 0xFFus |> byte // MSB 10 | bytes 11 | 12 | /// Writes the bytes of the 32-bit integer in little endian order. 13 | let ofU4 (bytes: Span) (value: uint32) = 14 | bytes.[0] <- value &&& 0xFFu |> byte // LSB 15 | bytes.[1] <- (value >>> 8) &&& 0xFFu |> byte 16 | bytes.[2] <- (value >>> 16) &&& 0xFFu |> byte 17 | bytes.[3] <- (value >>> 24) &&& 0xFFu |> byte // MSB 18 | bytes 19 | 20 | /// Writes the bytes of the 64-bit integer in little endian order. 21 | let ofU8 (bytes: Span) (value: uint64) = 22 | bytes.[0] <- value &&& 0xFFUL |> byte // LSB 23 | bytes.[1] <- (value >>> 8) &&& 0xFFUL |> byte 24 | bytes.[2] <- (value >>> 16) &&& 0xFFUL |> byte 25 | bytes.[3] <- (value >>> 24) &&& 0xFFUL |> byte 26 | bytes.[4] <- (value >>> 32) &&& 0xFFUL |> byte 27 | bytes.[5] <- (value >>> 40) &&& 0xFFUL |> byte 28 | bytes.[6] <- (value >>> 48) &&& 0xFFUL |> byte 29 | bytes.[7] <- (value >>> 56) &&& 0xFFUL |> byte // MSB 30 | bytes 31 | 32 | /// Reads a 16-bit integer in little endian order. 33 | let toU2 offset (bytes: Span) = 34 | (uint16 bytes.[offset + 1] <<< 8) // MSB 35 | ||| uint16 bytes.[offset] // LSB 36 | 37 | /// Reads a 32-bit integer in little endian order. 38 | let toU4 offset (bytes: Span) = 39 | (uint32 bytes.[offset + 3] <<< 24) // MSB 40 | ||| (uint32 bytes.[offset + 2] <<< 16) 41 | ||| (uint32 bytes.[offset + 1] <<< 8) 42 | ||| uint32 bytes.[offset] // LSB 43 | 44 | /// Reads a 64-bit integer in little endian order. 45 | let toU8 offset (bytes: Span) = 46 | (uint64 bytes.[offset + 7] <<< 56) // MSB 47 | ||| (uint64 bytes.[offset + 6] <<< 48) 48 | ||| (uint64 bytes.[offset + 5] <<< 40) 49 | ||| (uint64 bytes.[offset + 4] <<< 32) 50 | ||| (uint64 bytes.[offset + 3] <<< 24) 51 | ||| (uint64 bytes.[offset + 2] <<< 16) 52 | ||| (uint64 bytes.[offset + 1] <<< 8) 53 | ||| uint64 bytes.[offset] // LSB 54 | 55 | // TODO: Make big endian versions if necessary. 56 | //module BigEndian 57 | -------------------------------------------------------------------------------- /src/FSharpIL/PortableExecutable/Magic.fs: -------------------------------------------------------------------------------- 1 | /// Contains various magic numbers used throughout a PE file. 2 | [] 3 | module FSharpIL.PortableExecutable.Magic 4 | 5 | open System.Collections.Immutable 6 | 7 | open FSharpIL.Utilities 8 | 9 | /// The MS-DOS header, which contains a pointer to the PE signature (II.25.2.1). 10 | let msDosStub = 11 | [| 12 | 0x4duy; 0x5auy; 0x90uy; 0x00uy; 0x03uy; 0x00uy; 0x00uy; 0x00uy; 13 | 0x04uy; 0x00uy; 0x00uy; 0x00uy; 0xFFuy; 0xFFuy; 0x00uy; 0x00uy; 14 | 0xb8uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 15 | 0x40uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 16 | 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 17 | 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 18 | 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 19 | 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x80uy; 0x00uy; 0x00uy; 0x00uy; // lfanew 20 | 0x0euy; 0x1fuy; 0xbauy; 0x0euy; 0x00uy; 0xb4uy; 0x09uy; 0xcduy; 21 | 0x21uy; 0xb8uy; 0x01uy; 0x4cuy; 0xcduy; 0x21uy; 0x54uy; 0x68uy; 22 | 0x69uy; 0x73uy; 0x20uy; 0x70uy; 0x72uy; 0x6fuy; 0x67uy; 0x72uy; 23 | 0x61uy; 0x6duy; 0x20uy; 0x63uy; 0x61uy; 0x6euy; 0x6euy; 0x6fuy; 24 | 0x74uy; 0x20uy; 0x62uy; 0x65uy; 0x20uy; 0x72uy; 0x75uy; 0x6euy; 25 | 0x20uy; 0x69uy; 0x6euy; 0x20uy; 0x44uy; 0x4fuy; 0x53uy; 0x20uy; 26 | 0x6duy; 0x6fuy; 0x64uy; 0x65uy; 0x2euy; 0x0duy; 0x0duy; 0x0auy; 27 | 0x24uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 0x00uy; 28 | |] 29 | |> Convert.unsafeTo<_, ImmutableArray> 30 | 31 | let dosHeaderSignature = Convert.unsafeTo<_, ImmutableArray> "MZ"B 32 | 33 | /// The PE signature, which immediately precedes the COFF header (II.25.2.1). 34 | let portableExecutableSignature = Convert.unsafeTo<_, ImmutableArray> "PE\000\000"B 35 | 36 | let [] CoffHeaderSize = 20u 37 | 38 | /// The size of the optional header in a PE32 file when all fields are present, in bytes (II.25.2.2). 39 | let [] OptionalHeaderSize = 224us 40 | 41 | /// The size of the optional header in a PE32+ file when all fields are present, in bytes. 42 | let [] OptionalHeaderPlusSize = 240us 43 | 44 | /// The size of a single section header, in bytes. 45 | let [] SectionHeaderSize = 40u 46 | -------------------------------------------------------------------------------- /src/FSharpIL/ChunkedMemoryStream.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL 2 | 3 | open System 4 | open System.IO 5 | 6 | open FSharpIL.Utilities 7 | 8 | /// A read-only sequence of bytes that supports seeking used to read a non-contiguous region of memory. 9 | [] 10 | type ChunkedMemoryStream (data: ChunkedMemory) = 11 | inherit Stream() 12 | let mutable pos = 0u 13 | 14 | override _.CanRead = true 15 | override _.CanSeek = true 16 | override _.CanWrite = false 17 | override _.Length = int64 data.Length 18 | 19 | /// 20 | /// Thrown when the new position is negative or exceeds the length of the stream. 21 | /// 22 | override this.Position 23 | with get() = int64 pos 24 | and set position = 25 | if position < 0L || position > this.Length then argOutOfRange "position" position "The position was out of bounds" 26 | pos <- uint32 position 27 | 28 | override _.Flush() = () 29 | 30 | override _.Read(buffer, offset, count) = 31 | let count' = int32(min data.Length (uint32 count)) 32 | if count' > 0 then 33 | data.UnsafeCopyTo(pos, Span(buffer, offset, count')) 34 | pos <- pos + uint32 count 35 | count' 36 | 37 | /// 38 | /// Thrown when the is invalid. 39 | /// 40 | /// 41 | /// Thrown when seeking is attempted before the start or past the end of the stream. 42 | /// 43 | override this.Seek(offset, origin) = 44 | let pos' = 45 | match origin with 46 | | SeekOrigin.Begin -> offset 47 | | SeekOrigin.Current -> int64 pos + offset 48 | | SeekOrigin.End -> this.Length + offset 49 | | _ -> argOutOfRange "origin" origin "The origin is invalid" 50 | if pos' < 0L then invalidOp "Cannot seek before the start of the stream" 51 | if pos' >= this.Length then invalidOp "Cannot seek past the end of the stream" 52 | pos <- uint32 pos' 53 | this.Position 54 | 55 | override _.SetLength _ = notSupported "Cannot set the length, the stream does not support writing" 56 | override _.Write(_, _, _) = notSupported "The stream does not support writing" 57 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/CliMetadataBuilder.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Writing 2 | 3 | open FSharpIL 4 | open FSharpIL.Metadata 5 | 6 | // TODO: Come up with better name that shows how this type is only used when writing. 7 | [] 8 | [] 9 | type CliHeader = 10 | { MajorRuntimeVersion: uint16 11 | MinorRuntimeVersion: uint16 12 | Requires32Bit: bool } 13 | 14 | [] 15 | module CliHeader = 16 | let latestDefault = 17 | { MajorRuntimeVersion = 2us 18 | MinorRuntimeVersion = 5us 19 | Requires32Bit = false } 20 | 21 | // TODO: Make this class internal, or remove it entirely. 22 | 23 | /// Builds the CLI metadata stored in the .text section of a PE file (II.24). 24 | [] 25 | type CliMetadataBuilder internal 26 | ( 27 | header: CliHeader, 28 | root: CliMetadataRoot, 29 | methodBodies: Lazy, 30 | moduleRowBuilder, 31 | strings: StringsStreamBuilder, 32 | us: UserStringStreamBuilder, 33 | guid: GuidStreamBuilder, 34 | blob: BlobStreamBuilder //, 35 | //resources, 36 | //strongNameSignature, 37 | //vTableFixups 38 | ) 39 | = 40 | let mutable nextEmbeddedData = 0u 41 | member val internal EmbeddedData = System.Collections.Generic.List>() 42 | member val Tables = MetadataTablesBuilder(moduleRowBuilder, strings, guid, blob) 43 | member _.Header = header 44 | member _.Root = root 45 | member _.MethodBodies = methodBodies.Value 46 | member _.Strings = strings 47 | member _.UserString = us 48 | member _.Guid = guid 49 | member _.Blob = blob 50 | 51 | member val EntryPointToken: EntryPointToken = EntryPointToken.Null with get, set 52 | 53 | member this.AddEmbeddedData data = 54 | let location = FSharpIL.Metadata.Tables.FieldValueLocation nextEmbeddedData 55 | this.EmbeddedData.Add data 56 | nextEmbeddedData <- Checked.(+) nextEmbeddedData (Checked.uint32 data.Length) 57 | location 58 | 59 | member _.HeaderFlags = 60 | let mutable flags = CorFlags.ILOnly 61 | if header.Requires32Bit then flags <- flags ||| CorFlags.Requires32Bit 62 | //if (noImpl "is signed/has strong name signature") then flags <- flags ||| CorFlags.StrongNameSigned 63 | flags 64 | -------------------------------------------------------------------------------- /test/FSharpIL.Properties/Properties.fs: -------------------------------------------------------------------------------- 1 | module FSharpIL.Properties 2 | 3 | open Expecto 4 | 5 | open Swensen.Unquote 6 | 7 | open System 8 | open System.Collections.Immutable 9 | open System.Reflection.PortableExecutable 10 | 11 | open Mono.Cecil 12 | 13 | open FSharpIL.Generate 14 | 15 | open FSharpIL.Metadata 16 | open FSharpIL.Metadata.CliMetadata 17 | open FSharpIL.PortableExecutable 18 | 19 | let testCecil name body = 20 | testProperty name <| fun (ValidAssembly pe) -> 21 | use mdle = ModuleDefinition.ReadModule(WritePE.stream pe) 22 | body pe mdle 23 | 24 | let testPE name body = 25 | testProperty name <| fun (ValidAssembly pe) -> 26 | use reader = new PEReader(WritePE.stream pe) 27 | body pe reader 28 | 29 | [] 30 | let tests = 31 | testList "metadata" [ 32 | testCecil "module name matches parsed name" <| fun pe mdle -> 33 | let expected = string pe.CliHeader.Value.Module.Name 34 | expected =! mdle.Name 35 | 36 | testCecil "names of defined types match parsed names" <| fun pe mdle -> 37 | let expected = 38 | pe.CliHeader.Value.TypeDef.Rows 39 | |> Seq.map (fun t -> string t.TypeName, t.TypeNamespace) 40 | |> List.ofSeq 41 | let actual = 42 | mdle.Types 43 | |> Seq.map (fun t -> t.Name, t.Namespace) 44 | |> List.ofSeq 45 | expected =! actual 46 | ] 47 | 48 | //[] 49 | //let tests = 50 | // testList "write PE" [ 51 | // testPE "section names match parsed name" <| fun pe reader -> 52 | // let expected = 53 | // pe.SectionTable 54 | // |> Seq.map (fun section -> string section.Header.SectionName) 55 | // |> List.ofSeq 56 | // let actual = 57 | // reader.PEHeaders.SectionHeaders 58 | // |> Seq.map (fun header -> header.Name) 59 | // |> List.ofSeq 60 | // expected =! actual 61 | 62 | // testPE "file alignment matches" <| fun pe reader -> 63 | // reader.PEHeaders.PEHeader.FileAlignment =! int32 pe.NTSpecificFields.FileAlignment 64 | 65 | // testPE "all PE files are PE32" <| fun _ reader -> 66 | // reader.PEHeaders.PEHeader.Magic =! PEMagic.PE32 67 | 68 | // testPE "all PE files have 16 data directories" <| fun _ reader -> 69 | // reader.PEHeaders.PEHeader.NumberOfRvaAndSizes =! 0x10 70 | // ] 71 | -------------------------------------------------------------------------------- /src/FSharpIL/Utilities/Collections/HybridHashSet.fs: -------------------------------------------------------------------------------- 1 | namespace rec FSharpIL.Utilities.Collections 2 | 3 | open System.Collections.Generic 4 | open System.Runtime.CompilerServices 5 | 6 | type private HybridHashSetHelpers<'T> = struct 7 | interface IHybridCollectionMethods<'T, HashSet<'T>, HashSet<'T>.Enumerator> with 8 | [] 9 | member _.InitInner() = HashSet<'T>() 10 | [] 11 | member _.InnerEnumerator inner = inner.GetEnumerator() 12 | member _.EqualityComparer 13 | with [] get() = 14 | EqualityComparer<'T>.Default :> IEqualityComparer<'T> // Don't forget to change this if a different comparer is used. 15 | end 16 | 17 | type internal HybridHashSet<'T when 'T : not struct and 'T : equality> = struct 18 | val mutable private inner: HybridCollection<'T, HashSet<'T>, HashSet<'T>.Enumerator, HybridHashSetHelpers<'T>> 19 | 20 | internal new (capacity) = { inner = HybridCollection.create capacity } 21 | 22 | member this.Count = this.inner.Count 23 | member this.IsEmpty = this.inner.IsEmpty 24 | 25 | member this.Add item = 26 | match this.Count with 27 | | 0 -> 28 | this.inner.item0 <- item 29 | this.inner.Count <- 1 30 | true 31 | | 1 when this.inner.ItemsEqual(this.inner.item0, item) -> false 32 | | 1 -> 33 | this.inner.item1 <- item 34 | this.inner.Count <- 2 35 | true 36 | | 2 when 37 | this.inner.ItemsEqual(this.inner.item0, item) 38 | || this.inner.ItemsEqual(this.inner.item1, item) 39 | -> 40 | false 41 | | 2 -> 42 | this.inner.item2 <- item 43 | this.inner.Count <- 3 44 | true 45 | | _ when 46 | this.inner.ItemsEqual(this.inner.item0, item) 47 | || this.inner.ItemsEqual(this.inner.item1, item) 48 | || this.inner.ItemsEqual(this.inner.item2, item) 49 | -> 50 | false 51 | | _ -> 52 | this.inner.InitInner() 53 | let result = this.inner.inner.Add item 54 | if result then this.inner.Count <- this.inner.Count + 1 55 | result 56 | 57 | member this.Contains item = this.inner.Contains item 58 | 59 | member this.GetEnumerator() = this.inner.GetEnumerator() 60 | end 61 | -------------------------------------------------------------------------------- /src/ilinfo/VisibilityFilter.fs: -------------------------------------------------------------------------------- 1 | namespace ILInfo 2 | 3 | open System.Reflection 4 | 5 | type VisibilityFilter = 6 | | Public = 1uy 7 | | Private = 2uy 8 | | Family = 4uy 9 | | Assembly = 8uy 10 | | FamAndAssem = 0x10uy 11 | | FamOrAssem = 0x20uy 12 | | CompilerControlled = 0x40uy 13 | | All = 0x7Fuy 14 | 15 | [] 16 | module VisibilityFilter = 17 | let typeDef (vfilter: VisibilityFilter) flags = 18 | match flags &&& TypeAttributes.VisibilityMask with 19 | | TypeAttributes.NestedPublic 20 | | TypeAttributes.Public -> vfilter.HasFlag VisibilityFilter.Public 21 | | TypeAttributes.NestedFamily -> vfilter.HasFlag VisibilityFilter.Family 22 | | TypeAttributes.NestedFamANDAssem -> vfilter.HasFlag VisibilityFilter.FamAndAssem 23 | | TypeAttributes.NestedFamORAssem -> vfilter.HasFlag VisibilityFilter.FamOrAssem 24 | | TypeAttributes.NestedPrivate -> vfilter.HasFlag VisibilityFilter.Private 25 | | TypeAttributes.NotPublic 26 | | TypeAttributes.NestedAssembly 27 | | _ -> vfilter.HasFlag VisibilityFilter.Assembly 28 | 29 | let field (vfilter: VisibilityFilter) flags = 30 | match flags &&& FieldAttributes.FieldAccessMask with 31 | | FieldAttributes.Public -> vfilter.HasFlag VisibilityFilter.Public 32 | | FieldAttributes.Family -> vfilter.HasFlag VisibilityFilter.Family 33 | | FieldAttributes.Assembly -> vfilter.HasFlag VisibilityFilter.Assembly 34 | | FieldAttributes.FamANDAssem -> vfilter.HasFlag VisibilityFilter.FamAndAssem 35 | | FieldAttributes.FamORAssem -> vfilter.HasFlag VisibilityFilter.FamOrAssem 36 | | FieldAttributes.Private -> vfilter.HasFlag VisibilityFilter.Private 37 | | FieldAttributes.PrivateScope 38 | | _ -> vfilter.HasFlag VisibilityFilter.CompilerControlled 39 | 40 | let methodDef (vfilter: VisibilityFilter) flags = 41 | match flags &&& MethodAttributes.MemberAccessMask with 42 | | MethodAttributes.Public -> vfilter.HasFlag VisibilityFilter.Public 43 | | MethodAttributes.Family -> vfilter.HasFlag VisibilityFilter.Family 44 | | MethodAttributes.Assembly -> vfilter.HasFlag VisibilityFilter.Assembly 45 | | MethodAttributes.FamANDAssem -> vfilter.HasFlag VisibilityFilter.FamAndAssem 46 | | MethodAttributes.FamORAssem -> vfilter.HasFlag VisibilityFilter.FamOrAssem 47 | | MethodAttributes.Private -> vfilter.HasFlag VisibilityFilter.Private 48 | | MethodAttributes.PrivateScope 49 | | _ -> vfilter.HasFlag VisibilityFilter.CompilerControlled 50 | -------------------------------------------------------------------------------- /test/FSharpIL.Properties/Generate.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL 2 | 3 | open Expecto 4 | 5 | open FsCheck 6 | 7 | open FSharpIL.Metadata 8 | open FSharpIL.Metadata.Unchecked 9 | open FSharpIL.PortableExecutable 10 | 11 | type Operation = 12 | | AddTypeDef 13 | | ReferenceAssembly of System.Version * PublicKeyOrToken * AssemblyCulture 14 | 15 | type ValidAssembly = 16 | { File: PEFile 17 | Operations: Operation[] } 18 | 19 | [] 20 | module private Generate = 21 | // TODO: Test generation of PE file with different values for alignment. 22 | let private alignment = 23 | let inline pow2 num = pown 2 num 24 | gen { 25 | let! falignment = Gen.choose(9, 14) 26 | let! salignment = Gen.choose(falignment + 1, 15) 27 | return Alignment.create (pow2 salignment) (pow2 falignment) |> Option.get 28 | } 29 | 30 | let private identifier = 31 | Arb.generate |> Gen.map (fun (NonEmptyString identifier) -> Identifier.ofStr identifier) 32 | 33 | let private modulet: Gen = 34 | (fun name mvid -> { Name = name; Mvid = mvid }) identifier <*> Arb.generate 35 | 36 | let metadata: Gen = 37 | let generate mdle (operations: Operation[]) = 38 | let builder = CliMetadataBuilder mdle 39 | 40 | // TODO: Add Assembly row 41 | 42 | for op in operations do 43 | match op with 44 | | ReferenceAssembly(ver, pkey, culture) -> 45 | () 46 | | _ -> () 47 | 48 | operations, CliMetadata builder 49 | generate modulet <*> Arb.generate 50 | 51 | let assembly: Gen = 52 | gen { 53 | let! (operations, md) = metadata 54 | let! characteristics = Arb.generate 55 | return 56 | { File = PEFile.ofMetadata characteristics md 57 | Operations = operations } 58 | } 59 | 60 | // TODO: Use model based testing with Command<_, _> and Mono.Cecil 61 | 62 | type Generators = 63 | static member ValidMetadata() = Arb.fromGen metadata 64 | static member ValidAssembly() = Arb.fromGen assembly 65 | 66 | [] 67 | module Helpers = 68 | let private config = 69 | { FsCheckConfig.defaultConfig with // TODO: Instead of registering arbs here, do Arb.register in the entrypoint function. 70 | arbitrary = typeof :: FsCheckConfig.defaultConfig.arbitrary } 71 | 72 | let testProperty name body = testPropertyWithConfig config name body 73 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/StringsStreamBuilder.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Writing 2 | 3 | open System 4 | open System.Collections.Generic 5 | 6 | open FSharpIL.Metadata 7 | 8 | [] 9 | type private StringsStreamSerializer = 10 | interface StringHelpers.IStringSerializer> with 11 | member _.WriteBefore(_, _) = () 12 | member _.GetChars str = &str 13 | member _.WriteAfter(_, wr) = wr.Write 0uy 14 | 15 | /// Builds the #Strings metadata stream, containing null-terminated UTF-8 strings (II.24.2.3). 16 | [] 17 | type StringsStreamBuilder (capacity: int32) = 18 | static let empty = ReadOnlyMemory.Empty 19 | static let emptyi = { StringOffset = 0u } 20 | let mutable offset = { StringOffset = 1u } 21 | let strings = System.Collections.Immutable.ImmutableArray.CreateBuilder> capacity 22 | let lookup = Dictionary, StringOffset>(capacity, StringHelpers.comparer) 23 | do strings.Add empty // First entry is the empty string. 24 | do lookup.[empty] <- emptyi 25 | 26 | member _.IsEmpty = strings.Count = 1 27 | member _.EmptyString = emptyi 28 | 29 | /// The length of the #Strings metadata stream, in bytes. 30 | member _.StreamLength = offset.StringOffset 31 | 32 | member private _.AddUnsafe(str: inref>) = 33 | let offset' = offset 34 | offset <- { StringOffset = offset.StringOffset + 1u + uint32 str.Length } 35 | lookup.[str] <- offset' 36 | strings.Add str 37 | offset' 38 | 39 | member private this.Add str = 40 | match lookup.TryGetValue str with 41 | | true, existing -> existing 42 | | false, _ -> 43 | for i = 1 to str.Length - 1 do 44 | lookup.[str.Slice i] <- { StringOffset = offset.StringOffset + uint32 i } 45 | this.AddUnsafe &str 46 | 47 | member this.Add str = { IdentifierOffset.Offset = this.Add(Identifier.asMemory str) } 48 | member this.Add { FileName = name } = { FileNameOffset.Offset = this.Add name } 49 | 50 | member this.Add(str: Identifier voption) = 51 | match str with 52 | | ValueSome str' -> this.Add(str').Offset 53 | | ValueNone -> emptyi 54 | 55 | interface IStreamBuilder with 56 | member this.StreamLength = ValueSome this.StreamLength 57 | member _.StreamName = Magic.StreamNames.strings 58 | member _.Serialize(wr, _, _) = 59 | StringHelpers.serializeStringHeap System.Text.Encoding.UTF8 &wr strings 60 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/MemberVisibility.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli // TODO: Rename namespace to FSharpIL.CommonLanguageInfrastructure or FSharpIL.CommonTypeSystem or FSharpIL.Cts or FSharpIL.Architecture 2 | 3 | open System.Runtime.CompilerServices 4 | 5 | open FSharpIL.Metadata.Tables 6 | 7 | /// Specifies the visibility of a defined member. 8 | [] 9 | type MemberVisibility = 10 | | CompilerControlled 11 | /// The containing type can access this member. 12 | | Private 13 | | Public 14 | /// The containing type and its derived types can access this member. 15 | | Family 16 | /// Types in the same assembly as the containing type can access this member. 17 | | Assembly 18 | /// Types in the same assembly that are derived from the containing type can access this member. 19 | | FamilyAndAssembly 20 | /// Types in the same assembly as or types derived from the containing type can access this member. 21 | | FamilyOrAssembly 22 | 23 | [] 24 | module MemberVisibility = 25 | let inline ofMethod (visibility: MemberVisibility) = 26 | match visibility with 27 | | CompilerControlled -> MethodDefFlags.CompilerControlled 28 | | Private -> MethodDefFlags.Private 29 | | Public -> MethodDefFlags.Public 30 | | Family -> MethodDefFlags.Family 31 | | Assembly -> MethodDefFlags.Assembly 32 | | FamilyAndAssembly -> MethodDefFlags.FamAndAssem 33 | | FamilyOrAssembly -> MethodDefFlags.FamOrAssem 34 | 35 | let inline ofField (visibility: MemberVisibility) = 36 | match visibility with 37 | | CompilerControlled -> FieldFlags.CompilerControlled 38 | | Private -> FieldFlags.Private 39 | | Public -> FieldFlags.Public 40 | | Family -> FieldFlags.Family 41 | | Assembly -> FieldFlags.Assembly 42 | | FamilyAndAssembly -> FieldFlags.FamAndAssem 43 | | FamilyOrAssembly -> FieldFlags.FamOrAssem 44 | 45 | /// Specifies the visibility of a referenced member or type. 46 | [] 47 | [] 48 | type ExternalVisibility = 49 | | Public 50 | | Family 51 | 52 | [] 53 | module ExternalVisibility = 54 | let inline asMemberVisibility visibility = 55 | match visibility with 56 | | ExternalVisibility.Public -> Public 57 | | ExternalVisibility.Family -> Family 58 | 59 | let inline ofMethod visibility = 60 | match visibility with 61 | | ExternalVisibility.Public -> MethodDefFlags.Public 62 | | ExternalVisibility.Family -> MethodDefFlags.Family 63 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/ReadState.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open System.Runtime.CompilerServices 4 | 5 | type IReadState = interface end 6 | 7 | [] 8 | type MetadataReadState = 9 | /// Indicates that the CLI header is being read (II.25.3.3). 10 | | ReadCliHeader 11 | | FindMetadataRoot 12 | | ReadMetadataRoot 13 | | ReadStreamHeaders 14 | | ReadStringsStream 15 | | ReadGuidStream 16 | | ReadUserStringStream 17 | | ReadBlobStream 18 | | ReadTablesStream 19 | | ReadMetadataTables 20 | | ReadTableRows 21 | | MetadataReadFinished 22 | 23 | override this.ToString() = 24 | match this with 25 | | ReadCliHeader -> "reading CLI header" 26 | | FindMetadataRoot -> "locating metadata root" 27 | | ReadMetadataRoot -> "reading metadata root" 28 | | ReadStreamHeaders -> "reading metadata stream headers" 29 | | ReadStringsStream -> "reading strings stream" 30 | | ReadGuidStream -> "reading GUID stream" 31 | | ReadUserStringStream -> "reading user strings stream" 32 | | ReadBlobStream -> "reading metadata blob stream" 33 | | ReadTablesStream -> "reading tables stream" 34 | | ReadMetadataTables -> "reading metadata tables" 35 | | ReadTableRows -> "reading table rows" 36 | | MetadataReadFinished -> "the reading of metadata was finished" 37 | 38 | interface IReadState 39 | 40 | [] 41 | type FileReadState = 42 | | ReadDosMagic 43 | | MoveToLfanew 44 | /// The lfanew field pointing to the PE signature is being read (II.25.2.1). 45 | | ReadLfanew 46 | | MoveToPESignature 47 | | ReadPESignature 48 | /// The PE file header after the PE signature is being read (II.25.2.2). 49 | | ReadCoffHeader 50 | | ReadOptionalHeader 51 | | ReadDataDirectories 52 | | ReadSectionHeaders 53 | | ReadSectionData 54 | 55 | override this.ToString() = 56 | match this with 57 | | ReadDosMagic -> "reading DOS magic" 58 | | MoveToLfanew -> "moving to lfanew field" 59 | | ReadLfanew -> "reading lfanew field" 60 | | MoveToPESignature -> "moving to PE signature" 61 | | ReadPESignature -> "reading PE signature" 62 | | ReadCoffHeader -> "reading COFF header" 63 | | ReadOptionalHeader -> "reading PE optional header" 64 | | ReadDataDirectories -> "reading PE header data directories" 65 | | ReadSectionHeaders -> "reading section headers" 66 | | ReadSectionData -> "reading section data" 67 | 68 | interface IReadState 69 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Signatures/LocalVarSig.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata.Signatures 2 | 3 | open System.Collections.Immutable 4 | open System.Runtime.CompilerServices 5 | 6 | type LocalVariableTag = 7 | | TypedByRef = 0uy 8 | | Type = 1uy 9 | | ByRef = 2uy 10 | 11 | /// Represents a Constraint item (II.23.2.9). 12 | [] 13 | type Constraint = 14 | /// Indicates that the value pointed to by this local variable should not be moved "by the actions of garbage collection" 15 | /// (II.23.2.9). 16 | | Pinned 17 | 18 | /// Represents a single local variable in a LocalVarSig item (II.23.2.6). 19 | [] 20 | type LocalVariable internal 21 | ( 22 | modifiers: CustomMod list, 23 | constraints: Constraint list, 24 | tag: LocalVariableTag, 25 | ltype: EncodedType voption 26 | ) 27 | = 28 | member _.CustomMod = modifiers 29 | member _.Constraints = constraints 30 | member _.Tag = tag 31 | member _.Type = ltype 32 | 33 | (* 34 | [] 35 | [] 36 | type LocalVariable = 37 | | Local of CustomMod: CustomModifiers * Constraints: Constraint list * Type: EncodedType 38 | | ByRef of CustomMod: CustomModifiers * Constraints: Constraint list * Type: EncodedType 39 | | TypedByRef 40 | *) 41 | 42 | [] 43 | module LocalVariable = 44 | /// A constraint list containing the currently only kind of constraint, PINNED (II.23.2.9). 45 | let pinned = [ Constraint.Pinned ] 46 | 47 | let maxLocalCount = System.UInt16.MaxValue 48 | 49 | let inline (|Type|ByRef|TypedByRef|) (local: LocalVariable) = 50 | let inline info() = struct(local.CustomMod, local.Constraints, local.Type.Value) 51 | match local.Tag with 52 | | LocalVariableTag.TypedByRef -> TypedByRef 53 | | LocalVariableTag.ByRef -> ByRef(info()) 54 | | LocalVariableTag.Type 55 | | _ -> Type(info()) 56 | 57 | let Type(modifiers, constraints, localType) = 58 | LocalVariable(modifiers, constraints, LocalVariableTag.Type, ValueSome localType) 59 | 60 | let ByRef(modifiers, constraints, localType) = 61 | LocalVariable(modifiers, constraints, LocalVariableTag.ByRef, ValueSome localType) 62 | 63 | let TypedByRef modifiers = LocalVariable(modifiers, List.empty, LocalVariableTag.TypedByRef, ValueNone) 64 | 65 | /// 66 | /// Represents a LocalVarSig item, which describes the types of all of the local variables of a method (II.23.2.6). 67 | /// 68 | type LocalVarSig = ImmutableArray // TODO: Prevent local variable array from having UInt16.MaxValue or greater items by making custom collection type. 69 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/Constant.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open System 4 | open System.Runtime.CompilerServices 5 | 6 | open FSharpIL.Utilities 7 | 8 | [] 9 | [] 10 | type IntegerConstantKind = 11 | | Bool 12 | | Char 13 | | I1 14 | | U1 15 | | I2 16 | | U2 17 | | I4 18 | | U4 19 | | I8 20 | | U8 21 | 22 | [] 23 | [] 24 | type IntegerConstant = struct 25 | val Tag: IntegerConstantKind 26 | val Value: uint64 27 | 28 | new (value: bool) = { Tag = IntegerConstantKind.Bool; Value = if value then 1UL else 0UL } 29 | new (value: char) = { Tag = IntegerConstantKind.Char; Value = uint64 value } 30 | new (value: int8) = { Tag = IntegerConstantKind.I1; Value = uint64 value } 31 | new (value: uint8) = { Tag = IntegerConstantKind.U1; Value = uint64 value } 32 | new (value: int16) = { Tag = IntegerConstantKind.I2; Value = uint64 value } 33 | new (value: uint16) = { Tag = IntegerConstantKind.U2; Value = uint64 value } 34 | new (value: int32) = { Tag = IntegerConstantKind.I4; Value = uint64 value } 35 | new (value: uint32) = { Tag = IntegerConstantKind.U4; Value = uint64 value } 36 | new (value: int64) = { Tag = IntegerConstantKind.I8; Value = uint64 value } 37 | new (value: uint64) = { Tag = IntegerConstantKind.U8; Value = value } 38 | 39 | override this.ToString() = 40 | match this.Tag with 41 | | IntegerConstantKind.Bool -> if this.Value <> 0UL then "bool(true)" else "bool(false)" 42 | | IntegerConstantKind.Char -> sprintf "char(%i)" (uint16 this.Value) 43 | | IntegerConstantKind.I1 -> sprintf "int8(%i)" (int8 this.Value) 44 | | IntegerConstantKind.U1 -> sprintf "uint8(%i)" (uint8 this.Value) 45 | | IntegerConstantKind.I2 -> sprintf "int16(%i)" (int16 this.Value) 46 | | IntegerConstantKind.U2 -> sprintf "uint16(%i)" (uint16 this.Value) 47 | | IntegerConstantKind.I4 -> sprintf "int32(%i)" (int32 this.Value) 48 | | IntegerConstantKind.U4 -> sprintf "uint32(%i)" (uint32 this.Value) 49 | | IntegerConstantKind.I8 -> sprintf "int64(%i)" (int64 this.Value) 50 | | IntegerConstantKind.U8 -> sprintf "uint64(%i)" this.Value 51 | end 52 | 53 | [] 54 | [] 55 | type FloatConstantKind = 56 | | Single 57 | | Double 58 | 59 | [] 60 | type FloatConstant = 61 | val Tag: FloatConstantKind 62 | val Value: uint64 63 | 64 | new (value: Single) = { Tag = FloatConstantKind.Single; Value = Convert.unsafeTo value } 65 | new (value: Double) = { Tag = FloatConstantKind.Double; Value = Convert.unsafeTo value } 66 | 67 | [] 68 | type Constant = 69 | | NullConstant 70 | | StringConstant of string 71 | | IntegerConstant of IntegerConstant 72 | | FloatConstant of FloatConstant 73 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/BlobWriter.fsi: -------------------------------------------------------------------------------- 1 | [] 2 | module FSharpIL.Writing.BlobWriter 3 | 4 | open FSharpIL 5 | open FSharpIL.Metadata.Signatures 6 | 7 | [] val MaxCompressedUnsigned : uint32 = 0x1FFF_FFFFu 8 | [] val MaxCompressedSigned : int32 = 0x0FFFFFFF 9 | [] val MinCompressedSigned : int32 = 0xF0000000 10 | 11 | /// Calculates how many bytes would be taken up if the specified integer was compressed (II.23.2). 12 | /// 13 | /// Thrown when the cannot be compressed. 14 | /// 15 | val compressedUnsignedSize : value: uint32 -> uint32 16 | 17 | /// Writes a compressed unsigned integer (II.23.2). 18 | /// 19 | /// Thrown when the is greater than the maximum compressed unsigned integer. 20 | /// 21 | val compressedUnsigned : value: uint32 -> stream: byref -> unit 22 | 23 | /// Writes a signed compressed integer (II.23.2). 24 | /// 25 | /// Thrown when the is not in the range -2^28 and 2^28 - 1 inclusive. 26 | /// 27 | val compressedSigned : value: int32 -> stream: byref -> unit 28 | 29 | /// Writes a type encoded in a signature (II.23.2.12). 30 | val etype: EncodedType -> stream: byref -> unit 31 | 32 | /// Writes the signature of a MethodDef (II.23.2.1). 33 | val methodDefSig : signature: inref -> stream: byref -> unit 34 | 35 | /// Writes the signature of a method reference used in the MethodRef table (II.23.2.1). 36 | val methodRefSig: signature: inref -> stream: byref -> unit 37 | 38 | /// Writes the signature of a Field (II.23.2.4). 39 | val fieldSig : signature: inref -> stream: byref -> unit 40 | 41 | /// Writes the signature of a Property (II.23.2.5). 42 | val propertySig : signature: inref -> stream: byref -> unit 43 | 44 | /// Writes a custom attribute (II.23.3). 45 | val customAttrib : attrib: inref -> stream: byref -> unit 46 | 47 | /// Writes the local variable types of a method (II.23.2.6). 48 | /// 49 | /// Thrown when the number of local variables is greater than the maximum number of local variables allowed, or if no local 50 | /// variables are declared. 51 | /// 52 | val localVarSig : signature: LocalVarSig -> stream: byref -> unit // TODO: Make special collection type to enforce correct number of local variable types. 53 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Blobs/Offsets.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata.Blobs 2 | 3 | open System.Runtime.CompilerServices 4 | 5 | open FSharpIL.Metadata 6 | 7 | /// An offset into the #Blob heap pointing to a FieldSig (II.23.2.4). 8 | type [] FieldSigOffset = internal { FieldSig: BlobOffset } 9 | 10 | /// 11 | /// An offset into the #Blob heap pointing to a MethodDefSig, which describes the return type and parameter types 12 | /// of a method (II.23.2.1). 13 | /// 14 | type [] MethodDefSigOffset = internal { MethodDefSig: BlobOffset } 15 | 16 | /// 17 | /// An offset into the #Blob heap pointing to a MethodRefSig (II.23.2.2) or a FieldSig (II.23.2.4). 18 | /// 19 | type [] MemberRefSigOffset = internal { MemberRefSig: BlobOffset } 20 | 21 | /// An offset into the #Blob heap pointing to a constant value (II.22.9). 22 | type [] ConstantOffset = internal { Constant: BlobOffset } 23 | 24 | /// An offset into the #Blob heap pointing to a CustomAttrib item (II.23.3). 25 | type [] CustomAttributeOffset = internal { CustomAttrib: BlobOffset } 26 | 27 | /// 28 | /// An offset into the #Blob heap pointing to a PropertySig item, which describes the type of a property and its 29 | /// parameters (II.23.2.5). 30 | /// 31 | type [] PropertySigOffset = internal { PropertySig: BlobOffset } 32 | 33 | /// 34 | /// An offset into the #Blob heap pointing to a MethodSpec item, which describes the generic arguments of a method 35 | /// (II.23.2.15). 36 | /// 37 | type [] MethodSpecOffset = internal { MethodSpec: BlobOffset } 38 | 39 | /// 40 | /// An offset into the #Blob heap pointing to a LocalVarSig item, which describes the types of the local variables 41 | /// of a method (II.23.2.6). 42 | /// 43 | [] 44 | type LocalVarSigOffset = 45 | internal { LocalVarSig: BlobOffset } 46 | member this.IsNull = this.LocalVarSig.BlobOffset = 0u 47 | 48 | [] 49 | type PublicKeyOrTokenOffset = 50 | { IsPublicKey: bool 51 | Token: BlobOffset } 52 | member this.IsNull = this.Token.BlobOffset = 0u 53 | 54 | [] 55 | module PublicKeyOrTokenOffset = 56 | let PublicKey offset = { IsPublicKey = true; Token = offset } 57 | let Token offset = { IsPublicKey = false; Token = offset } 58 | let Null = Token Unchecked.defaultof 59 | 60 | let inline (|Null|Token|PublicKey|) value = 61 | match value with 62 | | { Token = BlobOffset.IsZero } -> Null 63 | | { IsPublicKey = false; Token = offset } -> Token offset 64 | | { IsPublicKey = true; Token = offset } -> PublicKey offset 65 | -------------------------------------------------------------------------------- /docs/FSharpIL.Documentation/Generator.fs: -------------------------------------------------------------------------------- 1 | module FSharpIL.Documentation.Generator 2 | 3 | open System.IO 4 | 5 | open Argu 6 | 7 | open FSharp.Formatting.Literate.Evaluation 8 | 9 | exception private GenerationException of exn 10 | 11 | type Arguments = 12 | | [] Content_Directory of content: string 13 | | [] Output_Directory of path: string 14 | | [] Style_Directory of style: string 15 | | [] Launch_Debugger 16 | 17 | interface IArgParserTemplate with 18 | member this.Usage = 19 | match this with 20 | | Content_Directory _ -> "specify the directory containing the documentation files" 21 | | Output_Directory _ -> "specify the directory where the resulting HTML documentation is written to" 22 | | Style_Directory _ -> "specify the directory containing CSS files" 23 | | Launch_Debugger -> "calls the Debugger.Launch method" 24 | 25 | let private write (content: DirectoryInfo) (style: DirectoryInfo) (output: DirectoryInfo) = 26 | try 27 | if not output.Exists then output.Create() 28 | 29 | let evaluator = FsiEvaluator(strict = true) 30 | 31 | evaluator.EvaluationFailed.Add (failwithf "Exception thrown while evaluating expression: %A") 32 | 33 | let style' = output.CreateSubdirectory "style" 34 | for file in style.GetFiles() do 35 | file.CopyTo(Path.Combine(style'.FullName, file.Name)) |> ignore 36 | 37 | for script in content.GetFiles("*.fs", SearchOption.AllDirectories) do // TODO: Include .fsx files if necessary. 38 | let output = 39 | let name = Path.GetFileNameWithoutExtension script.FullName 40 | let path = Path.Combine(output.FullName, sprintf $"{name}.html") 41 | FileInfo path 42 | 43 | if output.Exists then 44 | failwithf "Duplicate documentation file %s generated from %s" output.FullName script.FullName 45 | 46 | output.Create() |> Article.write script evaluator 47 | with 48 | | ex -> raise (GenerationException ex) 49 | 50 | [] 51 | let main args = 52 | let parser = ArgumentParser.Create() 53 | try 54 | let results = parser.ParseCommandLine(inputs = args) 55 | 56 | if results.Contains Launch_Debugger then 57 | System.Diagnostics.Debugger.Launch() |> ignore 58 | 59 | let content = results.GetResult Content_Directory |> DirectoryInfo 60 | let style = results.GetResult Style_Directory |> DirectoryInfo 61 | let output = results.GetResult Output_Directory |> DirectoryInfo 62 | write content style output 63 | 0 64 | with 65 | | GenerationException e -> stderr.WriteLine e.Message; -1 66 | | ex -> 67 | match ex with 68 | | :? ArguParseException -> () 69 | | _ -> stderr.WriteLine ex.Message 70 | 71 | parser.PrintUsage() |> stderr.WriteLine; -1 72 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/BuildPE.fsi: -------------------------------------------------------------------------------- 1 | /// Contains functions for building Portable Executable files (II.25). 2 | [] 3 | module FSharpIL.Writing.BuildPE 4 | 5 | open System 6 | 7 | open FSharpIL 8 | open FSharpIL.Metadata 9 | open FSharpIL.PortableExecutable 10 | 11 | /// Creates a Portable Executable file with sections containing arbitrary data. 12 | /// The Common Object File Format (COFF) header of the PE file. 13 | /// The optional header of the PE file. 14 | /// The array of functions used to generate the sections of the PE file. 15 | /// The generated Portable Executable file. 16 | val ofSectionBuilders<'State> : 17 | fileHeader: CoffHeader -> 18 | optionalHeader: OptionalHeader -> 19 | sections: System.Collections.Immutable.ImmutableArray> -> 20 | PEFile 21 | 22 | val ofMetadataBuilder : flags: FileCharacteristics -> builder: CliMetadataBuilder -> PEFile 23 | 24 | /// Creates a Portable Executable file from a CLI metadata module. 25 | val ofModuleBuilder : flags: FileCharacteristics -> builder: CliModuleBuilder -> PEFile 26 | 27 | // TODO: Consider making a version that uses a struct implementing an interface, and make sure to benchmark it. 28 | // TODO: Make mvid an option to generate Guid deterministically based on the contents of the module. 29 | 30 | /// Creates a Portable Executable file containing a CLI metadata module. 31 | /// Flags that specify whether the file is a library or an executable. 32 | /// The fields of the CLI header. 33 | /// 34 | /// The fields of the CLI metadata root, which specifies what version of the Common Language Runtime that the file can run on. 35 | /// 36 | /// The name of the CLI module. 37 | /// 38 | /// An identifier used to distinguish between two versions of the same module, can be randomly generated. 39 | /// 40 | /// 41 | /// The collection of functions used to update the contents of the CLI module and to update the state given the modifications 42 | /// made to the CLI module or any generated validation warnings. 43 | /// 44 | /// The initial state. 45 | /// 46 | /// If successful, the portable executable file containing the CLI module and the final state value; otherwise, an error object 47 | /// describing why the generated CLI metadata was invalid. 48 | /// 49 | val ofModule<'State> : 50 | flags: FileCharacteristics -> 51 | header: CliHeader -> 52 | root: CliMetadataRoot -> 53 | name: Identifier -> 54 | mvid: Guid -> 55 | builder: BuildCli.ModuleBuilder<'State> -> 56 | state: 'State -> 57 | ValidationResult 58 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/CoreAssemblyReference.fsi: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Writing 2 | // TODO: Make this file contain a module named CoreLib 3 | 4 | open FSharpIL.Cli 5 | open FSharpIL.Metadata 6 | 7 | [] 8 | type CoreAssemblyMembers = 9 | member Array: ReferencedTypeMembers 10 | member Delegate: ReferencedTypeMembers 11 | 12 | // TODO: Make this a field of some ObjectMembers class instead. 13 | /// 14 | /// The constructor for , called in the constructor of all directly derived types. 15 | /// 16 | member ObjectConstructor: MethodTok, MethodReference> 17 | 18 | /// 19 | /// The constructor of the type, which accepts a string 20 | /// containing the name of the target framework and its version. 21 | /// 22 | member TargetFrameworkConstructor: 23 | MethodTok, MethodReference> 24 | 25 | /// Represents the assembly containing core types such as . 26 | [] 27 | type CoreAssemblyReference = 28 | member Reference: ReferencedAssembly 29 | /// The core type , which serves as the base type for all types. 30 | member Object: TypeReference 31 | /// 32 | /// The core type , which serves as the base type for all value types (II.13). 33 | /// 34 | member ValueType: TypeReference 35 | /// 36 | /// The core type , which serves as the base type for all delegates (II.14.6). 37 | /// 38 | member Delegate: TypeReference 39 | /// 40 | /// The core type , which serves as the base type for all enumeration types (II.14.3). 41 | /// 42 | member Enum: TypeReference 43 | /// 44 | /// The type, which identifies the target framework that 45 | /// the current assembly or module was compiled with (.NET Core, .NET Framework, .NET standard, etc.). 46 | /// 47 | member TargetFrameworkAttribute: TypeReference 48 | /// 49 | /// The core type , which provides many static methods that compilers can use to create arrays. 50 | /// 51 | member Array: TypeReference 52 | 53 | new: assembly: ReferencedAssembly -> CoreAssemblyReference 54 | 55 | member Assembly : ReferencedAssembly 56 | 57 | /// 58 | /// Adds a reference to System.Private.CoreLib, which is the core assembly for .NET Core and .NET 5+. 59 | /// 60 | static member NetCore: 61 | version: FSharpIL.Metadata.Tables.AssemblyVersion * 62 | publicKeyToken: PublicKeyToken * 63 | ?hash: System.Collections.Immutable.ImmutableArray -> CoreAssemblyReference 64 | 65 | member AddReferencesTo: CliModuleBuilder -> ValidationResult 66 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/Field.fsi: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open System 4 | 5 | open FSharpIL.Metadata 6 | open FSharpIL.Metadata.Tables 7 | 8 | [] 9 | type Field = 10 | val Name: Identifier 11 | val Type: CliType 12 | 13 | internal new: name: Identifier * fieldType: CliType -> Field 14 | 15 | abstract Equals: other: Field -> bool 16 | default Equals: other: Field -> bool 17 | 18 | override ToString: unit -> string 19 | override Equals: obj -> bool 20 | override GetHashCode: unit -> int32 21 | 22 | interface IEquatable 23 | 24 | [] 25 | type DefinedField = 26 | inherit Field 27 | 28 | member Flags: FieldFlags 29 | 30 | new: flags: FieldFlags * name: Identifier * fieldType: CliType -> DefinedField 31 | 32 | override Equals: other: Field -> bool 33 | 34 | [] 35 | type LiteralFieldDefinition = 36 | inherit DefinedField 37 | 38 | member Value: Constant 39 | 40 | /// Represents a defined field whose value is stored at the specified Relative Virtual Address (II.16.3.2). 41 | [] 42 | type RvaFieldDefinition = 43 | inherit DefinedField 44 | 45 | val Value: FSharpIL.ChunkedMemory 46 | 47 | [] 48 | module FieldKinds = 49 | type [] Instance = 50 | interface IAttributeTag 51 | 52 | type [] Static = 53 | interface IAttributeTag 54 | 55 | [] 56 | type FieldDefinition<'Kind when 'Kind :> IAttributeTag and 'Kind : struct> = 57 | inherit DefinedField 58 | 59 | type DefinedField with 60 | static member Instance: 61 | visibility: MemberVisibility * 62 | flags: FieldAttributes * 63 | name: Identifier * 64 | signature: CliType -> FieldDefinition 65 | 66 | static member Static: 67 | visibility: MemberVisibility * 68 | flags: FieldAttributes * 69 | name: Identifier * 70 | signature: CliType -> FieldDefinition 71 | 72 | //static member Literal // TODO: How to enforce correct field type? 73 | //static member Rva 74 | 75 | [] 76 | type ReferencedField = 77 | inherit Field 78 | 79 | val Visibility: ExternalVisibility 80 | 81 | override Equals: other: Field -> bool 82 | 83 | //LiteralFieldReference 84 | 85 | [] 86 | type FieldReference<'Kind when 'Kind :> IAttributeTag> = 87 | inherit ReferencedField 88 | 89 | [] 90 | module Field = 91 | open System.Collections.Generic 92 | 93 | val inline (|Defined|Referenced|): field: Field -> Choice 94 | 95 | [] 96 | type SignatureComparer = interface IEqualityComparer 97 | 98 | val signatureComparer : SignatureComparer 99 | 100 | [] 101 | type DefinitionComparer = interface IEqualityComparer 102 | 103 | val definitionComparer : DefinitionComparer 104 | 105 | [] 106 | module DefinedField = 107 | val inline (|Instance|Static|Literal|WithRva|): 108 | field: DefinedField -> 109 | Choice, 110 | FieldDefinition, 111 | LiteralFieldDefinition, 112 | RvaFieldDefinition> 113 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Tables/CodedIndexPatterns.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module FSharpIL.Metadata.Tables.CodedIndexPatterns 3 | 4 | let inline invalidCodedIndex tag = failwithf "Invalid coded index tag %A" tag 5 | 6 | [] 7 | module TypeDefOrRef = 8 | let inline (|Def|Ref|Spec|) (index: TypeDefOrRef) = 9 | match index.Tag with 10 | | TypeDefOrRefTag.TypeRef -> Ref(TableIndex.ofIntUnsafe index.Index) 11 | | TypeDefOrRefTag.TypeSpec -> Spec(TableIndex.ofIntUnsafe index.Index) 12 | | TypeDefOrRefTag.TypeDef 13 | | _ -> Def(TableIndex.ofIntUnsafe index.Index) 14 | 15 | let Def ({ TableIndex = index }: TableIndex) = TypeDefOrRef(TypeDefOrRefTag.TypeDef, index) 16 | let Ref ({ TableIndex = index }: TableIndex) = TypeDefOrRef(TypeDefOrRefTag.TypeRef, index) 17 | let Spec ({ TableIndex = index }: TableIndex) = TypeDefOrRef(TypeDefOrRefTag.TypeSpec, index) 18 | 19 | 20 | 21 | 22 | [] 23 | module HasCustomAttribute = 24 | let Module = HasCustomAttribute(HasCustomAttributeTag.Module, 1u) 25 | 26 | let Assembly ({ TableIndex = index }: TableIndex) = HasCustomAttribute(HasCustomAttributeTag.Assembly, index) 27 | 28 | 29 | 30 | [] 31 | module MemberRefParent = 32 | let TypeDef({ TableIndex = index }: TableIndex) = MemberRefParent(MemberRefParentTag.TypeDef, index) 33 | let TypeRef({ TableIndex = index }: TableIndex) = MemberRefParent(MemberRefParentTag.TypeRef, index) 34 | let TypeSpec({ TableIndex = index }: TableIndex) = MemberRefParent(MemberRefParentTag.TypeSpec, index) 35 | 36 | [] 37 | module HasSemantics = 38 | let Property({ TableIndex = index }: TableIndex) = HasSemantics(HasSemanticsTag.Property, index) 39 | let Event({ TableIndex = index }: TableIndex) = HasSemantics(HasSemanticsTag.Event, index) 40 | 41 | 42 | 43 | 44 | 45 | [] 46 | module CustomAttributeType = 47 | let MethodDef ({ TableIndex = index }: TableIndex) = 48 | CustomAttributeType(CustomAttributeTypeTag.MethodDef, index) 49 | 50 | let MemberRef ({ TableIndex = index }: TableIndex) = 51 | CustomAttributeType(CustomAttributeTypeTag.MemberRef, index) 52 | 53 | [] 54 | module ResolutionScope = 55 | let inline (|Null|TypeRef|ModuleRef|Module|AssemblyRef|) (rscope: ResolutionScope) = 56 | match rscope.Tag with 57 | | _ when rscope.IsNull -> Null 58 | | ResolutionScopeTag.TypeRef -> TypeRef(TableIndex.ofIntUnsafe rscope.Index) 59 | | ResolutionScopeTag.ModuleRef -> ModuleRef(TableIndex.ofIntUnsafe rscope.Index) 60 | | ResolutionScopeTag.Module -> Module(TableIndex.ofIntUnsafe rscope.Index) 61 | | ResolutionScopeTag.AssemblyRef -> AssemblyRef(TableIndex.ofIntUnsafe rscope.Index) 62 | | bad -> invalidCodedIndex bad 63 | 64 | let Null = ResolutionScope() 65 | let TypeRef({ TableIndex = index }: TableIndex) = ResolutionScope(ResolutionScopeTag.TypeRef, index) 66 | let AssemblyRef({ TableIndex = index }: TableIndex) = ResolutionScope(ResolutionScopeTag.AssemblyRef, index) 67 | 68 | [] 69 | module TypeOrMethodDef = 70 | let Type ({ TableIndex = index }: TableIndex) = TypeOrMethodDef(TypeOrMethodDefTag.TypeDef, index) 71 | let Method ({ TableIndex = index }: TableIndex) = TypeOrMethodDef(TypeOrMethodDefTag.MethodDef, index) 72 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/CoreAssemblyReference.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Writing 2 | 3 | open System.Collections.Immutable 4 | 5 | open FSharpIL.Cli 6 | open FSharpIL.Metadata 7 | 8 | [] 9 | type CoreAssemblyMembers 10 | ( 11 | array: ReferencedTypeMembers<_>, 12 | del: ReferencedTypeMembers<_>, 13 | octor: MethodTok<_, _>, 14 | tfmctor: MethodTok<_, _> 15 | ) 16 | = 17 | member _.Array = array 18 | member _.Delegate = del 19 | member _.ObjectConstructor = octor 20 | member _.TargetFrameworkConstructor = tfmctor 21 | 22 | [] 23 | type CoreAssemblyReference (assembly: ReferencedAssembly) = 24 | static let netCoreLib = FileName.ofStr "System.Private.CoreLib" 25 | static let system = ValueSome(Identifier.ofStr "System") 26 | 27 | let object = 28 | TypeReference.ConcreteClass ( 29 | TypeReferenceParent.Assembly assembly, 30 | system, 31 | Identifier.ofStr "Object" 32 | ) 33 | 34 | let tfmattr = 35 | TypeReference.SealedClass ( 36 | TypeReferenceParent.Assembly assembly, 37 | ValueSome(Identifier.ofStr "System.Runtime.Versioning"), 38 | Identifier.ofStr "TargetFrameworkAttribute" 39 | ) 40 | 41 | let array = 42 | TypeReference.AbstractClass ( 43 | TypeReferenceParent.Assembly assembly, 44 | system, 45 | Identifier.ofStr "Array" 46 | ) 47 | 48 | let del = 49 | TypeReference.AbstractClass ( 50 | TypeReferenceParent.Assembly assembly, 51 | system, 52 | Identifier.ofStr "Delegate" 53 | ) 54 | 55 | let octor = ReferencedMethod.Constructor(ExternalVisibility.Public, ImmutableArray.Empty) 56 | let tfmctor = 57 | ReferencedMethod.Constructor ( 58 | ExternalVisibility.Public, 59 | ImmutableArray.Create(ParameterType.T PrimitiveType.String) 60 | ) 61 | 62 | let referenceSystemType name = 63 | { TypeReference.ResolutionScope = TypeReferenceParent.Assembly assembly 64 | Flags = ValueNone 65 | TypeNamespace = system 66 | TypeName = Identifier.ofStr name } 67 | 68 | member _.Reference = assembly 69 | member _.Object = object 70 | member val ValueType = referenceSystemType "ValueType" 71 | member _.Delegate = del 72 | member val Enum = referenceSystemType "Enum" 73 | member _.TargetFrameworkAttribute = tfmattr 74 | member _.Array = array 75 | 76 | member _.Assembly = assembly 77 | 78 | member this.AddReferencesTo(builder: CliModuleBuilder) = 79 | builder.ReferenceAssembly assembly 80 | 81 | validated { 82 | let! object' = builder.ReferenceType object 83 | let! _ = builder.ReferenceType(ReferencedType.Reference this.ValueType) 84 | let! del' = builder.ReferenceType this.Delegate 85 | let! _ = builder.ReferenceType(ReferencedType.Reference this.Enum) 86 | let! tfmattr' = builder.ReferenceType tfmattr 87 | let! array' = builder.ReferenceType array 88 | 89 | let! octor' = object'.ReferenceMethod octor 90 | let! tfmctor' = tfmattr'.ReferenceMethod tfmctor 91 | 92 | return CoreAssemblyMembers(array', del', octor', tfmctor') 93 | } 94 | 95 | static member NetCore(version, publicKeyToken, ?hash) = 96 | CoreAssemblyReference 97 | { Version = version 98 | PublicKeyOrToken = PublicKeyToken publicKeyToken 99 | Name = netCoreLib 100 | Culture = ValueNone 101 | HashValue = defaultArg hash System.Collections.Immutable.ImmutableArray.Empty } 102 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/MemberTok.fsi: -------------------------------------------------------------------------------- 1 | [] 2 | module FSharpIL.Cli.MemberTok 3 | 4 | open System 5 | open System.Runtime.CompilerServices 6 | 7 | [] 8 | type MemberTok<'Member when 'Member :> IEquatable<'Member>> = 9 | member Owner: TypeTok 10 | member Member: 'Member 11 | 12 | override ToString: unit -> string 13 | 14 | interface IEquatable> 15 | 16 | override GetHashCode: unit -> int32 17 | override Equals: obj -> bool 18 | 19 | type FieldTok = MemberTok 20 | type MethodTok = MemberTok 21 | 22 | [] 23 | type MethodTok<'Owner, 'Method when 'Method : not struct and 'Method :> Method> = 24 | member Method: 'Method 25 | member Token: MethodTok 26 | 27 | override ToString: unit -> string 28 | 29 | override GetHashCode: unit -> int32 30 | 31 | interface IEquatable> 32 | 33 | override Equals: obj -> bool 34 | 35 | [] 36 | type FieldTok<'Owner, 'Field when 'Field : not struct and 'Field :> Field> = 37 | member Field: 'Field 38 | member Token: FieldTok 39 | 40 | override ToString: unit -> string 41 | 42 | override GetHashCode: unit -> int32 43 | 44 | interface IEquatable> 45 | 46 | override Equals: obj -> bool 47 | 48 | val inline internal (|MethodTok|) : method: MethodTok<'Owner, 'Method> -> struct(TypeTok * 'Method) 49 | 50 | [] 51 | module MethodTok = 52 | val internal create : owner: TypeTok -> method: Method -> MethodTok 53 | 54 | val internal unsafeAs : token: MethodTok -> MethodTok<'Owner, 'Method> 55 | 56 | val internal ofTypeDef : 57 | owner: DefinedType -> 58 | method: 'Method -> 59 | cache: NamedTypeCache -> 60 | MethodTok 61 | 62 | val internal ofTypeRef : 63 | owner: ReferencedType -> 64 | method: 'Method -> 65 | cache: NamedTypeCache -> 66 | MethodTok 67 | 68 | val inline internal (|FieldTok|) : field: FieldTok<'Owner, 'Field> -> struct(TypeTok * 'Field) 69 | 70 | [] 71 | module FieldTok = 72 | val internal create : owner: TypeTok -> field: Field -> FieldTok 73 | 74 | val internal unsafeAs : token: FieldTok -> FieldTok<'Owner, 'Method> 75 | 76 | val internal ofTypeDef : owner: DefinedType -> field: 'Field -> cache: NamedTypeCache -> FieldTok 77 | 78 | val internal ofTypeRef : 79 | owner: ReferencedType -> 80 | field: 'Field -> 81 | cache: NamedTypeCache -> 82 | FieldTok 83 | 84 | type DefinedMethodTok = MethodTok 85 | 86 | [] 87 | type PropertyTok = 88 | member Owner: DefinedType 89 | member Property: Property 90 | 91 | member Getter: DefinedMethodTok voption 92 | member Setter: DefinedMethodTok voption 93 | //member Other: seq 94 | 95 | interface IEquatable 96 | 97 | [] 98 | module PropertyTok = 99 | val internal ofTypeDef : owner: DefinedType -> property: Property -> cache: NamedTypeCache -> PropertyTok 100 | 101 | [] 102 | type EventTok = 103 | member Owner: DefinedType 104 | member Event: Event 105 | 106 | member Add: DefinedMethodTok 107 | member Remove: DefinedMethodTok 108 | member Raise: DefinedMethodTok voption 109 | //member Other: seq 110 | 111 | interface IEquatable 112 | 113 | [] 114 | module EventTok = 115 | val internal ofTypeDef : owner: DefinedType -> event: Event -> cache: NamedTypeCache -> EventTok 116 | -------------------------------------------------------------------------------- /src/FSharpIL/Writing/UserStringStreamBuilder.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Writing 2 | 3 | open System 4 | open System.Collections.Generic 5 | open System.Collections.Immutable 6 | 7 | open FSharpIL.Metadata 8 | 9 | [] 10 | type private UserStringEntry = 11 | { String: ReadOnlyMemory 12 | /// The number of bytes allocated to hold the string, excluding the terminal byte. 13 | Length: uint32 14 | TerminalByte: uint8 } 15 | 16 | member inline this.TotalLength = this.Length + 1u 17 | 18 | [] 19 | module private UserStringHelpers = 20 | let inline (|IsNonZero|) value = value = 0us 21 | let inline (|IsInRange|) min max value = value >= max && value <= min 22 | 23 | /// Determines the terminal byte of a string (II.24.2.4). 24 | let getTerminalByte (str: inref>) = 25 | let chars = str.Span 26 | let mutable i, terminal = 0, 0uy 27 | 28 | while i < str.Length && terminal = 0uy do 29 | let ch = uint16 chars.[i] 30 | match ch >>> 8, ch &&& 0xFFus with 31 | | IsNonZero true, _ 32 | | _, IsInRange 0x1us 0x8us true 33 | | _, IsInRange 0xEus 0x1Fus true 34 | | _, 0x27us 35 | | _, 0x2Dus 36 | | _, 0x7Fus -> terminal <- 1uy 37 | | _ -> () 38 | 39 | i <- i + 1 40 | terminal 41 | 42 | [] 43 | type private UserStringSerializer = 44 | interface StringHelpers.IStringSerializer with 45 | /// Appends the length of the blob before the string. 46 | member _.WriteBefore(entry, wr) = if entry.String.Length > 0 then BlobWriter.compressedUnsigned entry.TotalLength &wr 47 | member _.GetChars entry = &entry.String 48 | member _.WriteAfter(entry, wr) = wr.Write entry.TerminalByte 49 | 50 | // Builds the #US metadata stream, containing length prefixed UTF-16 strings (II.24.2.4). 51 | [] 52 | type UserStringStreamBuilder (capacity: int32) = 53 | static let encoding = System.Text.Encoding.Unicode 54 | let mutable offset = 1u 55 | let strings = ImmutableArray.CreateBuilder capacity 56 | let lookup = Dictionary, UserStringOffset>(capacity, StringHelpers.comparer) 57 | do strings.Add Unchecked.defaultof // First entry is empty blob. 58 | do lookup.[ReadOnlyMemory.Empty] <- { UserStringOffset = 0u } 59 | 60 | member _.IsEmpty = strings.Count = 1 61 | 62 | /// The length of the #US metadata stream, in bytes. 63 | member _.StreamLength = offset 64 | 65 | member _.Add str = 66 | match lookup.TryGetValue str with 67 | | true, existing -> existing 68 | | false, _ -> 69 | let offset' = { UserStringOffset = offset } // TODO: Remove common code with #Strings metadata stream builder. 70 | let entry = 71 | { String = str 72 | Length = uint32(encoding.GetByteCount str.Span) 73 | TerminalByte = getTerminalByte &str } 74 | offset <- offset + BlobWriter.compressedUnsignedSize entry.TotalLength + entry.TotalLength 75 | strings.Add entry 76 | lookup.[str] <- offset' 77 | offset' 78 | 79 | member this.AddFolded str = 80 | let mutable offset' = offset 81 | let result = this.Add str 82 | for i = 1 to str.Length - 1 do 83 | offset' <- offset' + 1u 84 | lookup.[str.Slice i] <- { UserStringOffset = offset' } 85 | result 86 | 87 | member this.AddFolded(str: string) = 88 | match str with 89 | | null 90 | | "" -> Unchecked.defaultof<_> 91 | | _ -> this.AddFolded(str.AsMemory()) 92 | 93 | interface IStreamBuilder with 94 | member this.StreamLength = ValueSome this.StreamLength 95 | member _.StreamName = Magic.StreamNames.us 96 | member _.Serialize(wr, _, _) = StringHelpers.serializeStringHeap encoding &wr strings 97 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/Attributes.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open System.Runtime.CompilerServices 4 | 5 | open Microsoft.FSharp.Core.LanguagePrimitives 6 | 7 | open FSharpIL.Metadata.Tables 8 | 9 | type IBitwiseOperand<'T when 'T : struct> = interface 10 | abstract Or: 'T * 'T -> 'T 11 | end 12 | 13 | type IAttributeTag<'Flags> = interface 14 | abstract member RequiredFlags : 'Flags with get 15 | end 16 | 17 | [] 18 | [] 19 | type Attributes<'Tag, 'Flags, 'Op, 'Num 20 | when 'Flags : enum<'Num> 21 | and 'Tag :> IAttributeTag<'Flags> 22 | and 'Tag : struct 23 | and 'Op :> IBitwiseOperand<'Num> 24 | and 'Op : struct 25 | and 'Num : struct> 26 | internal (flags: 'Flags) 27 | = 28 | member _.Flags = flags 29 | 30 | override _.ToString() = flags.ToString() 31 | 32 | static member val None = Unchecked.defaultof> 33 | 34 | static member inline private BitwiseOr(left: 'Flags, right: 'Flags): 'Flags = // TODO: Fix, boxing occurs with OfValue 35 | EnumOfValue(Unchecked.defaultof<'Op>.Or(EnumToValue left, EnumToValue right)) 36 | 37 | static member op_Implicit(flags: Attributes<'Tag, 'Flags, 'Op, 'Num>) = // TODO: Fix, boxing occurs with ToValue 38 | EnumToValue<_, 'Num>(Attributes<'Tag, 'Flags, 'Op, 'Num>.BitwiseOr(flags.Flags, Unchecked.defaultof<'Tag>.RequiredFlags)) 39 | 40 | static member (|||) (left: Attributes<'Tag, 'Flags, 'Op, 'Num>, right: Attributes<'Tag, 'Flags, 'Op, 'Num>) = 41 | Attributes<'Tag, 'Flags, 'Op, 'Num>(Attributes<'Tag, 'Flags, 'Op, 'Num>.BitwiseOr(left.Flags, right.Flags)) 42 | 43 | [] 44 | module AttributeKinds = 45 | type U2 = struct 46 | interface IBitwiseOperand with 47 | [] 48 | member _.Or(left, right) = left ||| right 49 | end 50 | 51 | type U4 = struct 52 | interface IBitwiseOperand with 53 | [] 54 | member _.Or(left, right) = left ||| right 55 | end 56 | 57 | type TypeAttributes<'Tag when 'Tag :> IAttributeTag and 'Tag : struct> = 58 | Attributes<'Tag, TypeDefFlags, AttributeKinds.U4, uint32> 59 | 60 | type FieldAttributes<'Tag when 'Tag :> IAttributeTag and 'Tag : struct> = 61 | Attributes<'Tag, FieldFlags, AttributeKinds.U2, uint16> 62 | 63 | type MethodAttributes<'Tag when 'Tag :> IAttributeTag and 'Tag : struct> = 64 | Attributes<'Tag, MethodDefFlags, AttributeKinds.U2, uint16> 65 | 66 | [] 67 | module Attribute = 68 | let inline (|Attributes|) flags = Attributes.op_Implicit flags 69 | 70 | [] 71 | module TypeAttributes = 72 | type Tag = IAttributeTag 73 | 74 | type [] IHasStaticMethods = inherit Tag 75 | 76 | let BeforeFieldInit<'Tag when 'Tag :> IHasStaticMethods and 'Tag : struct> = 77 | TypeAttributes<'Tag> TypeDefFlags.BeforeFieldInit 78 | 79 | type [] IHasLayout = inherit Tag 80 | 81 | let SequentialLayout<'Tag when 'Tag : struct and 'Tag :> IHasLayout> = 82 | TypeAttributes<'Tag> TypeDefFlags.SequentialLayout 83 | 84 | let ExplicitLayout<'Tag when 'Tag : struct and 'Tag :> IHasLayout> = 85 | TypeAttributes<'Tag> TypeDefFlags.ExplicitLayout 86 | 87 | type [] IHasStringFormat = inherit Tag 88 | 89 | let UnicodeClass<'Tag when 'Tag : struct and 'Tag :> IHasStringFormat> = 90 | TypeAttributes<'Tag> TypeDefFlags.UnicodeClass 91 | 92 | let AutoClass<'Tag when 'Tag : struct and 'Tag :> IHasStringFormat> = 93 | TypeAttributes<'Tag> TypeDefFlags.AutoClass 94 | 95 | type [] ISerializableType = inherit Tag 96 | 97 | let Serializable<'Tag when 'Tag : struct and 'Tag :> ISerializableType> = 98 | TypeAttributes<'Tag> TypeDefFlags.Serializable 99 | 100 | [] 101 | module FieldAttributes = 102 | type Tag = IAttributeTag 103 | 104 | let NotSerialized<'Tag when 'Tag :> Tag and 'Tag : struct and 'Tag :> Tag> = 105 | FieldAttributes<'Tag> FieldFlags.NotSerialized 106 | 107 | let InitOnly<'Tag when 'Tag :> Tag and 'Tag : struct and 'Tag :> Tag> = 108 | FieldAttributes<'Tag> FieldFlags.InitOnly 109 | 110 | [] 111 | module MethodAttributes = 112 | type Tag = IAttributeTag 113 | 114 | let SpecialName<'Tag when 'Tag :> Tag and 'Tag : struct> = 115 | MethodAttributes<'Tag> MethodDefFlags.SpecialName 116 | 117 | let HideBySig<'Tag when 'Tag :> Tag and 'Tag : struct> = 118 | MethodAttributes<'Tag> MethodDefFlags.HideBySig 119 | -------------------------------------------------------------------------------- /src/ilinfo/Program.fs: -------------------------------------------------------------------------------- 1 | module ILInfo.Program 2 | 3 | open System 4 | open System.IO 5 | 6 | open Argu 7 | 8 | [] 9 | [] 10 | type OutputFormat = 11 | | Text 12 | | Html 13 | 14 | type Argument = 15 | | Headers 16 | | Heaps 17 | | No_Metadata 18 | | Public_Only 19 | | Visibility of VisibilityFilter list 20 | | [] File of path: string 21 | | [] Format of OutputFormat 22 | | Launch_Debugger 23 | | [] Output of path: string 24 | 25 | interface IArgParserTemplate with 26 | member this.Usage = 27 | match this with 28 | | Headers -> "Include fields of file headers in the output." 29 | | Heaps -> "Include the raw metadata heaps in the output." 30 | | No_Metadata -> "Exclude CIL metadata output." 31 | | Public_Only -> "Only include items with public visibility in the output." 32 | | Visibility _ -> "Only include items with the specified visibility." 33 | | File _ -> "Read input from the specified file." 34 | | Format _ -> "Output in the specified format." 35 | | Launch_Debugger -> "Launch the debugger." 36 | | Output _ -> "Direct output to the specified file instead of to standard output." 37 | 38 | type ParsedArguments = 39 | { [] mutable IncludeHeaders: IncludeHeaders 40 | [] mutable IncludeMetadata: IncludeMetadata 41 | [] mutable InputFile: string 42 | [] mutable LaunchDebugger: bool 43 | mutable OutputFormat: OutputFormat 44 | mutable OutputKind: OutputKind 45 | mutable VisibilityFilter: VisibilityFilter } 46 | 47 | let (|ValidFile|NotFound|InvalidPath|UnauthorizedAccess|) path = 48 | try 49 | let file = FileInfo path 50 | if file.Exists 51 | then ValidFile file 52 | else NotFound path 53 | with 54 | | :? ArgumentException 55 | | :? PathTooLongException -> InvalidPath path 56 | | :? UnauthorizedAccessException -> UnauthorizedAccess path 57 | 58 | let exitfn format = Printf.kprintf (fun msg -> eprintfn "error : %s" msg; -1) format 59 | 60 | // TODO: Rename to fsdasm 61 | [] 62 | let main args = 63 | let parser = ArgumentParser.Create() 64 | try 65 | let result = parser.ParseCommandLine args 66 | let args' = { OutputFormat = OutputFormat.Text; OutputKind = OutputKind.Console; VisibilityFilter = VisibilityFilter.Public } 67 | 68 | for arg in result.GetAllResults() do 69 | match arg with 70 | | Headers -> args'.IncludeHeaders <- IncludeHeaders 71 | | Heaps -> failwith "TODO: Allow printing of raw metadata heaps" 72 | | No_Metadata -> args'.IncludeMetadata <- NoMetadata 73 | | Public_Only -> args'.VisibilityFilter <- VisibilityFilter.Public 74 | | Visibility vis -> args'.VisibilityFilter <- List.reduce (|||) vis 75 | | File file -> args'.InputFile <- file 76 | | Format format -> args'.OutputFormat <- format 77 | | Launch_Debugger -> args'.LaunchDebugger <- true 78 | | Output file -> args'.OutputKind <- OutputKind.File file 79 | 80 | if args'.LaunchDebugger then System.Diagnostics.Debugger.Launch() |> ignore 81 | 82 | match args' with 83 | | { InputFile = NotFound path } -> exitfn "The file \"%s\" does not exist." path 84 | | { InputFile = InvalidPath path } 85 | | { OutputKind = OutputKind.File(InvalidPath path) } -> exitfn "The file \"%s\" is invalid." path 86 | | { InputFile = UnauthorizedAccess path } 87 | | { OutputKind = OutputKind.File(UnauthorizedAccess path) } -> exitfn "Cannot access the file \"%s\"." path 88 | | { InputFile = ValidFile file; OutputKind = output; OutputFormat = format } -> 89 | let output' = 90 | match format with 91 | | OutputFormat.Text -> ILOutput.text 92 | | OutputFormat.Html -> 93 | // TODO: Write HTML tag stuff. 94 | ILOutput.html 95 | use destination = 96 | match output with 97 | | OutputKind.Console -> stdout 98 | | OutputKind.File path -> new StreamWriter(path) :> TextWriter 99 | use reader = file.OpenRead() 100 | 101 | ILOutput.write args'.IncludeHeaders args'.IncludeMetadata args'.VisibilityFilter 102 | |> FSharpIL.Reading.ReadPE.fromStream reader (output', new IndentedTextWriter(destination, " ")) 103 | |> ignore 104 | 0 105 | with 106 | | :? ArguException as e -> 107 | stderr.WriteLine e.Message 108 | -1 109 | -------------------------------------------------------------------------------- /src/FSharpIL/Utilities/Compare.fs: -------------------------------------------------------------------------------- 1 | module internal FSharpIL.Utilities.Compare 2 | 3 | open System 4 | open System.Collections.Generic 5 | open System.Collections.Immutable 6 | open System.Runtime.CompilerServices 7 | 8 | /// 9 | /// Contains functions for comparing objects and collections containing objects that implement 10 | /// . 11 | /// 12 | [] 13 | module Equatable = 14 | let inline withComparer (comparer: IEqualityComparer<'T>) x y = comparer.Equals(x, y) 15 | 16 | type [] IReferenceComparer<'T when 'T : struct> = 17 | abstract Equals: x: inref<'T> * y: inref<'T> -> bool 18 | abstract GetHashCode: inref<'T> -> int32 19 | 20 | let inline equals<'X, 'Y when 'X :> IEquatable<'Y>> (x: 'X) (y: 'Y) = x.Equals(other = y) 21 | 22 | let inline sequences x y = 23 | let mutable xenumerator = (^Sequence : (member GetEnumerator: unit -> ^Enumerator) x) 24 | let mutable yenumerator = (^Sequence : (member GetEnumerator: unit -> ^Enumerator) y) 25 | let mutable eq, cont = true, true 26 | while eq && cont do 27 | let mutable xmoved = (^Enumerator : (member MoveNext: unit -> bool) xenumerator) 28 | let mutable ymoved = (^Enumerator : (member MoveNext: unit -> bool) yenumerator) 29 | 30 | cont <- xmoved && ymoved 31 | 32 | if xmoved = ymoved then 33 | if cont then 34 | eq <- 35 | equals (^Enumerator : (member Current: ^T) xenumerator) (^Enumerator : (member Current: ^T) yenumerator) 36 | else eq <- false 37 | eq 38 | 39 | let lists (x: #IList<'X>) (y: #IList<'Y>) = 40 | let mutable eq, i = x.Count = y.Count, 0 41 | while eq && i < x.Count do 42 | eq <- equals x.[i] y.[i] 43 | i <- i + 1 44 | eq 45 | 46 | let blocks (x: ImmutableArray<'X>) (y: ImmutableArray<'Y>) = (x.IsDefaultOrEmpty && y.IsDefaultOrEmpty) || lists x y 47 | 48 | let inline voption (x: 'X voption) (y: 'Y voption) = 49 | match x, y with 50 | | ValueSome x', ValueSome y' -> equals x' y' 51 | | ValueNone, ValueNone -> true 52 | | _ -> false 53 | 54 | type BlockComparer<'T when 'T :> IEquatable<'T>> = 55 | | BlockComparer 56 | 57 | interface IEqualityComparer> with 58 | member _.Equals(x, y) = blocks x y 59 | member _.GetHashCode obj = 60 | let mutable hcode = HashCode() 61 | 62 | if not obj.IsDefaultOrEmpty then 63 | for i = 0 to obj.Length - 1 do hcode.Add obj.[i] 64 | 65 | hcode.ToHashCode() 66 | 67 | [] 68 | type ValueOptionComparer<'T when 'T :> IEquatable<'T>> (comparer: IEqualityComparer<'T>) = 69 | interface IEqualityComparer<'T voption> with 70 | member _.Equals(x, y) = 71 | match x, y with 72 | | ValueSome x', ValueSome y' -> comparer.Equals(x', y') 73 | | ValueNone, ValueNone -> true 74 | | _ -> false 75 | 76 | member _.GetHashCode value = 77 | match value with 78 | | ValueSome value' -> comparer.GetHashCode value' 79 | | ValueNone -> 0 80 | 81 | // TODO: Fix equality implementations by making x be a generic parameter. 82 | /// Equality operator for objects implementing . 83 | let inline (===) (x: 'X when 'X :> IEquatable<'Y>) (y: 'Y) = Equatable.equals x y 84 | 85 | /// 86 | /// Contains functions for comparing objects and collections containing objects that implement 87 | /// . 88 | /// 89 | [] 90 | module Comparable = 91 | /// 92 | /// A value less than zero if comes before , 93 | /// a value greater than zero if comes after , 94 | /// or zero if is equal to . 95 | /// 96 | let inline comparison<'X, 'Y when 'X :> IComparable<'Y>> (x: 'X) (y: 'Y) = x.CompareTo y 97 | 98 | /// Less-than operator for objects implementing . 99 | let inline (<.) x y = Comparable.comparison x y < 0 100 | 101 | /// Less-than-or-equal operator for objects implementing . 102 | let inline (<=.) x y = Comparable.comparison x y <= 0 103 | 104 | /// Greater-than operator for objects implementing . 105 | let inline (.>) x y = Comparable.comparison x y > 0 106 | 107 | /// Greater-than-or-equal operator for objects implementing . 108 | let inline (.>=) x y = Comparable.comparison x y >= 0 109 | -------------------------------------------------------------------------------- /src/FSharpIL/Metadata/Strings.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Metadata 2 | 3 | open System 4 | open System.Runtime.CompilerServices 5 | 6 | open FSharpIL.Utilities.Compare 7 | 8 | /// Represents an offset into the #Strings metadata stream pointing to a non-empty string. 9 | [] 10 | [] 11 | [] 12 | type IdentifierOffset = 13 | internal { Offset: StringOffset } 14 | 15 | /// Represents an offset into the #Strings metadata stream pointing to the name of an assembly or file. 16 | [] 17 | [] 18 | [] 19 | type FileNameOffset = 20 | internal { Offset: IdentifierOffset } 21 | 22 | /// 23 | /// Represents a that cannot be , be empty, or contain any null characters. 24 | /// 25 | [] 26 | type Identifier = 27 | val private identifier: string 28 | 29 | internal new (identifier) = { identifier = identifier } 30 | 31 | member this.AsMemory() = this.identifier.AsMemory() 32 | 33 | override this.ToString() = this.identifier 34 | 35 | static member (+) (x: Identifier, y: Identifier) = Identifier(x.identifier + y.identifier) 36 | 37 | static member (+) (id: Identifier, str) = 38 | match str with 39 | | null 40 | | "" -> id 41 | | _ -> Identifier(id.identifier + str) 42 | 43 | override this.GetHashCode() = StringComparer.Ordinal.GetHashCode this.identifier 44 | 45 | interface IEquatable with 46 | member this.Equals other = StringComparer.Ordinal.Equals(this.identifier, other.identifier) 47 | 48 | interface IComparable with 49 | member this.CompareTo other = StringComparer.Ordinal.Compare(this.identifier, other.identifier) 50 | 51 | interface IComparable with member this.CompareTo obj = Comparable.comparison this (obj :?> Identifier) 52 | 53 | override this.Equals obj = 54 | match obj with 55 | | :? Identifier as other -> this === other 56 | | _ -> false 57 | 58 | /// Represents the name of an assembly (II.22.2) or file (II.22.19). 59 | [] 60 | type FileName = 61 | internal { FileName: Identifier } 62 | override this.ToString() = this.FileName.ToString() 63 | 64 | [] 65 | module Identifier = 66 | let inline private create nulli empty nullc valid = 67 | function 68 | | null -> nulli() 69 | | "" -> empty() 70 | | str when str.Contains '\000' -> nullc() 71 | | str -> Identifier str |> valid 72 | 73 | /// Tries to create an identifier from the specified string. 74 | let tryOfStr str = 75 | let inline none() = ValueNone 76 | create none none none ValueSome str 77 | 78 | /// Creates an identifier from the specified string. 79 | /// Thrown when the input string is . 80 | /// Thrown when the input string is empty or contains a null character. 81 | let ofStr str = 82 | create 83 | (fun() -> nullArg "str") 84 | (fun() -> invalidArg "str" "The string cannot be null or empty") 85 | (fun() -> invalidArg "str" "The string cannot contain any null characters") 86 | id 87 | str 88 | 89 | let inline asMemory (id: Identifier) = id.AsMemory() 90 | 91 | [] 92 | module FileName = 93 | let inline private invalidFileName() = invalidArg "str" "The assembly name was empty or contains invalid characters." 94 | 95 | let tryOfId (id: Identifier) = 96 | if id.ToString().IndexOfAny [| ':'; '\\'; '/' |] = -1 97 | then ValueSome { FileName = id } 98 | else ValueNone 99 | 100 | let tryOfStr (str: string) = 101 | match Identifier.tryOfStr str with 102 | | ValueSome name -> tryOfId name 103 | | ValueNone -> ValueNone 104 | 105 | /// Creates a name for an Assembly or AssemblyRef. 106 | /// 107 | /// The name is empty or contains a colon, a forward slash, or a backslash character. 108 | /// 109 | let ofStr (str: string) = 110 | match tryOfStr str with 111 | | ValueSome name -> name 112 | | ValueNone -> invalidFileName() 113 | 114 | let ofId id = 115 | match tryOfId id with 116 | | ValueSome name -> name 117 | | ValueNone -> invalidFileName() 118 | 119 | [] 120 | module StringOffsetPatterns = 121 | let (|IdentifierOffset|) { IdentifierOffset.Offset = offset } = offset 122 | let (|FileNameOffset|) { FileNameOffset.Offset = offset } = offset.Offset 123 | -------------------------------------------------------------------------------- /src/FSharpIL/Reading/MetadataTablesReader.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Reading 2 | 3 | open FSharpIL.Metadata.Tables 4 | 5 | /// Streams refered to by various rows in the metadata tables. 6 | type ReferencedMetadataStreams = 7 | { Strings: ParsedStringsStream 8 | Guid: ParsedGuidStream 9 | UserString: ParsedUserStringStream 10 | Blob: ParsedBlobStream 11 | Tables: ParsedMetadataTables } 12 | 13 | // TODO: Allow function to return a ReadError/BlobError 14 | // TODO: Remove sequential reader 15 | // TODO: Make a module called BrowseCli that returns the ReferencedMetadataStreams. 16 | //[] 17 | type TableRowReader<'Row, 'State> = StructureReader // ('Row -> ReferencedMetadataStreams -> FSharpIL.PortableExecutable.FileOffset -> 'State -> 'State voption) voption 18 | 19 | //[] 20 | [] 21 | type SequentialTableReader<'State> = 22 | { ReadHeader: StructureReader 23 | ReadModule: TableRowReader 24 | ReadTypeRef: TableRowReader 25 | ReadTypeDef: TableRowReader 26 | ReadField: TableRowReader 27 | ReadMethodDef: TableRowReader 28 | ReadParam: TableRowReader 29 | ReadInterfaceImpl: TableRowReader 30 | ReadMemberRef: TableRowReader 31 | ReadConstant: TableRowReader 32 | ReadCustomAttribute: TableRowReader 33 | //ReadFieldMarshal: TableRowReader 34 | //ReadDeclSecurity: TableRowReader 35 | ReadClassLayout: TableRowReader 36 | //FieldLayout: TableRowReader 37 | ReadStandaloneSig: TableRowReader 38 | //ReadEventMap: TableRowReader 39 | //ReadEvent: TableRowReader 40 | ReadPropertyMap: TableRowReader 41 | ReadProperty: TableRowReader 42 | ReadMethodSemantics: TableRowReader 43 | ReadMethodImpl: TableRowReader 44 | //ReadModuleRef: TableRowReader 45 | ReadTypeSpec: TableRowReader 46 | //ReadImplMap: TableRowReader 47 | ReadFieldRva: TableRowReader 48 | ReadAssembly: TableRowReader 49 | ReadAssemblyRef: TableRowReader 50 | //ReadFile: TableRowReader 51 | //ReadExportedType: TableRowReader 52 | ReadManifestResource: TableRowReader 53 | ReadNestedClass: TableRowReader 54 | ReadGenericParam: TableRowReader 55 | ReadMethodSpec: TableRowReader 56 | ReadGenericParamConstraint: TableRowReader 57 | // TODO: Include debugging rows not covered in latest version of ECMA-335. 58 | } 59 | 60 | type MetadataTablesReader<'State> = 61 | //| AllAtOnce of (ReferencedMetadataStreams -> FileOffset -> 'State -> Result<'State, ReadError>) 62 | /// The tables are read in the order that they appear. 63 | | (*[]*) SequentialTableReader of SequentialTableReader<'State> 64 | 65 | [] 66 | module MetadataTablesReader = 67 | [] 68 | let defaultSequentialReader<'State> = 69 | { ReadHeader = ValueNone 70 | ReadModule = ValueNone 71 | ReadTypeRef = ValueNone 72 | ReadTypeDef = ValueNone 73 | ReadField = ValueNone 74 | ReadMethodDef = ValueNone 75 | ReadParam = ValueNone 76 | ReadInterfaceImpl = ValueNone 77 | ReadMemberRef = ValueNone 78 | ReadConstant = ValueNone 79 | ReadCustomAttribute = ValueNone 80 | //ReadFieldMarshal = ValueNone 81 | //ReadDeclSecurity = ValueNone 82 | ReadClassLayout = ValueNone 83 | //FieldLayout = ValueNone 84 | ReadStandaloneSig = ValueNone 85 | //ReadEventMap = ValueNone 86 | //ReadEvent = ValueNone 87 | ReadPropertyMap = ValueNone 88 | ReadProperty = ValueNone 89 | ReadMethodSemantics = ValueNone 90 | ReadMethodImpl = ValueNone 91 | //ReadModuleRef = ValueNone 92 | ReadTypeSpec = ValueNone 93 | //ReadImplMap = ValueNone 94 | ReadFieldRva = ValueNone 95 | ReadAssembly = ValueNone 96 | ReadAssemblyRef = ValueNone 97 | //ReadFile = ValueNone 98 | //ReadExportedType = ValueNone 99 | ReadManifestResource = ValueNone 100 | ReadNestedClass = ValueNone 101 | ReadGenericParam = ValueNone 102 | ReadMethodSpec = ValueNone 103 | ReadGenericParamConstraint = ValueNone 104 | } 105 | : SequentialTableReader<'State> 106 | -------------------------------------------------------------------------------- /src/FSharpIL/Cli/Field.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpIL.Cli 2 | 3 | open System 4 | open System.Runtime.CompilerServices 5 | 6 | open FSharpIL.Metadata 7 | open FSharpIL.Metadata.Tables 8 | 9 | open FSharpIL.Utilities.Compare 10 | 11 | type Field = 12 | val Name: Identifier 13 | val Type: CliType 14 | 15 | new (name, fieldType) = { Name = name; Type = fieldType } 16 | 17 | abstract Equals: other: Field -> bool 18 | default this.Equals(other: Field) = 19 | this.Name === other.Name && this.Type === other.Type 20 | 21 | override this.ToString() = this.Name.ToString() 22 | 23 | override this.Equals(obj: obj) = 24 | match obj with 25 | | :? Field as other -> this.Equals(other = other) 26 | | _ -> false 27 | 28 | override this.GetHashCode() = HashCode.Combine(this.Name, this.Type) 29 | 30 | interface IEquatable with member this.Equals other = this.Equals(other = other) 31 | 32 | [] 33 | type DefinedField (flags: FieldFlags, name, fieldType) = 34 | inherit Field(name, fieldType) 35 | 36 | member _.Flags = flags 37 | 38 | override _.Equals(other: Field) = 39 | match other with 40 | | :? DefinedField -> base.Equals(other = other) 41 | | _ -> false 42 | 43 | [] 44 | type LiteralFieldDefinition (name, signature, value: Constant) = 45 | inherit DefinedField(FieldFlags.Static ||| FieldFlags.Literal, name, signature) 46 | 47 | member _.Value = value 48 | 49 | [] 50 | type RvaFieldDefinition = 51 | inherit DefinedField 52 | 53 | val Value: FSharpIL.ChunkedMemory 54 | 55 | new (name, signature, value: FSharpIL.ChunkedMemory) = 56 | { inherit DefinedField(FieldFlags.Static ||| FieldFlags.HasFieldRva, name, signature) 57 | Value = value } 58 | 59 | [] 60 | module FieldKinds = 61 | type Instance = struct 62 | interface IAttributeTag with 63 | member _.RequiredFlags with [] get() = Unchecked.defaultof<_> 64 | end 65 | 66 | type Static = struct 67 | interface IAttributeTag with 68 | member _.RequiredFlags with [] get() = FieldFlags.Static 69 | end 70 | 71 | [] 72 | type FieldDefinition<'Kind when 'Kind :> IAttributeTag and 'Kind : struct> 73 | ( 74 | visibility, 75 | flags: FieldAttributes<'Kind>, 76 | name, 77 | signature 78 | ) 79 | = 80 | inherit DefinedField ( 81 | Unchecked.defaultof<'Kind>.RequiredFlags ||| MemberVisibility.ofField visibility ||| flags.Flags, 82 | name, 83 | signature 84 | ) 85 | 86 | type DefinedField with 87 | static member Instance(visibility, flags, name, signature) = 88 | FieldDefinition(visibility, flags, name, signature) 89 | 90 | static member Static(visibility, flags, name, signature) = 91 | FieldDefinition(visibility, flags, name, signature) 92 | 93 | [] 94 | type ReferencedField = 95 | inherit Field 96 | 97 | val Visibility: ExternalVisibility 98 | 99 | new (visibility, name, signature) = 100 | { inherit Field(name, signature) 101 | Visibility = visibility } 102 | 103 | override _.Equals(other: Field) = 104 | match other with 105 | | :? ReferencedField -> base.Equals(other = other) 106 | | _ -> false 107 | 108 | [] 109 | type FieldReference<'Kind when 'Kind :> IAttributeTag> (visibility, name, signature) = 110 | inherit ReferencedField(visibility, name, signature) 111 | 112 | [] 113 | module Field = 114 | open System.Collections.Generic 115 | 116 | let inline (|Defined|Referenced|) (field: Field) = 117 | match field with 118 | | :? DefinedField as fdef -> Defined fdef 119 | | _ -> Referenced(field :?> ReferencedField) 120 | 121 | [] 122 | type SignatureComparer() = 123 | interface IEqualityComparer with 124 | member _.Equals(x, y) = x.Type === y.Type 125 | member _.GetHashCode field = field.Type.GetHashCode() 126 | 127 | let signatureComparer = SignatureComparer() 128 | 129 | [] 130 | type DefinitionComparer() = 131 | interface IEqualityComparer with 132 | member _.Equals(x, y) = 133 | (x.Flags ||| y.Flags &&& FieldFlags.FieldAccessMask <> FieldFlags.CompilerControlled) && x.Equals y 134 | member _.GetHashCode field = field.GetHashCode() 135 | 136 | let definitionComparer = DefinitionComparer() 137 | 138 | [] 139 | module DefinedField = 140 | let inline (|Instance|Static|Literal|WithRva|) (field: DefinedField) = 141 | match field with 142 | | :? FieldDefinition as ifield -> Instance ifield 143 | | :? FieldDefinition as sfield -> Static sfield 144 | | :? LiteralFieldDefinition as lfield -> Literal lfield 145 | | _ -> WithRva(field :?> RvaFieldDefinition) 146 | --------------------------------------------------------------------------------