Skip to content

Friend assemblies ("InternalsVisibleTo") can leak via automatic inlining cause runtime error #7422

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

Open
manofstick opened this issue Aug 18, 2019 · 5 comments
Labels
Area-Compiler-Checking Type checking, attributes and all aspects of logic checking Bug Impact-Medium (Internal MS Team use only) Describes an issue with moderate impact on existing code.
Milestone

Comments

@manofstick
Copy link
Contributor

Assembly A exposed it's internal to Assembly B via the InternalsVisibleTo attribute. Assembly B provides a simple method to Assembly C, but because it's a simple method, it is inlined by the f# compiler into Assembly C. This results in Runtime error.

Expected behavior

The FSharp compiler shouldn't inline methods that have calls to methods that have been exposed via InternalsVisibleTo.

Actual behavior

Runtime error.

Known workarounds

As per #5178, the use of [<MethodImpl(MethodImplOptions.NoInlining)>] stops the f# compiler from inling the method, but has the side effect that the JIT also stops inlining the call.

Related information

C:> fsc.exe
Microsoft (R) F# Compiler version 10.4.0 for F# 4.6
Copyright (c) Microsoft Corporation. All Rights Reserved.
@dsyme
Copy link
Contributor

dsyme commented Aug 19, 2019

I think this is a duplicate of #7110

@matthid
Copy link
Contributor

matthid commented Aug 19, 2019

@dsyme This talks about a runtime error, #7110 about a compile time error. #7110 is arguably by design but this looks like a faulty optimization by the compiler (=bug).
@manofstick A sample would obviously help :)

@dsyme
Copy link
Contributor

dsyme commented Aug 19, 2019

Ah yes.

The FSharp compiler shouldn't inline methods that have calls to methods that have been exposed via InternalsVisibleTo.

As mentioned in #7110, the intention is that the compiler doesn't inline in this case. So there may be a bug here

manofstick added a commit to manofstick/Cistern.Linq that referenced this issue Aug 20, 2019
@manofstick
Copy link
Contributor Author

Sample of this bug...

Clone the repository https://github.com/manofstick/Cistern.Linq

Go to branch fsharp_7422

Load solution from src directory. Set Cistern.Linq.Playground.FSharp as the startup project.

Execute and get the following error at runtime:

Unhandled Exception: System.MethodAccessException: Attempt by method 'Program.main(System.String[])' to access method 'Cistern.Linq.ChainLinq.Links.Identity`1<System.Int32>.get_Instance()' failed.
   at Program.main(String[] _arg1) in C:\src\Cistern.Linq\src\Cistern.Linq.Playground.FSharp\Program.fs:line 17

C:\Program Files\dotnet\dotnet.exe (process 7060) exited with code -1073741510.
Press any key to close this window . . .

To workaround, uncomment the [<MethodImpl(MethodImplOptions.NoInlining)>] in on unfold Linq.fs

@cartermp cartermp added this to the Backlog milestone Aug 6, 2020
@dsyme dsyme added the Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code. label Aug 31, 2020
@OkkeHendriks
Copy link

As promised in fscheck/FsCheck#549 I made a simple example repository to reproduce this issue. It is constructed the same way as the scenario which caused this issue in FsCheck.

When looking at the generated IL, it indeed emits the newobj calls instead of call statements as we saw in fscheck/FsCheck#549:

open assembly_A_optimized.referenceModulePublicA
open assembly_B_not_optimized.referenceModulePublicB

[<EntryPoint>]
let main _ =
    makeExceptionA ()
    makeExceptionAggressiveInliningA ()
    makeExceptionNoInliningA ()

    makeExceptionB ()
    makeExceptionAggressiveInliningB ()
    makeExceptionNoInliningB ()

    0

compiles into (optimization on for using program):

// Location: C:\git\inlined_internal\usage_optimized\bin\Debug\netcoreapp3.1\usage_optimized.dll
// Sequence point data from C:\git\inlined_internal\usage_optimized\bin\Debug\netcoreapp3.1\usage_optimized.pdb

