Fsharp: The compiler does not recognize a user-provided IsByRefLike attribute as special.

Created on 13 Feb 2020  路  6Comments  路  Source: dotnet/fsharp

Let's take a look at the following code snippet:

namespace System.Runtime.CompilerServices

open System

[<AttributeUsage(AttributeTargets.All, AllowMultiple = false)>]
[<Sealed>]
type IsReadOnlyAttribute() =
    inherit System.Attribute()

[<AttributeUsage(AttributeTargets.All, AllowMultiple = false)>]
[<Sealed>]
type IsByRefLikeAttribute() =
    inherit System.Attribute()

namespace global

open System
open System.Runtime.CompilerServices

[<IsByRefLike; Struct>]
[<NoEquality; NoComparison>] // Not setting these attributes in .NET Core crashes with a different error.
type MyRefStruct(data: int) =
    member x.Data = data

module Program =
    [<EntryPoint>]
    let main _ =
        let x = MyRefStruct(521)
        Console.WriteLine(box x)
        0

We define our own System.Runtime.CompilerServices.IsByRefLikeAttribute type (as frameworks that do not bundle it do), and defined a simple ref struct containing an integer. In our entry point, we create such a ref struct on the stack, box it, and print it on the console.

Clearly, this is forbidden. However the compiler gladly allows it without shedding a tear.

If we run the program, the results vary depending on the framework.

  • On .NET Framework, the program successfully runs, printing MyRefStruct. It looks like that ref structs are a compiler's construct, and their embargo from the heap is not enforced, supposedly for compatibility reasons.

  • On .NET Core (at least in versions 2.1 and 3.1), the program crashes with an explanatory message:

Unhandled Exception: System.InvalidProgramException: Cannot create boxed ByRef-like values.
   at Program.main(String[] _arg1)

MyRefStruct is considered a ref struct by decompilers and .NET Core's runtime. The compiler however is not aware of that, and does not fail when a ref struct gets boxed.

It gets even worse. Let's change MyRefStruct into this:

[<IsByRefLike; Struct>]
[<NoEquality; NoComparison>]
type MyRefStruct(data: Span<int>) =
    member x.Data = data

// We can even remove the entry point

That was a ref struct holding another ref struct. Totally fine for the runtime. The compiler however, because she does not know that MyRefStruct is a ref struct, does not allow it to hold a Span saying that A type would store a byref typed value. This is not permitted by Common IL..


All these problems are caused because we used our own IsByRefLikeAttribute. If we remove the attribute definitions and use those provided by the framework, everything runs fine.

The problem is for those who target frameworks that are not bundling that attribute, like .NET Framework versions before 4.7.1, or worse, the widely used .NET Standard 2.0.

What is more, the fact that .NET Framework is _not_ enforcing those rules in runtime can lead to nasty and hard-to-diagnose bugs.

Area-Compiler Severity-Low bug

All 6 comments

On .NET Framework, the program successfully runs, printing MyRefStruct. It looks like that ref structs are a compiler's construct, and their embargo from the heap is not enforced, supposedly for compatibility reasons.

This isn't for compatibility reasons. .NET Framework has no understanding of the concept of byref-like structs. Code "works" because it's just a struct from the perspective of the desktop runtime, so they get boxed. This is fundamentally a .NET Core concept, not a .NET Framework concept, and it will never be ported to .NET Framework.

So while this is probably a bug, I don't think it's one that we'll consider worth fixing, since the scenario isn't really something we consider valid.

So while this is probably a bug, I don't think it's one that we'll consider worth fixing, since the scenario isn't really something we consider valid.

@cartermp I do not object on .NET Framework's behavior, that's not the bug I reported. The problem is that the compiler does not recognize that we created a ref struct with an attribute we defined ourselves.

IMHO, I think it's quite worth fixing. It affects .NET Standard 2.0 library authors who might incorrectly use a ref struct they defined. The compiler would allow ref structs to be boxed, which is significant enough IMHO.

And there is more. The compiler fails to report an error when our custom IsByRefLikeAttribute is used without [<Struct>], as it does with the system's attribute.

The essence of this bug report is that the compiler does not recognize the custom IsByRefLikeAttribute as special.

@teo-tsirpanis note that this: AttributeTargets.All is an issue; you need to specify Struct like the system attributes.

The problem is that the compiler does not recognize that we created a ref struct with an attribute we defined ourselves.

Correct, and this is what probably isn't worth addressing. We might accept a PR, but this isn't a priority for us. Byref-like structs are fundamentally a .NET Core concept, not a portable one.

@teo-tsirpanis note that this: AttributeTargets.All is an issue; you need to specify Struct like the system attributes.

I know, that's how it is defined in the RFC.

The same happens with either AttributeTargets.All or Struct. I also tested that it happens even with the attribute being internal (which is very good, as it won't pollute the public API of a library).

I will file a PR over there to fix it.

Was this page helpful?
0 / 5 - 0 ratings