Skip to content

Unable to access constructor of DiscardException with optimized build. #549

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

Closed
OkkeHendriks opened this issue Feb 17, 2021 · 3 comments
Closed

Comments

@OkkeHendriks
Copy link

OkkeHendriks commented Feb 17, 2021

When running the following test in a visual studio project which is unoptimized the test raises the expected DiscardException.

[<Xunit.Fact>]
let testUnoptimized () =
    FsCheck.Prop.discard ()
    true

When running the exact same test in a project which is optimized, the following unexpected exception is raised:

  Message: 
    System.MethodAccessException : Attempt by method 'test.testOptimized()' to access method 'FsCheck.Testable+DiscardException..ctor()' failed.
  Stack Trace: 
    test.testOptimized() line 6

image

This is not what happens when I run the FsCheck.Test project tests. There, Prop.discard () works as expected and as far as I can tell this is also with optimization on.

The above example, in both a project which has optimization enabled and in one with optimization disabled, is available here:
https://github.com/OkkeHendriks/fscheck_discard_issue

Does someone see my stupid mistake or is more going on?

Version info:
I tried to use the same .NET framework version (4.5.2) and other assemblies/packages as in the FsCheck.Test project on master.

$ dotnet --info
.NET SDK (reflecting any global.json):
 Version:   5.0.102
 Commit:    71365b4d42

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

Host (useful for support):
  Version: 5.0.2
  Commit:  cb5f173b96

.NET SDKs installed:
  2.1.811 [C:\Program Files\dotnet\sdk]
  3.1.101 [C:\Program Files\dotnet\sdk]
  5.0.102 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
  Microsoft.AspNetCore.All 2.1.23 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.24 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.23 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.24 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.1.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.1.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 3.1.11 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 5.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.1.23 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.24 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.2.8 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.11 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 5.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.WindowsDesktop.App 3.1.1 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 3.1.11 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
  Microsoft.WindowsDesktop.App 5.0.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

To install additional .NET runtimes or SDKs:
  https://aka.ms/dotnet-download

Microsoft (R) F# Interactive version 11.0.0.0 for F# 5.0
Copyright (c) Microsoft Corporation. All Rights Reserved.
@kurtschelfthout
Copy link
Member

My guess is that this is an F# compiler bug, somewhat similar to dotnet/fsharp#7422 . It looks to me like fsc's inlining optimization is too aggressive. I bet that in your optimized build it inlines the discard() method in your test assembly, which then can't execute because the DiscardException is internal in FsCheck.dll.

It works in FsCheck.Test because FsCheck has an InternalsVisibileToAttribute for it...so we can test some internals ;).

You can probably confirm by disassembling the test assembly and visually checking my inlining guess. Adding [<MethodImpl(MethodImplOptions.NoInlining)>] to Prop.discard should also fix it, and I'd consider that a reasonable workaround.

If that all checks out and you fancy it, worth creating a small repro and submitting an issue to the fine folks over at https://github.com/dotnet/fsharp too...

@OkkeHendriks
Copy link
Author

First of all, thanks for the quick reply.

Seems like it is indeed inlined in the optimized build:

.method public static bool
    testOptimizedFact() cil managed
  {
    .custom instance void [xunit.core]Xunit.FactAttribute::.ctor()
      = (01 00 00 00 )
    .maxstack 8

    // [8 5 - 8 28]
    IL_0000: ldc.i4.0
    IL_0001: brfalse.s    IL_000b
    IL_0003: ldnull
    IL_0004: unbox.any    [FSharp.Core]Microsoft.FSharp.Core.Unit
    IL_0009: br.s         IL_0011
    IL_000b: newobj       instance void [FsCheck]FsCheck.Testable/DiscardException::.ctor()
    IL_0010: throw
    IL_0011: pop

    // [9 5 - 9 9]
    IL_0012: ldc.i4.1
    IL_0013: ret

  } // end of method test::testOptimizedFact

And not in the unoptimized build:

  .method public static bool
    testUnoptimizedFact() cil managed
  {
    .custom instance void [xunit.core]Xunit.FactAttribute::.ctor()
      = (01 00 00 00 )
    .maxstack 8

    // [8 5 - 8 28]
    IL_0000: call         !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [FsCheck]FsCheck.Prop::Discard<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
    IL_0005: pop

    // [9 5 - 9 9]
    IL_0006: ldc.i4.1
    IL_0007: ret

  } // end of method test::testUnoptimizedFact

I tried with a build of FsCheck with the following, and this indeed works as intended, I created a PR for this, see #550.

    [<CompiledName("Discard")>]
    [<MethodImpl(MethodImplOptions.NoInlining)>]
    let discard() = raise DiscardException

I will have a look later at creating a simple example repo for the underlying issue and add it to the discussion at dotnet/fsharp#7422.

@kurtschelfthout
Copy link
Member

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants