BadImageFormatException : Bad IL format when using `base`
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
}
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
@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.