Skip to content

BadImageFormatException : Bad IL format when using base #13926

Description

@abelbraaksma

Using base.XXX to call a base method causes a BadImageFormatException to be raised.

EDIT: it looks like F# allowing me to call an abstract method, as the base class is abstract. This, I believe, should not be allowed and JIT therefore throws a BadImageFormatException. If you try to write code in F# with an AbstractClass and a derived class, you'd receive a normal compile-time error.

Repro steps

The following code (requires Giraffe and FsUnit to run the test) throws a BadImageFormatException

namespace Test

open Giraffe
open Xunit
open FsUnit.Xunit

open System.Text.Json
open System.Text.Json.Serialization

type StringTrimJsonSerializer(o: JsonSerializerOptions) =
    inherit JsonConverter<string>()

    override this.Read(reader, _, _) =
        match reader.TokenType with
        | JsonTokenType.String -> reader.GetString().Trim()
        | _ -> JsonException("Type is not a string") |> raise

    /// This causes a BadImageFormatException
    override this.Write(writer, objectToWrite, options) = base.Write(writer, objectToWrite, options)


module SerializationTests =

    type SomeType = { Amount: decimal; Currency: string }

    let serialize item =
        let options = SystemTextJson.Serializer.DefaultOptions
        StringTrimJsonSerializer options |> options.Converters.Add
        JsonSerializer.Serialize(item, options)

    let deserialize<'T> (stringValue: string) =
        let options = SystemTextJson.Serializer.DefaultOptions
        StringTrimJsonSerializer options |> options.Converters.Add
        JsonSerializer.Deserialize<'T>(stringValue, options)

    [<Fact>]
    let ``Roundtip type should trim currency whitespace`` () =
        { Amount = 42.99M; Currency = "  USD   " }
        |> serialize
        |> deserialize<SomeType>
        |> should equal { Amount = 42.99M; Currency = "USD" }

Expected behavior

Should call the base method or give compile error if such method doesn't exist.

Actual behavior

Throws:

BadImageFormatException : Bad IL format.

Known workarounds

Don't use base in a derived method.

Related information

On dotnet 6, using System.Text.Json serialization BCL classes, Windows 10/11.

EDIT, the IL of the offending code looks as follows (Release build):

.method public hidebysig virtual 
	instance void Write (
		class [System.Text.Json]System.Text.Json.Utf8JsonWriter writer,
		string objectToWrite,
		class [System.Text.Json]System.Text.Json.JsonSerializerOptions options
	) cil managed 
{
	// Method begins at RVA 0x316c
	// Header size: 1
	// Code size: 10 (0xa)
	.maxstack 8

	// base.Write(writer, objectToWrite, options);
	IL_0000: ldarg.0
	IL_0001: ldarg.1
	IL_0002: ldarg.2
	IL_0003: ldarg.3
	IL_0004: call instance void class [System.Text.Json]System.Text.Json.Serialization.JsonConverter`1<string>::Write(class [System.Text.Json]System.Text.Json.Utf8JsonWriter, !0, class [System.Text.Json]System.Text.Json.JsonSerializerOptions)
	// }
	IL_0009: ret
} 

Activity

  1. vzarytovskii commented on Sep 19, 2022

    @vzarytovskii
    Member

    Reproduces on

    module Testing
    
    open System.Text.Json
    open System.Text.Json.Serialization
    
    type StringTrimJsonSerializer(o: JsonSerializerOptions) =
        inherit JsonConverter<string>()
        override this.Read(reader, _, _) =
            match reader.TokenType with
            | JsonTokenType.String -> reader.GetString().Trim()
            | _ -> JsonException("Type is not a string") |> raise
        override this.Write(writer, objectToWrite, options) = base.Write(writer, objectToWrite, options)
    
    type SomeType = { Foo: string}
    
    let serialize item =
        let options = JsonSerializerOptions()
        StringTrimJsonSerializer options |> options.Converters.Add
        JsonSerializer.Serialize(item, options)
    
    [<EntryPoint>]
    let main _ =
        { Foo = "a" } |> serialize
        0
    

    Does not reproduce on:

    module Testing
    
    open System.Text.Json
    open System.Text.Json.Serialization
    
    type StringTrimJsonSerializer(o: JsonSerializerOptions) =
        inherit JsonConverter<string>()
        override this.Read(reader, _, _) =
            match reader.TokenType with
            | JsonTokenType.String -> reader.GetString().Trim()
            | _ -> JsonException("Type is not a string") |> raise
        override this.Write(writer, objectToWrite, options) = base.Write(writer, objectToWrite, options)
    
    type SomeType = { Foo: int }
    
    let serialize item =
        let options = JsonSerializerOptions()
        StringTrimJsonSerializer options |> options.Converters.Add
        JsonSerializer.Serialize(item, options)
    
    [<EntryPoint>]
    let main _ =
        { Foo = 1 } |> serialize
        0

    Both release and debug

  2. abelbraaksma commented on Sep 19, 2022

    @abelbraaksma
    ContributorAuthor

    @vzarytovskii The latter never calls the StringTrimJsonSerializer.Write method (there's no string in the type), so it makes sense that it doesn't fail. The JIT never has to emit the method, so you won't get the BadImageFormatException.

    The problem seems to be that base here is an abstract class and the Write method itself is also abstract. In other words, it should never compile. If you try this with F# classes (abstract A, concrete B derives from A) then if B uses base.Foo it'll give a compile error saying that you cannot call an abstract method.

    The same is true here, but for some reason, F# doesn't consider it an abstract method.

  3. added this to the Backlog milestone on Sep 21, 2022
  4. self-assigned this
    on Jun 1, 2023
  5. moved this from Not Planned to Planned in F# Compiler and Toolingon Jun 1, 2023
  6. modified the milestones: Backlog, June-2023 on Jun 1, 2023
  7. added a commit that references this issue on Mar 5, 2024
    dd3b52c
  8. added a commit that references this issue on Mar 5, 2024
    d73bb4b
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Area-Compiler-CheckingType checking, attributes and all aspects of logic checkingBugImpact-Medium(Internal MS Team use only) Describes an issue with moderate impact on existing code.

Type

No type

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions