diff --git a/fcs/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj b/fcs/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj index 88236b9dffc..fd8033a7a2d 100644 --- a/fcs/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj +++ b/fcs/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj @@ -1,4 +1,4 @@ - + $(MSBuildProjectDirectory)\..\..\src @@ -26,6 +26,9 @@ Common.fs + + AssemblyReaderShim.fs + EditorTests.fs diff --git a/src/absil/ilread.fs b/src/absil/ilread.fs index 7dc2a49eee1..1d1b8f75408 100644 --- a/src/absil/ilread.fs +++ b/src/absil/ilread.fs @@ -3941,11 +3941,20 @@ type ILReaderOptions = metadataOnly: MetadataOnlyFlag tryGetMetadataSnapshot: ILReaderTryGetMetadataSnapshot } + +type ILModuleReader = + abstract ILModuleDef : ILModuleDef + abstract ILAssemblyRefs : ILAssemblyRef list + + /// ILModuleReader objects only need to be explicitly disposed if memory mapping is used, i.e. reduceMemoryUsage = false + inherit System.IDisposable + + [] -type ILModuleReader(ilModule: ILModuleDef, ilAssemblyRefs: Lazy, dispose: unit -> unit) = - member x.ILModuleDef = ilModule - member x.ILAssemblyRefs = ilAssemblyRefs.Force() - interface IDisposable with +type ILModuleReaderImpl(ilModule: ILModuleDef, ilAssemblyRefs: Lazy, dispose: unit -> unit) = + interface ILModuleReader with + member x.ILModuleDef = ilModule + member x.ILAssemblyRefs = ilAssemblyRefs.Force() member x.Dispose() = dispose() // ++GLOBAL MUTABLE STATE (concurrency safe via locking) @@ -3986,7 +3995,7 @@ let tryMemoryMapWholeFile opts fileName = let OpenILModuleReaderFromBytes fileName bytes opts = let pefile = ByteFile(fileName, bytes) :> BinaryFile let ilModule, ilAssemblyRefs, pdb = openPE (fileName, pefile, opts.pdbDirPath, (opts.reduceMemoryUsage = ReduceMemoryFlag.Yes), opts.ilGlobals, true) - new ILModuleReader(ilModule, ilAssemblyRefs, (fun () -> ClosePdbReader pdb)) + new ILModuleReaderImpl(ilModule, ilAssemblyRefs, (fun () -> ClosePdbReader pdb)) :> ILModuleReader let OpenILModuleReader fileName opts = // Pseudo-normalize the paths. @@ -4042,18 +4051,18 @@ let OpenILModuleReader fileName opts = createByteFileChunk opts fullPath (Some (metadataPhysLoc, metadataSize)) let ilModule, ilAssemblyRefs = openPEMetadataOnly (fullPath, peinfo, pectxtEager, pevEager, mdfile, reduceMemoryUsage, opts.ilGlobals) - new ILModuleReader(ilModule, ilAssemblyRefs, ignore) + new ILModuleReaderImpl(ilModule, ilAssemblyRefs, ignore) else // If we are not doing metadata-only, then just go ahead and read all the bytes and hold them either strongly or weakly // depending on the heuristic let pefile = createByteFileChunk opts fullPath None let ilModule, ilAssemblyRefs, _pdb = openPE (fullPath, pefile, None, reduceMemoryUsage, opts.ilGlobals, false) - new ILModuleReader(ilModule, ilAssemblyRefs, ignore) + new ILModuleReaderImpl(ilModule, ilAssemblyRefs, ignore) if keyOk then ilModuleReaderCacheLock.AcquireLock (fun ltok -> ilModuleReaderCache.Put(ltok, key, ilModuleReader)) - ilModuleReader + ilModuleReader :> ILModuleReader else // This case is primarily used in fsc.exe. @@ -4075,13 +4084,25 @@ let OpenILModuleReader fileName opts = disposer, pefile let ilModule, ilAssemblyRefs, pdb = openPE (fullPath, pefile, opts.pdbDirPath, reduceMemoryUsage, opts.ilGlobals, false) - let ilModuleReader = new ILModuleReader(ilModule, ilAssemblyRefs, (fun () -> ClosePdbReader pdb)) + let ilModuleReader = new ILModuleReaderImpl(ilModule, ilAssemblyRefs, (fun () -> ClosePdbReader pdb)) // Readers with PDB reader disposal logic don't go in the cache. Note the PDB reader is only used in static linking. if keyOk && opts.pdbDirPath.IsNone then ilModuleReaderCacheLock.AcquireLock (fun ltok -> ilModuleReaderCache.Put(ltok, key, ilModuleReader)) - ilModuleReader + ilModuleReader :> ILModuleReader + +[] +module Shim = + open Microsoft.FSharp.Compiler.Lib + type IAssemblyReader = + abstract GetILModuleReader: filename: string * readerOptions: ILReaderOptions -> ILModuleReader + [] + type DefaultAssemblyReader() = + interface IAssemblyReader with + member __.GetILModuleReader(filename, readerOptions) = + OpenILModuleReader filename readerOptions + let mutable AssemblyReader = DefaultAssemblyReader() :> IAssemblyReader diff --git a/src/absil/ilread.fsi b/src/absil/ilread.fsi index 2c05d9299a8..c548447444a 100644 --- a/src/absil/ilread.fsi +++ b/src/absil/ilread.fsi @@ -38,12 +38,12 @@ type ILReaderMetadataSnapshot = (obj * nativeint * int) type ILReaderTryGetMetadataSnapshot = (* path: *) string * (* snapshotTimeStamp: *) System.DateTime -> ILReaderMetadataSnapshot option [] -type internal MetadataOnlyFlag = Yes | No +type MetadataOnlyFlag = Yes | No [] -type internal ReduceMemoryFlag = Yes | No +type ReduceMemoryFlag = Yes | No -type internal ILReaderOptions = +type ILReaderOptions = { pdbDirPath: string option ilGlobals: ILGlobals @@ -64,16 +64,17 @@ type internal ILReaderOptions = /// and from which we can read the metadata. Only used when metadataOnly=true. tryGetMetadataSnapshot: ILReaderTryGetMetadataSnapshot } + /// Represents a reader of the metadata of a .NET binary. May also give some values (e.g. IL code) from the PE file /// if it was provided. -[] -type internal ILModuleReader = - member ILModuleDef : ILModuleDef - member ILAssemblyRefs : ILAssemblyRef list +type ILModuleReader = + abstract ILModuleDef: ILModuleDef + abstract ILAssemblyRefs: ILAssemblyRef list /// ILModuleReader objects only need to be explicitly disposed if memory mapping is used, i.e. reduceMemoryUsage = false - interface System.IDisposable - + inherit System.IDisposable + + /// Open a binary reader, except first copy the entire contents of the binary into /// memory, close the file and ensure any subsequent reads happen from the in-memory store. /// PDB files may not be read with this option. @@ -89,4 +90,16 @@ type Statistics = mutable weakByteFileCount : int mutable byteFileCount : int } -val GetStatistics : unit -> Statistics \ No newline at end of file +val GetStatistics : unit -> Statistics + +[] +module Shim = + + type IAssemblyReader = + abstract GetILModuleReader: filename: string * readerOptions: ILReaderOptions -> ILModuleReader + + [] + type DefaultAssemblyReader = + interface IAssemblyReader + + val mutable AssemblyReader: IAssemblyReader diff --git a/src/fsharp/CompileOps.fs b/src/fsharp/CompileOps.fs index 094f07e73f2..5f604c25c89 100644 --- a/src/fsharp/CompileOps.fs +++ b/src/fsharp/CompileOps.fs @@ -2623,14 +2623,7 @@ type TcConfigBuilder = ri, fileNameOfPath ri, ILResourceAccess.Public -let OpenILBinary(filename, reduceMemoryUsage, ilGlobalsOpt, pdbDirPath, shadowCopyReferences, tryGetMetadataSnapshot) = - let ilGlobals = - // ILScopeRef.Local can be used only for primary assembly (mscorlib or System.Runtime) itself - // Remaining assemblies should be opened using existing ilGlobals (so they can properly locate fundamental types) - match ilGlobalsOpt with - | None -> mkILGlobals ILScopeRef.Local - | Some g -> g - +let OpenILBinary(filename, reduceMemoryUsage, ilGlobals, pdbDirPath, shadowCopyReferences, tryGetMetadataSnapshot) = let opts : ILReaderOptions = { ilGlobals = ilGlobals metadataOnly = MetadataOnlyFlag.Yes @@ -2651,7 +2644,7 @@ let OpenILBinary(filename, reduceMemoryUsage, ilGlobalsOpt, pdbDirPath, shadowCo ignore shadowCopyReferences #endif filename - OpenILModuleReader location opts + AssemblyReader.GetILModuleReader(location, opts) #if DEBUG [] @@ -2781,12 +2774,13 @@ type TcConfig private (data : TcConfigBuilder, validate:bool) = do if ((primaryAssemblyExplicitFilenameOpt.IsSome || fslibExplicitFilenameOpt.IsSome) && data.framework) then error(Error(FSComp.SR.buildExplicitCoreLibRequiresNoFramework("--noframework"), rangeStartup)) + let ilGlobals = mkILGlobals ILScopeRef.Local let clrRootValue, targetFrameworkVersionValue = match primaryAssemblyExplicitFilenameOpt with | Some(primaryAssemblyFilename) -> let filename = ComputeMakePathAbsolute data.implicitIncludeDir primaryAssemblyFilename try - use ilReader = OpenILBinary(filename, data.reduceMemoryUsage, None, None, data.shadowCopyReferences, data.tryGetMetadataSnapshot) + use ilReader = OpenILBinary(filename, data.reduceMemoryUsage, ilGlobals, None, data.shadowCopyReferences, data.tryGetMetadataSnapshot) let ilModule = ilReader.ILModuleDef match ilModule.ManifestOfAssembly.Version with | Some(v1, v2, _, _) -> @@ -2819,7 +2813,7 @@ type TcConfig private (data : TcConfigBuilder, validate:bool) = let filename = ComputeMakePathAbsolute data.implicitIncludeDir fslibFilename if fslibReference.ProjectReference.IsNone then try - use ilReader = OpenILBinary(filename, data.reduceMemoryUsage, None, None, data.shadowCopyReferences, data.tryGetMetadataSnapshot) + use ilReader = OpenILBinary(filename, data.reduceMemoryUsage, ilGlobals, None, data.shadowCopyReferences, data.tryGetMetadataSnapshot) () with e -> error(Error(FSComp.SR.buildErrorOpeningBinaryFile(filename, e.Message), rangeStartup)) @@ -4143,7 +4137,17 @@ type TcImports(tcConfigP:TcConfigProvider, initialResolutions:TcAssemblyResoluti None else None - let ilILBinaryReader = OpenILBinary(filename, tcConfig.reduceMemoryUsage, ilGlobalsOpt, pdbDirPath, tcConfig.shadowCopyReferences, tcConfig.tryGetMetadataSnapshot) + + let ilGlobals = + // ILScopeRef.Local can be used only for primary assembly (mscorlib or System.Runtime) itself + // Remaining assemblies should be opened using existing ilGlobals (so they can properly locate fundamental types) + match ilGlobalsOpt with + | None -> mkILGlobals ILScopeRef.Local + | Some g -> g + + let ilILBinaryReader = + OpenILBinary (filename, tcConfig.reduceMemoryUsage, ilGlobals, pdbDirPath, tcConfig.shadowCopyReferences, tcConfig.tryGetMetadataSnapshot) + tcImports.AttachDisposeAction(fun _ -> (ilILBinaryReader :> IDisposable).Dispose()) ilILBinaryReader.ILModuleDef, ilILBinaryReader.ILAssemblyRefs with e -> diff --git a/tests/service/AssemblyReaderShim.fs b/tests/service/AssemblyReaderShim.fs new file mode 100644 index 00000000000..e7fd0a48ba3 --- /dev/null +++ b/tests/service/AssemblyReaderShim.fs @@ -0,0 +1,33 @@ +#if INTERACTIVE +#r "../../debug/fcs/net45/FSharp.Compiler.Service.dll" // note, run 'build fcs debug' to generate this, this DLL has a public API so can be used from F# Interactive +#r "../../packages/NUnit.3.5.0/lib/net45/nunit.framework.dll" +#load "FsUnit.fs" +#load "Common.fs" +#else +module FSharp.Compiler.Service.Tests.AssemblyReaderShim +#endif + +open FSharp.Compiler.Service.Tests.Common +open FsUnit +open Microsoft.FSharp.Compiler.AbstractIL.ILBinaryReader +open NUnit.Framework + +[] +let ``Assembly reader shim gets requests`` () = + let defaultReader = Shim.AssemblyReader + let mutable gotRequest = false + let reader = + { new IAssemblyReader with + member x.GetILModuleReader(path, opts) = + gotRequest <- true + defaultReader.GetILModuleReader(path, opts) + } + Shim.AssemblyReader <- reader + let source = """ +module M +let x = 123 +""" + + let fileName, options = Common.mkTestFileAndOptions source [| |] + Common.checker.ParseAndCheckFileInProject(fileName, 0, source, options) |> Async.RunSynchronously |> ignore + gotRequest |> should be True