.class public abstract sealed auto ansi
  ProgramOptimized
    extends [System.Runtime]System.Object
{
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags)
    = (01 00 07 00 00 00 00 00 ) // ........
    // int32(7) // 0x00000007

  .method public static int32
    main(
      string[] _arg1
    ) cil managed
  {
    .entrypoint
    .custom instance void [FSharp.Core]Microsoft.FSharp.Core.EntryPointAttribute::.ctor()
      = (01 00 00 00 )
    .maxstack 8

    // [6 5 - 6 22]
    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 [assembly_A_optimized]assembly_A_optimized.referenceModuleInternalA/TestExceptionA::.ctor()
    IL_0010: throw
    IL_0011: pop

    // [7 5 - 7 40]
    IL_0012: ldc.i4.0
    IL_0013: brfalse.s    IL_001d
    IL_0015: ldnull
    IL_0016: unbox.any    [FSharp.Core]Microsoft.FSharp.Core.Unit
    IL_001b: br.s         IL_0023
    IL_001d: newobj       instance void [assembly_A_optimized]assembly_A_optimized.referenceModuleInternalA/TestExceptionA::.ctor()
    IL_0022: throw
    IL_0023: pop

    // [8 5 - 8 29]
    IL_0024: call         !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_A_optimized]assembly_A_optimized.referenceModulePublicA::makeExceptionNoInliningA<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
    IL_0029: pop

    // [10 5 - 10 19]
    IL_002a: call         !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_B_not_optimized]assembly_B_not_optimized.referenceModulePublicB::makeExceptionB<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
    IL_002f: pop

    // [11 5 - 11 37]
    IL_0030: call         !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_B_not_optimized]assembly_B_not_optimized.referenceModulePublicB::makeExceptionAggressiveInliningB<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
    IL_0035: pop

    // [12 5 - 12 29]
    IL_0036: call         !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_B_not_optimized]assembly_B_not_optimized.referenceModulePublicB::makeExceptionNoInliningB<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
    IL_003b: pop

    // [14 5 - 14 6]
    IL_003c: ldc.i4.0
    IL_003d: ret

  } // end of method ProgramOptimized::main
} // end of class ProgramOptimized

and with optimization off for the using program, this compiles into:

// Location: C:\git\inlined_internal\usage_not_optimized\bin\Debug\netcoreapp3.1\usage_not_optimized.dll
// Sequence point data from C:\git\inlined_internal\usage_not_optimized\bin\Debug\netcoreapp3.1\usage_not_optimized.pdb

.class public abstract sealed auto ansi
  ProgramNotOptimized
    extends [System.Runtime]System.Object
{
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags)
    = (01 00 07 00 00 00 00 00 ) // ........
    // int32(7) // 0x00000007

  .method public static int32
    main(
      string[] _arg1
    ) cil managed
  {
    .entrypoint
    .custom instance void [FSharp.Core]Microsoft.FSharp.Core.EntryPointAttribute::.ctor()
      = (01 00 00 00 )
    .maxstack 3
    .locals init (
      [0] string[] V_0
    )

    IL_0000: ldarg.0      // _arg1
    IL_0001: stloc.0      // V_0

    // [7 5 - 7 22]
    IL_0002: call         !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_A_optimized]assembly_A_optimized.referenceModulePublicA::makeExceptionA<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
    IL_0007: pop

    // [8 5 - 8 40]
    IL_0008: call         !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_A_optimized]assembly_A_optimized.referenceModulePublicA::makeExceptionAggressiveInliningA<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
    IL_000d: pop

    // [9 5 - 9 32]
    IL_000e: call         !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_A_optimized]assembly_A_optimized.referenceModulePublicA::makeExceptionNoInliningA<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
    IL_0013: pop

    // [11 5 - 11 22]
    IL_0014: call         !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_B_not_optimized]assembly_B_not_optimized.referenceModulePublicB::makeExceptionB<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
    IL_0019: pop

    // [12 5 - 12 40]
    IL_001a: call         !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_B_not_optimized]assembly_B_not_optimized.referenceModulePublicB::makeExceptionAggressiveInliningB<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
    IL_001f: pop

    // [13 5 - 13 32]
    IL_0020: call         !!0/*class [FSharp.Core]Microsoft.FSharp.Core.Unit*/ [assembly_B_not_optimized]assembly_B_not_optimized.referenceModulePublicB::makeExceptionNoInliningB<class [FSharp.Core]Microsoft.FSharp.Core.Unit>()
    IL_0025: pop

    // [15 5 - 15 6]
    IL_0026: ldc.i4.0
    IL_0027: ret

  } // end of method ProgramNotOptimized::main
} // end of class ProgramNotOptimized

Thing to notice is that both the reference assembly and the 'using' program must be compiled with optimization on for this to occur. When the using program does not enable optimization all calls emit correct IL code. When the using program has optimization enabled only the function calls to the optimized reference assembly have inlined internal calls.

AlsoInternalsVisibleTo is not used in this example.

I hope this helps.

@dsyme dsyme added Impact-Medium (Internal MS Team use only) Describes an issue with moderate impact on existing code. Area-Compiler-Checking Type checking, attributes and all aspects of logic checking and removed Area-Compiler Impact-Low (Internal MS Team use only) Describes an issue with limited impact on existing code. labels Apr 4, 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-Checking Type checking, attributes and all aspects of logic checking 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

5 participants