Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NullReferenceException when calling a virtual Object method on a value type from inline function #8098

Open
ForNeVeR opened this issue Jan 5, 2020 · 7 comments
Labels
Area-Compiler-SRTP bugs in SRTP inference, resolution, witness passing, code gen Bug Impact-Medium (Internal MS Team use only) Describes an issue with moderate impact on existing code.
Milestone

Comments

@ForNeVeR
Copy link
Contributor

ForNeVeR commented Jan 5, 2020

Repro steps

Write the following F# program, and run it:

let inline toString (x: ^a) =
    (^a : (member ToString : unit -> string) x)

[<EntryPoint>]
let main argv =
    let s = toString 123
    printfn "%s" s
    0

Expected behavior

This program should print a string 123.

Actual behavior

This program throws a NullReferenceException:

Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at Program.main(String[] argv) in T:\Temp\ConsoleApp12\ConsoleApp12\Program.fs:line 6

It works on reference types though.

Known workarounds

Do not use an inline function there.

Related information

$ dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   3.1.100
 Commit:    cd82f021f4

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.18362
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\3.1.100\

Host (useful for support):
  Version: 3.1.0
  Commit:  65f04fb6db
@abelbraaksma
Copy link
Contributor

It's a bit of a guess, but I think this is by design. Your code doesn't do boxing, and the compiler doesn't automatically do that for statically resolved parameters. If you'd add box, I think it'll work as expected.

@ForNeVeR
Copy link
Contributor Author

ForNeVeR commented Jan 6, 2020

By design or not, I believe that's a very bad idea to compile code in such a way that's guaranteed to fail in runtime.

It should either compile well or generate a compile error, and should never do the strange thing. I'm okay with filing a language suggestion if it's preferred for this particular change.

@abelbraaksma
Copy link
Contributor

I believe that's a very bad idea to compile code in such a way that's guaranteed to fail in runtime.

I agree, but there are a lot of cases where it's impossible for a compiler to find out at compile time that it will always fail, let alone to decide whether your code was intentional, or not.

I mean, you can write let f x = x / 0, but the compiler won't complain, even though it'll always fail when called.

Though it's pretty hard to know as a programmer that you need boxing when you use inline generics to mix both reference and value types. There are special extensions in the compiler code itself to deal with this, but these are not available to normal code.

I believe there are several proposals underway, and SRTP itself gets a lot of attention for F# 5.0 at the moment, but I'm not sure any of those address this particular issue.

Interestingly, F#'s own string function uses boxing as well to prevent this error from occurring.

@ForNeVeR
Copy link
Contributor Author

ForNeVeR commented Feb 9, 2020

Is it really that hard to know which cases are "bad"? It seems they are simple (or at least let's say "straightforward"), if I understand correctly.

We could issue a compilation error / warning each time a member constraint (e.g. a (member ToString : unit -> string)) has to resolve to a virtual member if it is known that the statically resolved type in question (^a) is a value type.

Does it sound right? Are there any nonproblematic uses of this pattern (I mean, will such an error break any good, working code)? Any other downsides I don't see?

To me, it seems it's possible to detect all such uses of virtual methods on structs in compile time (because it's the compiler that emits the broken code), but I may be very wrong in this regard.

@abelbraaksma
Copy link
Contributor

Actually, I now think this is solvable, because virtual members on structs can only ever be the ones defined on object (and interfaces, but that's not possible without new language features). I believe they can be successfully run with a constrained callvirt, in which case no boxing takes place. But the compiler must issue the correct call.

@cartermp
Copy link
Contributor

Tagging #4924 and using this as a canonical sub-problem of that issue

#4924 (comment) for more information

@dsyme
Copy link
Contributor

dsyme commented Aug 31, 2020

Like everything related to SRTP resolution we should assess w.r.t. to the overall work in #6805

@dsyme dsyme added the Impact-Medium (Internal MS Team use only) Describes an issue with moderate impact on existing code. label Aug 31, 2020
@cartermp cartermp added this to the Backlog milestone Sep 1, 2020
@dsyme dsyme added Area-Compiler-SRTP bugs in SRTP inference, resolution, witness passing, code gen and removed Area-Compiler labels Mar 31, 2022
@vzarytovskii vzarytovskii moved this to Not Planned in F# Compiler and Tooling Jun 17, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compiler-SRTP bugs in SRTP inference, resolution, witness passing, code gen Bug Impact-Medium (Internal MS Team use only) Describes an issue with moderate impact on existing code.
Projects
Status: New
Development

No branches or pull requests

4 participants