dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.83k stars 773 forks source link

Trying to use `NativePtr.toVoidPtr` or `.ToPointer` causes `TypeLoadException` #9957

Open mvikto opened 3 years ago

mvikto commented 3 years ago

Repro steps

Provide the steps required to reproduce the problem:

  1. run code below
    
    #nowarn "9"

open FSharp.NativeInterop

[] let main argv = let numbers = [| 1..10 |] use v = fixed numbers let v' = NativePtr.toVoidPtr v printfn "void = %A" v' 0

[Project to reproduce the problem](https://github.com/dotnet/fsharp/files/5089306/repro.zip)

**Expected behavior**
`toVoidPtr` should just work and convert `nativeptr<int>` to `voidptr`

**Actual behavior**
It throws this when run:

Unhandled exception. System.TypeLoadException: The generic type 'Microsoft.FSharp.Core.FSharpFunc`2' was used with an invalid instantiation in assembly 'FSharp.Core, Version=4.7.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.



**Related information**
*  Operating system: Linux and Windows (I tried both)
* .NET Core 3.1
* Visual Studio Code
cartermp commented 3 years ago

I don't think this has to do with the conversion, but to do with the pretty-printing call with printfn. Same happens if it's %O for ToString(). Note that if you swap out for Console.WriteLine, you'll see a more helpful message:

Unhandled exception. System.InvalidOperationException: The given type cannot be boxed.

Which makes sense to me, since this is just some unverifiable pointer floating in the program at this point.

Was there a different usage that led to the same runtime exception?

mvikto commented 3 years ago

At some point in my code I decided to alias toVoidPtr with let like this:

#nowarn "9"

let toVoid = NativeInterop.NativePtr.toVoidPtr

[<EntryPoint>]
let main argv =
    let numbers = [| 1..10 |]
    use v = fixed numbers
    let v' = toVoid v
    0

If you call toVoid you'll get the same cryptic message, but if you do:

#nowarn "9"

let toVoid x = NativeInterop.NativePtr.toVoidPtr x

[<EntryPoint>]
let main argv =
    let numbers = [| 1..10 |]
    use v = fixed numbers
    let v' = toVoid v
    0

Then it works. Why is it like that?

cartermp commented 3 years ago

So this is ultimately by design, even if it's a little tricky. You can notice the difference in these two decompilations:

The first example needs to box a void pointer, which is a 💥 because that can't get boxed. So I think there's some missing compiler analysis here, since the first example should not compile. It's similar to our analysis for byref<'T>, where you cannot box it.

cartermp commented 3 years ago

@TIHan what are your thoughts?

TIHan commented 3 years ago

In regards to the error on this example:

#nowarn "9"

let toVoid = NativeInterop.NativePtr.toVoidPtr

[<EntryPoint>]
let main argv =
    let numbers = [| 1..10 |]
    use v = fixed numbers
    let v' = toVoid v
    0

This is by-design and should stay as is. You are partially applying the function which is not allowed if it contains byref or nativeptrs as part of the signature. I don't think it is worth it to add more heuristics to allow partial application with those types of parameters.

dsyme commented 3 years ago

Agreed, closing as by-design. Please consider re-opening at fslang-suggestions

cartermp commented 3 years ago

Note that I think this should stay open and not be at all a language suggestion. I labeled this as a compile enhancement so that we enhance the analysis, not that we allow this pattern. We currently throw an exception at runtime when we ought to not compile @TIHan @dsyme

abelbraaksma commented 3 years ago

Possibly related: https://github.com/dotnet/fsharp/issues/9994 (where, after some back and forth with myself, I found that generics and void* weren't exactly friends).

cartermp commented 3 years ago

Re-opening as this is not about allowing the coding pattern, but about ensuring we have the compiler analysis in place to disallow it