提交 cc7cffc4 编写于 作者: H Heejae Chang 提交者: GitHub

Merge pull request #17834 from heejaechang/RpsFix

made host analyzer not to load assembly to find out analyzer referenc…
......@@ -5,8 +5,8 @@
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
......@@ -86,10 +86,29 @@ public Checksum CreateChecksum(MetadataReference reference, CancellationToken ca
public Checksum CreateChecksum(AnalyzerReference reference, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
using (var stream = SerializableBytes.CreateWritableStream())
using (var writer = new ObjectWriter(stream, cancellationToken: cancellationToken))
{
WriteTo(reference, writer, checksum: true, cancellationToken: cancellationToken);
switch (reference)
{
case AnalyzerFileReference file:
WriteAnalyzerFileReferenceMvid(file, writer, cancellationToken);
break;
case UnresolvedAnalyzerReference unresolved:
WriteUnresolvedAnalyzerReferenceTo(unresolved, writer);
break;
case AnalyzerImageReference _:
// TODO: think a way to support this or a way to deal with this kind of situation.
// https://github.com/dotnet/roslyn/issues/15783
throw new NotSupportedException(nameof(AnalyzerImageReference));
default:
throw ExceptionUtilities.UnexpectedValue(reference.GetType());
}
stream.Position = 0;
return Checksum.Create(stream);
......@@ -128,9 +147,53 @@ public MetadataReference ReadMetadataReferenceFrom(ObjectReader reader, Cancella
throw ExceptionUtilities.UnexpectedValue(type);
}
public void WriteTo(AnalyzerReference reference, ObjectWriter writer, CancellationToken cancellationToken)
public void WriteTo(AnalyzerReference reference, ObjectWriter writer, bool usePathFromAssembly, CancellationToken cancellationToken)
{
WriteTo(reference, writer, checksum: false, cancellationToken: cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
switch (reference)
{
case AnalyzerFileReference file:
{
// fail to load analyzer assembly
var assemblyPath = usePathFromAssembly ? TryGetAnalyzerAssemblyPath(file) : file.FullPath;
if (assemblyPath == null)
{
WriteUnresolvedAnalyzerReferenceTo(reference, writer);
return;
}
writer.WriteString(nameof(AnalyzerFileReference));
writer.WriteInt32((int)SerializationKinds.FilePath);
// TODO: remove this kind of host specific knowledge from common layer.
// but think moving it to host layer where this implementation detail actually exist.
//
// analyzer assembly path to load analyzer acts like
// snapshot version for analyzer (since it is based on shadow copy)
// we can't send over bits and load analyer from memory (image) due to CLR not being able
// to find satellite dlls for analyzers.
writer.WriteString(file.FullPath);
writer.WriteString(assemblyPath);
return;
}
case UnresolvedAnalyzerReference unresolved:
{
WriteUnresolvedAnalyzerReferenceTo(unresolved, writer);
return;
}
case AnalyzerImageReference _:
{
// TODO: think a way to support this or a way to deal with this kind of situation.
// https://github.com/dotnet/roslyn/issues/15783
throw new NotSupportedException(nameof(AnalyzerImageReference));
}
default:
throw ExceptionUtilities.UnexpectedValue(reference.GetType());
}
}
public AnalyzerReference ReadAnalyzerReferenceFrom(ObjectReader reader, CancellationToken cancellationToken)
......@@ -159,6 +222,29 @@ public AnalyzerReference ReadAnalyzerReferenceFrom(ObjectReader reader, Cancella
throw ExceptionUtilities.UnexpectedValue(type);
}
private void WriteAnalyzerFileReferenceMvid(AnalyzerFileReference reference, ObjectWriter writer, CancellationToken cancellationToken)
{
try
{
using (var stream = new FileStream(reference.FullPath, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete))
using (var peReader = new PEReader(stream))
{
var metadataReader = peReader.GetMetadataReader();
var mvidHandle = metadataReader.GetModuleDefinition().Mvid;
var guid = metadataReader.GetGuid(mvidHandle);
writer.WriteValue(guid.ToByteArray());
}
}
catch
{
// we can't load the assembly analyzer file reference is pointing to.
// rather than crashing, handle it gracefully
WriteUnresolvedAnalyzerReferenceTo(reference, writer);
}
}
protected void WritePortableExecutableReferenceHeaderTo(
PortableExecutableReference reference, SerializationKinds kind, ObjectWriter writer, CancellationToken cancellationToken)
{
......@@ -511,63 +597,6 @@ private unsafe void WriteTo(MetadataReader reader, ObjectWriter writer, Cancella
writer.WriteValue(bytes);
}
private void WriteTo(AnalyzerReference reference, ObjectWriter writer, bool checksum, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var file = reference as AnalyzerFileReference;
if (file != null)
{
// fail to load analyzer assembly
var assemblyPath = TryGetAnalyzerAssemblyPath(file);
if (assemblyPath == null)
{
WriteUnresolvedAnalyzerReferenceTo(reference, writer);
return;
}
writer.WriteString(nameof(AnalyzerFileReference));
writer.WriteInt32((int)SerializationKinds.FilePath);
if (checksum)
{
// we don't write full path when creating checksum
// make sure we always normalize assemblyPath to lower case since it comes from Assembly.Location
// unlike FullPath which comes from string
writer.WriteString(assemblyPath.ToLowerInvariant());
return;
}
// TODO: remove this kind of host specific knowledge from common layer.
// but think moving it to host layer where this implementation detail actually exist.
//
// analyzer assembly path to load analyzer acts like
// snapshot version for analyzer (since it is based on shadow copy)
// we can't send over bits and load analyer from memory (image) due to CLR not being able
// to find satellite dlls for analyzers.
writer.WriteString(file.FullPath);
writer.WriteString(assemblyPath);
return;
}
var unresolved = reference as UnresolvedAnalyzerReference;
if (unresolved != null)
{
WriteUnresolvedAnalyzerReferenceTo(reference, writer);
return;
}
var image = reference as AnalyzerImageReference;
if (image != null)
{
// TODO: think a way to support this or a way to deal with this kind of situation.
// https://github.com/dotnet/roslyn/issues/15783
throw new NotSupportedException(nameof(AnalyzerImageReference));
}
throw ExceptionUtilities.UnexpectedValue(reference.GetType());
}
private static void WriteUnresolvedAnalyzerReferenceTo(AnalyzerReference reference, ObjectWriter writer)
{
writer.WriteString(nameof(UnresolvedAnalyzerReference));
......
......@@ -59,8 +59,6 @@ private static Checksum CreateChecksumFromStreamWriter(string kind, Action<Objec
///
/// unlike project analyzer, analyzer that got installed from vsix doesn't do shadow copying
/// so we don't need to load assembly to find out actual filepath.
///
/// this also will be temporary solution for RC since we will move to MVID for checksum soon
/// </summary>
internal sealed class WorkspaceAnalyzerReferenceAsset : CustomAsset
{
......@@ -68,7 +66,9 @@ internal sealed class WorkspaceAnalyzerReferenceAsset : CustomAsset
private readonly Serializer _serializer;
public WorkspaceAnalyzerReferenceAsset(AnalyzerReference reference, Serializer serializer) :
base(CreateChecksum(reference), WellKnownSynchronizationKinds.AnalyzerReference)
base(
serializer.CreateChecksum(reference, CancellationToken.None),
WellKnownSynchronizationKinds.AnalyzerReference)
{
_reference = reference;
_serializer = serializer;
......@@ -76,21 +76,12 @@ internal sealed class WorkspaceAnalyzerReferenceAsset : CustomAsset
public override Task WriteObjectToAsync(ObjectWriter writer, CancellationToken cancellationToken)
{
_serializer.SerializeAnalyzerReference(_reference, writer, cancellationToken);
// host analyzer is not shadow copied, no need to load assembly to get real path
// this also prevent us from loading assemblies for all vsix analyzers preemptively
const bool usePathFromAssembly = false;
_serializer.SerializeAnalyzerReference(_reference, writer, usePathFromAssembly, cancellationToken);
return SpecializedTasks.EmptyTask;
}
private static Checksum CreateChecksum(AnalyzerReference reference)
{
using (var stream = SerializableBytes.CreateWritableStream())
using (var objectWriter = new ObjectWriter(stream))
{
objectWriter.WriteString(WellKnownSynchronizationKinds.AnalyzerReference);
objectWriter.WriteString(reference.FullPath);
return Checksum.Create(stream);
}
}
}
}
......@@ -19,7 +19,7 @@ internal interface IReferenceSerializationService : IWorkspaceService
void WriteTo(Encoding encoding, ObjectWriter writer, CancellationToken cancellationToken);
void WriteTo(MetadataReference reference, ObjectWriter writer, CancellationToken cancellationToken);
void WriteTo(AnalyzerReference reference, ObjectWriter writer, CancellationToken cancellationToken);
void WriteTo(AnalyzerReference reference, ObjectWriter writer, bool usePathFromAssembly, CancellationToken cancellationToken);
Encoding ReadEncodingFrom(ObjectReader reader, CancellationToken cancellationToken);
MetadataReference ReadMetadataReferenceFrom(ObjectReader reader, CancellationToken cancellationToken);
......
......@@ -131,7 +131,7 @@ public void Serialize(object value, ObjectWriter writer, CancellationToken cance
return;
case WellKnownSynchronizationKinds.AnalyzerReference:
SerializeAnalyzerReference((AnalyzerReference)value, writer, cancellationToken);
SerializeAnalyzerReference((AnalyzerReference)value, writer, usePathFromAssembly: true, cancellationToken: cancellationToken);
return;
case WellKnownSynchronizationKinds.SourceText:
......
......@@ -139,10 +139,10 @@ private MetadataReference DeserializeMetadataReference(ObjectReader reader, Canc
return _hostSerializationService.ReadMetadataReferenceFrom(reader, cancellationToken);
}
public void SerializeAnalyzerReference(AnalyzerReference reference, ObjectWriter writer, CancellationToken cancellationToken)
public void SerializeAnalyzerReference(AnalyzerReference reference, ObjectWriter writer, bool usePathFromAssembly, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
_hostSerializationService.WriteTo(reference, writer, cancellationToken);
_hostSerializationService.WriteTo(reference, writer, usePathFromAssembly, cancellationToken);
}
private AnalyzerReference DeserializeAnalyzerReference(ObjectReader reader, CancellationToken cancellationToken)
......
......@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.IO;
using System.Linq;
......@@ -396,32 +397,48 @@ public async Task Missing_Analyzer_Serailization_Desktop_Test()
[Fact]
public async Task RoundTrip_Analyzer_Serailization_Test()
{
var workspace = new AdhocWorkspace();
var serializer = new Serializer(workspace);
using (var tempRoot = new TempRoot())
{
var workspace = new AdhocWorkspace();
var serializer = new Serializer(workspace);
var reference = new AnalyzerFileReference(typeof(object).Assembly.Location, new MockShadowCopyAnalyzerAssemblyLoader());
// actually shadow copy content
var location = typeof(object).Assembly.Location;
var file = tempRoot.CreateFile("shadow", "dll");
file.CopyContentFrom(location);
// make sure this doesn't throw
var assetFromFile = SolutionAsset.Create(serializer.CreateChecksum(reference, CancellationToken.None), reference, serializer);
var assetFromStorage = await CloneAssetAsync(serializer, assetFromFile).ConfigureAwait(false);
var assetFromStorage2 = await CloneAssetAsync(serializer, assetFromStorage).ConfigureAwait(false);
var reference = new AnalyzerFileReference(location, new MockShadowCopyAnalyzerAssemblyLoader(ImmutableDictionary<string, string>.Empty.Add(location, file.Path)));
// make sure this doesn't throw
var assetFromFile = SolutionAsset.Create(serializer.CreateChecksum(reference, CancellationToken.None), reference, serializer);
var assetFromStorage = await CloneAssetAsync(serializer, assetFromFile).ConfigureAwait(false);
var assetFromStorage2 = await CloneAssetAsync(serializer, assetFromStorage).ConfigureAwait(false);
}
}
[Fact]
public async Task RoundTrip_Analyzer_Serailization_Desktop_Test()
{
var hostServices = MefHostServices.Create(
using (var tempRoot = new TempRoot())
{
var hostServices = MefHostServices.Create(
MefHostServices.DefaultAssemblies.Add(typeof(Host.TemporaryStorageServiceFactory.TemporaryStorageService).Assembly));
var workspace = new AdhocWorkspace(hostServices);
var serializer = new Serializer(workspace);
var workspace = new AdhocWorkspace(hostServices);
var serializer = new Serializer(workspace);
var reference = new AnalyzerFileReference(typeof(object).Assembly.Location, new MockShadowCopyAnalyzerAssemblyLoader());
// actually shadow copy content
var location = typeof(object).Assembly.Location;
var file = tempRoot.CreateFile("shadow", "dll");
file.CopyContentFrom(location);
// make sure this doesn't throw
var assetFromFile = SolutionAsset.Create(serializer.CreateChecksum(reference, CancellationToken.None), reference, serializer);
var assetFromStorage = await CloneAssetAsync(serializer, assetFromFile).ConfigureAwait(false);
var assetFromStorage2 = await CloneAssetAsync(serializer, assetFromStorage).ConfigureAwait(false);
var reference = new AnalyzerFileReference(location, new MockShadowCopyAnalyzerAssemblyLoader(ImmutableDictionary<string, string>.Empty.Add(location, file.Path)));
// make sure this doesn't throw
var assetFromFile = SolutionAsset.Create(serializer.CreateChecksum(reference, CancellationToken.None), reference, serializer);
var assetFromStorage = await CloneAssetAsync(serializer, assetFromFile).ConfigureAwait(false);
var assetFromStorage2 = await CloneAssetAsync(serializer, assetFromStorage).ConfigureAwait(false);
}
}
[Fact]
......@@ -774,14 +791,20 @@ protected override PortableExecutableReference WithPropertiesImpl(MetadataRefere
private class MockShadowCopyAnalyzerAssemblyLoader : IAnalyzerAssemblyLoader
{
private readonly ImmutableDictionary<string, string> _map;
public MockShadowCopyAnalyzerAssemblyLoader(ImmutableDictionary<string, string> map)
{
_map = map;
}
public void AddDependencyLocation(string fullPath)
{
}
public Assembly LoadFromPath(string fullPath)
{
// return something other than given one. mimicing behavior of shadow copy
return typeof(SharedAttribute).Assembly;
return Assembly.LoadFrom(_map[fullPath]);
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册