VisualStudioMetadataReferenceManager.cs 14.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Host;
T
Tomas Matousek 已提交
15
using Microsoft.CodeAnalysis.PooledObjects;
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
using Microsoft.VisualStudio.Shell.Interop;
using Roslyn.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem
{
    /// <summary>
    /// Manages metadata references for VS projects. 
    /// </summary>
    /// <remarks>
    /// They monitor changes in the underlying files and provide snapshot references (subclasses of <see cref="PortableExecutableReference"/>) 
    /// that can be passed to the compiler. These snapshot references serve the underlying metadata blobs from a VS-wide storage, if possible, 
    /// from <see cref="ITemporaryStorageService"/>.
    /// </remarks>
    internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceService
    {
        private static readonly Guid s_IID_IMetaDataImport = new Guid("7DAC8207-D3AE-4c75-9B67-92801A497D44");
        private static readonly ConditionalWeakTable<Metadata, object> s_lifetimeMap = new ConditionalWeakTable<Metadata, object>();

        private readonly MetadataCache _metadataCache;
        private readonly ImmutableArray<string> _runtimeDirectories;

        private readonly ITemporaryStorageService _temporaryStorageService;

39 40 41 42 43 44 45 46 47 48 49 50 51
        internal IVsXMLMemberIndexService XmlMemberIndexService { get; }

        /// <summary>
        /// The smart open scope service. This can be null during shutdown when using the service might crash. Any
        /// use of this field or derived types should be synchronized with <see cref="_readerWriterLock"/> to ensure
        /// you don't grab the field and then use it while shutdown continues.
        /// </summary>
        private IVsSmartOpenScope SmartOpenScopeServiceOpt { get; set; }

        internal IVsFileChangeEx FileChangeService { get; }

        private readonly ReaderWriterLockSlim _readerWriterLock = new ReaderWriterLockSlim();

52 53 54 55 56
        internal VisualStudioMetadataReferenceManager(IServiceProvider serviceProvider, ITemporaryStorageService temporaryStorageService)
        {
            _metadataCache = new MetadataCache();
            _runtimeDirectories = GetRuntimeDirectories();

57 58
            XmlMemberIndexService = (IVsXMLMemberIndexService)serviceProvider.GetService(typeof(SVsXMLMemberIndexService));
            SmartOpenScopeServiceOpt = (IVsSmartOpenScope)serviceProvider.GetService(typeof(SVsSmartOpenScope));
59

60
            FileChangeService = (IVsFileChangeEx)serviceProvider.GetService(typeof(SVsFileChangeEx));
61 62
            _temporaryStorageService = temporaryStorageService;

63 64 65
            Debug.Assert(XmlMemberIndexService != null);
            Debug.Assert(SmartOpenScopeServiceOpt != null);
            Debug.Assert(FileChangeService != null);
66 67 68
            Debug.Assert(temporaryStorageService != null);
        }

69
        internal IEnumerable<ITemporaryStreamStorage> GetStorages(string fullPath, DateTime snapshotTimestamp)
70 71 72
        {
            var key = new FileKey(fullPath, snapshotTimestamp);
            // check existing metadata
C
CyrusNajmabadi 已提交
73
            if (_metadataCache.TryGetSource(key, out var source))
74
            {
C
CyrusNajmabadi 已提交
75
                if (source is RecoverableMetadataValueSource metadata)
76 77 78 79 80 81 82 83
                {
                    return metadata.GetStorages();
                }
            }

            return null;
        }

84
        public PortableExecutableReference CreateMetadataReference(string filePath, MetadataReferenceProperties properties)
85
        {
86
            return new VisualStudioPortableExecutableReference(this, properties, filePath, fileChangeTrackerOpt: null);
87 88 89 90 91 92 93 94 95 96 97 98
        }

        public void ClearCache()
        {
            _metadataCache.ClearCache();
        }

        private bool VsSmartScopeCandidate(string fullPath)
        {
            return _runtimeDirectories.Any(d => fullPath.StartsWith(d, StringComparison.OrdinalIgnoreCase));
        }

T
Tomas Matousek 已提交
99 100 101 102 103 104 105 106
        internal static IEnumerable<string> GetReferencePaths()
        {
            // TODO:
            // WORKAROUND: properly enumerate them
            yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5");
            yield return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0");
        }

107 108
        private static ImmutableArray<string> GetRuntimeDirectories()
        {
T
Tomas Matousek 已提交
109
            return GetReferencePaths().Concat(
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
                new string[]
                {
                    Environment.GetFolderPath(Environment.SpecialFolder.Windows),
                    Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),
                    Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
                    RuntimeEnvironment.GetRuntimeDirectory()
                }).Select(FileUtilities.NormalizeDirectoryPath).ToImmutableArray();
        }

        /// <exception cref="IOException"/>
        /// <exception cref="BadImageFormatException" />
        internal Metadata GetMetadata(string fullPath, DateTime snapshotTimestamp)
        {
            var key = new FileKey(fullPath, snapshotTimestamp);
            // check existing metadata
C
CyrusNajmabadi 已提交
125
            if (_metadataCache.TryGetMetadata(key, out var metadata))
126 127 128 129
            {
                return metadata;
            }

C
CyrusNajmabadi 已提交
130
            if (VsSmartScopeCandidate(key.FullPath) && TryCreateAssemblyMetadataFromMetadataImporter(key, out var newMetadata))
131 132 133 134 135 136 137 138 139 140 141
            {
                if (!_metadataCache.TryGetOrAddMetadata(key, new WeakConstantValueSource<AssemblyMetadata>(newMetadata), out metadata))
                {
                    newMetadata.Dispose();
                }

                return metadata;
            }

            // use temporary storage
            var storages = new List<ITemporaryStreamStorage>();
142
            newMetadata = CreateAssemblyMetadataFromTemporaryStorage(key, storages);
143

C
Charles Stoner 已提交
144
            // don't dispose assembly metadata since it shares module metadata
145 146 147 148 149 150 151 152 153 154
            if (!_metadataCache.TryGetOrAddMetadata(key, new RecoverableMetadataValueSource(newMetadata, storages, s_lifetimeMap), out metadata))
            {
                newMetadata.Dispose();
            }

            return metadata;
        }

        /// <exception cref="IOException"/>
        /// <exception cref="BadImageFormatException" />
155
        private AssemblyMetadata CreateAssemblyMetadataFromTemporaryStorage(FileKey fileKey, List<ITemporaryStreamStorage> storages)
156
        {
157 158
            var moduleMetadata = CreateModuleMetadataFromTemporaryStorage(fileKey, storages);
            return CreateAssemblyMetadata(fileKey, moduleMetadata, storages, CreateModuleMetadataFromTemporaryStorage);
159 160
        }

161
        private ModuleMetadata CreateModuleMetadataFromTemporaryStorage(FileKey moduleFileKey, List<ITemporaryStreamStorage> storages)
162
        {
C
CyrusNajmabadi 已提交
163
            GetStorageInfoFromTemporaryStorage(moduleFileKey, out var storage, out var stream, out var pImage);
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238

            var metadata = ModuleMetadata.CreateFromMetadata(pImage, (int)stream.Length);

            // first time, the metadata is created. tie lifetime.
            s_lifetimeMap.Add(metadata, stream);

            // hold onto storage if requested
            if (storages != null)
            {
                storages.Add(storage);
            }

            return metadata;
        }

        private void GetStorageInfoFromTemporaryStorage(FileKey moduleFileKey, out ITemporaryStreamStorage storage, out Stream stream, out IntPtr pImage)
        {
            int size;
            using (var copyStream = SerializableBytes.CreateWritableStream())
            {
                // open a file and let it go as soon as possible
                using (var fileStream = FileUtilities.OpenRead(moduleFileKey.FullPath))
                {
                    var headers = new PEHeaders(fileStream);

                    var offset = headers.MetadataStartOffset;
                    size = headers.MetadataSize;

                    // given metadata contains no metadata info.
                    // throw bad image format exception so that we can show right diagnostic to user.
                    if (size <= 0)
                    {
                        throw new BadImageFormatException();
                    }

                    StreamCopy(fileStream, copyStream, offset, size);
                }

                // copy over the data to temp storage and let pooled stream go
                storage = _temporaryStorageService.CreateTemporaryStreamStorage(CancellationToken.None);

                copyStream.Position = 0;
                storage.WriteStream(copyStream);
            }

            // get stream that owns direct access memory
            stream = storage.ReadStream(CancellationToken.None);

            // stream size must be same as what metadata reader said the size should be.
            Contract.ThrowIfFalse(stream.Length == size);

            // under VS host, direct access should be supported
            var directAccess = (ISupportDirectMemoryAccess)stream;
            pImage = directAccess.GetPointer();
        }

        private void StreamCopy(Stream source, Stream destination, int start, int length)
        {
            source.Position = start;

            var buffer = SharedPools.ByteArray.Allocate();

            var read = 0;
            var left = length;
            while ((read = source.Read(buffer, 0, Math.Min(left, buffer.Length))) != 0)
            {
                destination.Write(buffer, 0, read);
                left -= read;
            }

            SharedPools.ByteArray.Free(buffer);
        }

        /// <exception cref="IOException"/>
        /// <exception cref="BadImageFormatException" />
239
        private bool TryCreateAssemblyMetadataFromMetadataImporter(FileKey fileKey, out AssemblyMetadata metadata)
240
        {
C
CyrusNajmabadi 已提交
241
            metadata = default;
242

243
            var manifestModule = TryCreateModuleMetadataFromMetadataImporter(fileKey);
244 245 246 247 248 249 250 251 252
            if (manifestModule == null)
            {
                return false;
            }

            metadata = CreateAssemblyMetadata(fileKey, manifestModule, null, CreateModuleMetadata);
            return true;
        }

253
        private ModuleMetadata TryCreateModuleMetadataFromMetadataImporter(FileKey moduleFileKey)
254
        {
C
CyrusNajmabadi 已提交
255
            if (!TryGetFileMappingFromMetadataImporter(moduleFileKey, out var info, out var pImage, out var length))
256 257 258 259
            {
                return null;
            }

260
            Debug.Assert(pImage != IntPtr.Zero, "Base address should not be zero if GetFileFlatMapping call succeeded.");
261 262 263 264 265 266 267 268 269

            var metadata = ModuleMetadata.CreateFromImage(pImage, (int)length);
            s_lifetimeMap.Add(metadata, info);

            return metadata;
        }

        private ModuleMetadata CreateModuleMetadata(FileKey moduleFileKey, List<ITemporaryStreamStorage> storages)
        {
270
            var metadata = TryCreateModuleMetadataFromMetadataImporter(moduleFileKey);
271 272 273
            if (metadata == null)
            {
                // getting metadata didn't work out through importer. fallback to shadow copy one
274
                metadata = CreateModuleMetadataFromTemporaryStorage(moduleFileKey, storages);
275 276 277 278 279 280 281
            }

            return metadata;
        }

        private bool TryGetFileMappingFromMetadataImporter(FileKey fileKey, out IMetaDataInfo info, out IntPtr pImage, out long length)
        {
282 283 284 285 286 287 288
            // We might not be able to use COM services to get this if VS is shutting down. We'll synchronize to make sure this
            // doesn't race against 
            using (_readerWriterLock.DisposableRead())
            {
                // here, we don't care about timestamp since all those bits should be part of Fx. and we assume that 
                // it won't be changed in the middle of VS running.
                var fullPath = fileKey.FullPath;
289

C
CyrusNajmabadi 已提交
290 291 292
                info = default;
                pImage = default;
                length = default;
293

294 295 296 297
                if (SmartOpenScopeServiceOpt == null)
                {
                    return false;
                }
298

C
CyrusNajmabadi 已提交
299
                if (ErrorHandler.Failed(SmartOpenScopeServiceOpt.OpenScope(fullPath, (uint)CorOpenFlags.ReadOnly, s_IID_IMetaDataImport, out var ppUnknown)))
300 301 302
                {
                    return false;
                }
303

304 305 306 307 308 309
                info = ppUnknown as IMetaDataInfo;
                if (info == null)
                {
                    return false;
                }

C
CyrusNajmabadi 已提交
310
                return ErrorHandler.Succeeded(info.GetFileMapping(out pImage, out length, out var mappingType)) && mappingType == CorFileMapping.Flat;
311
            }
312 313 314 315 316 317
        }

        /// <exception cref="IOException"/>
        /// <exception cref="BadImageFormatException" />
        private AssemblyMetadata CreateAssemblyMetadata(
            FileKey fileKey, ModuleMetadata manifestModule, List<ITemporaryStreamStorage> storages,
318
            Func<FileKey, List<ITemporaryStreamStorage>, ModuleMetadata> moduleMetadataFactory)
319
        {
320
            var moduleBuilder = ArrayBuilder<ModuleMetadata>.GetInstance();
321 322 323 324

            string assemblyDir = null;
            foreach (string moduleName in manifestModule.GetModuleNames())
            {
325
                if (moduleBuilder.Count == 0)
326 327 328 329 330 331
                {
                    moduleBuilder.Add(manifestModule);
                    assemblyDir = Path.GetDirectoryName(fileKey.FullPath);
                }

                var moduleFileKey = FileKey.Create(PathUtilities.CombineAbsoluteAndRelativePaths(assemblyDir, moduleName));
332
                var metadata = moduleMetadataFactory(moduleFileKey, storages);
333 334 335 336

                moduleBuilder.Add(metadata);
            }

C
CyrusNajmabadi 已提交
337 338 339 340 341 342 343
            if (moduleBuilder.Count == 0)
            {
                moduleBuilder.Add(manifestModule);
            }

            return AssemblyMetadata.Create(
                moduleBuilder.ToImmutableAndFree());
344
        }
345 346 347 348 349 350 351 352 353 354

        public void DisconnectFromVisualStudioNativeServices()
        {
            using (_readerWriterLock.DisposableWrite())
            {
                // IVsSmartOpenScope can't be used as we shutdown, and this is pretty commonly hit according to 
                // Windows Error Reporting as we try creating metadata for compilations.
                SmartOpenScopeServiceOpt = null;
            }
        }
355
    }
T
Tomas Matousek 已提交
356
}