Skip to content

No compiler error for instance members on static class; produces invalid IL & causes runtime error #13147

@brianrourkeboll

Description

@brianrourkeboll

Repro steps

  1. Define a static class using the combination of SealedAttribute and AbstractClassAttribute.
  2. Add fields, instance members, even constructors:1
    a. Fields (mutable or not), properties, methods, indexers.
    b. Abstract, concrete, override...

Examples

E.g., this

// This compiles and produces nonsense IL.
[<Sealed; AbstractClass>]
type T =
    abstract A : int
    abstract B : int with get, set
    abstract C : i:int -> int
    abstract D : i:int -> int
    default _.D i = i + 3
    member _.E = 3
    val F : int
    val mutable G : int
    member _.H (i, j) = i + j
    member _.Item with get i = 3 and set i value = ()
    override _.ToString () = "🙃"
    new () = { F = 3; G = 3 }
    new (x, y) = { F = x; G = y }

generates this (SharpLab):

[Serializable]
[Sealed]
[AbstractClass]
[CompilationMapping(SourceConstructFlags.ObjectType)]
public static class T
{
    internal int F@;

    public int G;

    [CompilationMapping(SourceConstructFlags.Field, 0)]
    public int F
    {
        get
        {
            return F@;
        }
    }

    public abstract override int A { get; }

    public abstract override int B { get; set; }

    public int E
    {
        get
        {
            return 3;
        }
    }

    public int this[object i]
    {
        get
        {
            return 3;
        }
        set
        {
        }
    }

    public abstract override int C(int i);

    public override int D(int i)
    {
        return i + 3;
    }

    public int H(int i, int j)
    {
        return i + j;
    }

    public override string ToString()
    {
        return "\ud83d\ude43";
    }

    public T()
    {
        F@ = 3;
        G = 3;
    }

    public T(int x, int y)
    {
        F@ = x;
        G = y;
    }
}

There is also no error when making a class with a primary constructor static (SharpLab):

// Just throw [<Sealed; AbstractClass>] on the default
// SharpLab class and it still compiles!
[<Sealed; AbstractClass>]
type C() =
    member _.M() = ()

There is no error for fieldless single-case unions, either (SharpLab):2345

[<Sealed; AbstractClass>]
type U = U
let u = U

There is likewise no warning or error when adding SealedAttribute to a struct fieldless single-case union (SharpLab):5

[<Sealed; Struct>]
type U = U

Expected behavior

The code should not compile.

Actual behavior

The code compiles, and calling it results in a System.BadImageFormatException with the message "Bad IL format." at runtime.6

> Unchecked.defaultof<T>.A;;
System.BadImageFormatException: Bad IL format.
   at <StartupCode$FSI_0007>.$FSI_0007.main@()
Stopped due to error

Known workarounds

Don't try weird things.

Related information

  • .NET SDK 6.0.300.

Footnotes

  1. Not auto-properties: using member val correctly gives FS3133, presumably because the check for a primary constructor is separate. member val is still let through if you do have a primary constructor (SharpLab).

  2. It looks like the compiler ignores the combination of Sealed and AbstractClass here and just adds the AbstractClass attribute without actually making the class static.

  3. The compiler correctly gives FS0942 and FS0939 when a field is added (SharpLab) or a second case is added (SharpLab).

  4. The compiler does disallow this for records with FS0942 and FS0939 (SharpLab).

  5. If you add a field or a bar | to either of these, the compiler does give you FS0942 and/or FS0939. 2

  6. The compiler does disallow calling a constructor added to a static class with FS0759.

Metadata

Metadata

Assignees

No one assigned

    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.good first issuehelp wanted

    Type

    No type

    Projects

    Status

    Done

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions