Skip to content

System.Text.Json source generator emits invalid constructor accessor for generic types with inaccessible constructors (MissingMethodException) #133369

Description

@svick

Description

Using the System.Text.Json source generator to deserialize a generic type whose constructor is inaccessible (e.g. a private constructor annotated with [JsonConstructor]) generates code that compiles but throws System.MissingMethodException at runtime. The equivalent non-generic type works.

Repro

using System.Text.Json;
using System.Text.Json.Serialization;

Wrapper<int>? result = JsonSerializer.Deserialize("{\"Value\":42}", MyContext.Default.WrapperInt32);
System.Console.WriteLine(result!.Value);

public class Wrapper<T>
{
    [JsonConstructor]
    private Wrapper(T value) => Value = value;
    public T Value { get; }
}

[JsonSerializable(typeof(Wrapper<int>))]
public partial class MyContext : JsonSerializerContext { }

Expected

Deserialization succeeds and prints 42, the same as for a non-generic type with an inaccessible [JsonConstructor].

Actual

System.MissingMethodException: Method not found: 'Wrapper`1..ctor'.

Cause

For an inaccessible constructor the generator emits a constructor accessor. For a generic type it emits a flat [UnsafeAccessor(UnsafeAccessorKind.Constructor)] extern that names the closed generic type directly:

[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
private static extern Wrapper<int> __ctor_WrapperInt32(int value);

UnsafeAccessor does not resolve a constructor against a directly-named closed generic type — it must live inside a generic wrapper class whose type parameters flow into the signature, which is what the generator already does for generic member (get/set/field) accessors but not for constructors. This is confirmed by an isolated test:

using System;
using System.Runtime.CompilerServices;

class Gen<T>
{
    private Gen(T value) { Value = value; }
    public T Value;
}

static class Flat
{
    [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
    public static extern Gen<int> Create(int value);   // what STJ emits today
}
static class Wrapper<T>
{
    [UnsafeAccessor(UnsafeAccessorKind.Constructor)]
    public static extern Gen<T> Create(T value);       // what STJ should emit
}

class Program
{
    static void Main()
    {
        try { Console.WriteLine("flat OK: " + Flat.Create(1).Value); }
        catch (Exception e) { Console.WriteLine("flat FAIL: " + e.GetType().Name); }
        try { Console.WriteLine("wrapper OK: " + Wrapper<int>.Create(2).Value); }
        catch (Exception e) { Console.WriteLine("wrapper FAIL: " + e.GetType().Name); }
    }
}
// Output:
// flat FAIL: MissingMethodException
// wrapper OK: 2

Relevant source:

  • src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs:940-943 — the constructor accessor is enabled for generic types (gated on SupportsGenericUnsafeAccessors).
  • src/libraries/System.Text.Json/gen/JsonSourceGenerator.Emitter.cs:1396-1397 — emits private static extern {closedTypeFQN} __ctor_...(...) with no generic wrapper class (unlike the member accessors, which use __GenericAccessors_<T>).

Version / regression

Introduced by #124650 ("[STJ Source gen] Use UnsafeAccessor/reflection for inaccessible members"). Present on main and in release/11.0-preview4 through release/11.0-preview7, so it ships in .NET 11 previews. Released .NET 10 is unaffected: there an inaccessible generic constructor produces SYSLIB1222 and falls back rather than emitting a constructor accessor.

Proposed fix

Emit the constructor extern inside a generic wrapper class (as the member accessors already do), e.g. __GenericCtorAccessor_<TypeFriendlyName><T>.__ctor_..., referenced through the closed type args at the call site.

Note

This issue (including the repro) was created with the help of GitHub Copilot.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions