forked from dotnet/roslyn
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathVisualStudioMetadataReferenceManager.cs
354 lines (289 loc) · 14.9 KB
/
VisualStudioMetadataReferenceManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
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;
using Microsoft.CodeAnalysis.PooledObjects;
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("7DAC8207-D3AE-4c75-9B67-92801A497D44");
private static readonly ConditionalWeakTable<Metadata, object> s_lifetimeMap = new();
private readonly MetadataCache _metadataCache = new();
private readonly ImmutableArray<string> _runtimeDirectories;
private readonly TemporaryStorageService _temporaryStorageService;
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();
internal VisualStudioMetadataReferenceManager(
IServiceProvider serviceProvider,
TemporaryStorageService temporaryStorageService)
{
_runtimeDirectories = GetRuntimeDirectories();
XmlMemberIndexService = (IVsXMLMemberIndexService)serviceProvider.GetService(typeof(SVsXMLMemberIndexService));
Assumes.Present(XmlMemberIndexService);
SmartOpenScopeServiceOpt = (IVsSmartOpenScope)serviceProvider.GetService(typeof(SVsSmartOpenScope));
Assumes.Present(SmartOpenScopeServiceOpt);
FileChangeService = (IVsFileChangeEx)serviceProvider.GetService(typeof(SVsFileChangeEx));
Assumes.Present(FileChangeService);
_temporaryStorageService = temporaryStorageService;
Assumes.Present(_temporaryStorageService);
}
internal IEnumerable<ITemporaryStreamStorage>? GetStorages(string fullPath, DateTime snapshotTimestamp)
{
var key = new FileKey(fullPath, snapshotTimestamp);
// check existing metadata
if (_metadataCache.TryGetSource(key, out var source))
{
if (source is RecoverableMetadataValueSource metadata)
{
return metadata.GetStorages();
}
}
return null;
}
public PortableExecutableReference CreateMetadataReferenceSnapshot(string filePath, MetadataReferenceProperties properties)
=> new VisualStudioMetadataReference.Snapshot(this, properties, filePath, fileChangeTrackerOpt: null);
public void ClearCache()
=> _metadataCache.ClearCache();
private bool VsSmartScopeCandidate(string fullPath)
=> _runtimeDirectories.Any(static (d, fullPath) => fullPath.StartsWith(d, StringComparison.OrdinalIgnoreCase), fullPath);
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");
}
private static ImmutableArray<string> GetRuntimeDirectories()
{
return GetReferencePaths().Concat(
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
if (_metadataCache.TryGetMetadata(key, out var metadata))
{
return metadata;
}
if (VsSmartScopeCandidate(key.FullPath) && TryCreateAssemblyMetadataFromMetadataImporter(key, out var newMetadata))
{
var metadataValueSource = new ConstantValueSource<Optional<AssemblyMetadata>>(newMetadata);
if (!_metadataCache.GetOrAddMetadata(key, metadataValueSource, out metadata))
{
newMetadata.Dispose();
}
return metadata;
}
// use temporary storage
var storages = new List<TemporaryStorageService.TemporaryStreamStorage>();
newMetadata = CreateAssemblyMetadataFromTemporaryStorage(key, storages);
// don't dispose assembly metadata since it shares module metadata
if (!_metadataCache.GetOrAddMetadata(key, new RecoverableMetadataValueSource(newMetadata, storages), out metadata))
{
newMetadata.Dispose();
}
// guarantee that the metadata is alive while we add the source to the cache
GC.KeepAlive(newMetadata);
return metadata;
}
/// <exception cref="IOException"/>
/// <exception cref="BadImageFormatException" />
private AssemblyMetadata CreateAssemblyMetadataFromTemporaryStorage(
FileKey fileKey, List<TemporaryStorageService.TemporaryStreamStorage> storages)
{
var moduleMetadata = CreateModuleMetadataFromTemporaryStorage(fileKey, storages);
return CreateAssemblyMetadata(fileKey, moduleMetadata, storages, CreateModuleMetadataFromTemporaryStorage);
}
private ModuleMetadata CreateModuleMetadataFromTemporaryStorage(
FileKey moduleFileKey, List<TemporaryStorageService.TemporaryStreamStorage>? storages)
{
GetStorageInfoFromTemporaryStorage(moduleFileKey, out var storage, out var stream);
unsafe
{
// For an unmanaged memory stream, ModuleMetadata can take ownership directly.
var metadata = ModuleMetadata.CreateFromMetadata((IntPtr)stream.PositionPointer, (int)stream.Length, stream.Dispose);
// hold onto storage if requested
storages?.Add(storage);
return metadata;
}
}
private void GetStorageInfoFromTemporaryStorage(
FileKey moduleFileKey, out TemporaryStorageService.TemporaryStreamStorage storage, out UnmanagedMemoryStream stream)
{
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();
copyStream.Position = 0;
storage.WriteStream(copyStream);
}
// get stream that owns the underlying unmanaged 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);
return;
}
private static void StreamCopy(Stream source, Stream destination, int start, int length)
{
source.Position = start;
var buffer = SharedPools.ByteArray.Allocate();
int read;
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" />
private bool TryCreateAssemblyMetadataFromMetadataImporter(FileKey fileKey, [NotNullWhen(true)] out AssemblyMetadata? metadata)
{
metadata = null;
var manifestModule = TryCreateModuleMetadataFromMetadataImporter(fileKey);
if (manifestModule == null)
{
return false;
}
metadata = CreateAssemblyMetadata(fileKey, manifestModule, storages: null, CreateModuleMetadata);
return true;
}
private ModuleMetadata? TryCreateModuleMetadataFromMetadataImporter(FileKey moduleFileKey)
{
if (!TryGetFileMappingFromMetadataImporter(moduleFileKey, out var info, out var pImage, out var length))
{
return null;
}
Debug.Assert(pImage != IntPtr.Zero, "Base address should not be zero if GetFileFlatMapping call succeeded.");
var metadata = ModuleMetadata.CreateFromImage(pImage, (int)length);
s_lifetimeMap.Add(metadata, info);
return metadata;
}
private ModuleMetadata CreateModuleMetadata(FileKey moduleFileKey, List<TemporaryStorageService.TemporaryStreamStorage>? storages)
{
var metadata = TryCreateModuleMetadataFromMetadataImporter(moduleFileKey);
// getting metadata didn't work out through importer. fallback to shadow copy one
metadata ??= CreateModuleMetadataFromTemporaryStorage(moduleFileKey, storages);
return metadata;
}
private bool TryGetFileMappingFromMetadataImporter(FileKey fileKey, [NotNullWhen(true)] out IMetaDataInfo? info, out IntPtr pImage, out long length)
{
// 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;
info = null;
pImage = default;
length = default;
if (SmartOpenScopeServiceOpt == null)
{
return false;
}
if (ErrorHandler.Failed(SmartOpenScopeServiceOpt.OpenScope(fullPath, (uint)CorOpenFlags.ReadOnly, s_IID_IMetaDataImport, out var ppUnknown)))
{
return false;
}
info = ppUnknown as IMetaDataInfo;
if (info == null)
{
return false;
}
return ErrorHandler.Succeeded(info.GetFileMapping(out pImage, out length, out var mappingType)) && mappingType == CorFileMapping.Flat;
}
}
/// <exception cref="IOException"/>
/// <exception cref="BadImageFormatException" />
private static AssemblyMetadata CreateAssemblyMetadata(
FileKey fileKey, ModuleMetadata manifestModule, List<TemporaryStorageService.TemporaryStreamStorage>? storages,
Func<FileKey, List<TemporaryStorageService.TemporaryStreamStorage>?, ModuleMetadata> moduleMetadataFactory)
{
var moduleBuilder = ArrayBuilder<ModuleMetadata>.GetInstance();
string? assemblyDir = null;
foreach (var moduleName in manifestModule.GetModuleNames())
{
if (assemblyDir is null)
{
moduleBuilder.Add(manifestModule);
assemblyDir = Path.GetDirectoryName(fileKey.FullPath);
}
// Suppression should be removed or addressed https://github.com/dotnet/roslyn/issues/41636
var moduleFileKey = FileKey.Create(PathUtilities.CombineAbsoluteAndRelativePaths(assemblyDir, moduleName)!);
var metadata = moduleMetadataFactory(moduleFileKey, storages);
moduleBuilder.Add(metadata);
}
if (moduleBuilder.Count == 0)
{
moduleBuilder.Add(manifestModule);
}
return AssemblyMetadata.Create(
moduleBuilder.ToImmutableAndFree());
}
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;
}
}
}
}