Fsharp: Returning a voidptr option throws at runtime

Created on 24 Aug 2020  路  10Comments  路  Source: dotnet/fsharp

When a function returns Some x where x is of type voidptr, the code compiles but throws at runtime.

Repro steps

Simple repro:

```f#
let check () =
let mutable tmp = IntPtr.Zero
let mutable handle = NativeInterop.NativePtr.toVoidPtr &&tmp
Some handle

[]
let main argv =
// this throws at runtime
check() |> ignore


A similar approach that also fails (but succeeds with `|> ignore`):

```f#
let check () =
    let mutable tmp = IntPtr.Zero
    let mutable handle = NativeInterop.NativePtr.toVoidPtr &&tmp
    handle

[<EntryPoint>]
let main argv =
    // this throws at runtime
    printfn "Handle: %A" (check())

Expected behavior

Either a compile error if this is illegal, or a valid result.

Actual behavior

The following error is output to the console:

Unhandled exception. System.TypeLoadException: The generic type 'Microsoft.FSharp.Core.FSharpOption`1' was used with an invalid instantiation in assembly 'FSharp.Core, Version=4.7.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
at Program.main(String[] argv)

Known workarounds

None. Though using your own DU allows this to run with |> ignore, it doesn't allow it to deconstruct. I.e.

This doesn't throw:

```f#
type Handle = Handle of voidptr

let check () =
let mutable tmp = IntPtr.Zero
let mutable handle = NativeInterop.NativePtr.toVoidPtr &&tmp
Handle handle

[]
let main argv =
check() |> ignore


But this does:

```f#
let main argv =
    // this throws at runtime
    match check() with
    | Handle x -> printfn "Handle: %A" x

Related information

Seen on any recent version of VS / F#.

Note that I don't know if voidptr is allowed as field. ~Probably not, which would make it understandable that this fails, though a compile-time error would be better.~ EDIT: see below, it works in several scenarios, just not with (v)options.

A possible use-case to allow this (if there're no CLS restrictions) is that returned void pointers are often NULL or valid, and options are a good way to wrap such returns.

Area-Compiler Severity-Medium bug

All 10 comments

Interestingly, this works:

```f#
[] // works with and without Struct, with or without multiple cases
type Handle = Handle of voidptr

let check () =
let mutable tmp = IntPtr.Zero
let mutable handle = NativeInterop.NativePtr.toVoidPtr &&tmp
Handle handle

[]
let main argv =
// this succeeds!
let y = match check() with Handle x -> NativeInterop.NativePtr.ofVoidPtr x
printfn "%A" y


But this doesn't

```f#
let check () =
    let mutable tmp = IntPtr.Zero
    let mutable handle = NativeInterop.NativePtr.toVoidPtr &&tmp
    Some handle  // fails with ValueSome and Some

[<EntryPoint>]
let main argv =
    // this throws at runtime
    let y = match check() with Some x -> NativeInterop.NativePtr.ofVoidPtr<int> x
    printfn "%A" y

Which suggests there's some issue with having voidptr in an option or voption. Though arguably, they should behave the same as any other DU, where this actually works. I can now confirm failing with build-in types:

  • Some x fails
  • Ok x and Error x fails
  • Choice1of2 x fails
  • When I use type Handle<'T> = Handle of 'T it also fails
  • QED: the restriction appears to be with generics

I also noticed that voidptr and nativeptr<_> don't implement ToString() and GetType() (or at least: F# disallows me to access it), which may very well be the reason that the printfn examples fail. EDIT: not so sure, isolated tests succeed.

Since voidptr translates to void* in the CLR type system, and since that type system forbids (afaik) to instantiate a generic type argument with a void*, it stands to reason to disallow this at compile time. The workaround will then always be to convert it to nativeint.

Well, the compiler does warn you:

Uses of this construct may result in the generation of unverifiable .NET IL code. This warning can be disabled using '--nowarn:9' or '#nowarn "9"'.

To be honest I think this is by-design, the warning is there. Perhaps it could be more explicit about "and your program may not run :-))"

This should be an error instead of a warning, since it's clearly a pattern that will never succeed in the first place.

Note that https://github.com/dotnet/fsharp/issues/9957 was closed by design when it was also suggested that we add compiler analysis (as there is no warning or error) similar to byref<'T> where you cannot box it.

"and your program may not run :-))"

:rofl: (let's add the smiley to the error)

This should be an error instead of a warning, since it's clearly a pattern that will never succeed in the first place.

I agree. At least the part where voidptr is used in a generic instantiation. The rest of the warnings should, of course, remain.

Note that C# disallows this properly: it doesn't compile: https://stackoverflow.com/questions/47643007/pointer-as-generic-type-argument

This isn't a feature improvement. This is a bug as all pointer types should not be used as generic instantiations.

@TIHan if this is consiered a but then https://github.com/dotnet/fsharp/issues/9957 should also be considered one, since it's the same root cause: lack of compiler analysis.

@dsyme @TIHan I suggest that both of you get on the same page here, since one of you claim this is a bug and another claims it is by design

ex: nativeptr<byte> compiles down to a System.IntPtr instead of a byte* in IL. Because of this, using nativeptr<byte> as a type argument will be valid at runtime.

voidptr on the other hand, compiles down to void* and not System.IntPtr - which explains why it explodes.

I wasn't aware that we could use nativeptr<_> as type arguments; I've always assumed them to be illegal. But, they are and they work at runtime. This explains why there are not any checks to cover something like voidptr.

I still think this is a bug because we are able to write code that cause type load exceptions when they can easily be prevented.

Was this page helpful?
0 / 5 - 0 ratings