|
// 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.
#nullable disable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Threading;
using Microsoft.CodeAnalysis;
using System.Reflection;
using System.Diagnostics;
using SystemMetadataReader = System.Reflection.Metadata.MetadataReader;
namespace Microsoft.VisualStudio.LanguageServices.Implementation
{
internal static class AnalyzerDependencyChecker
{
public static AnalyzerDependencyResults ComputeDependencyConflicts(IEnumerable<string> analyzerFilePaths, IEnumerable<IIgnorableAssemblyList> ignorableAssemblyLists, IBindingRedirectionService bindingRedirectionService = null, CancellationToken cancellationToken = default)
{
var analyzerInfos = new List<AnalyzerInfo>();
foreach (var analyzerFilePath in analyzerFilePaths)
{
cancellationToken.ThrowIfCancellationRequested();
var info = TryReadAnalyzerInfo(analyzerFilePath);
if (info != null)
{
analyzerInfos.Add(info);
}
}
var allIgnorableAssemblyLists = new List<IIgnorableAssemblyList>(ignorableAssemblyLists);
allIgnorableAssemblyLists.Add(new IgnorableAssemblyIdentityList(analyzerInfos.Select(info => info.Identity)));
// First check for analyzers with the same identity but different
// contents (that is, different MVIDs).
var conflicts = FindConflictingAnalyzers(analyzerInfos, cancellationToken);
// Then check for missing references.
var missingDependencies = FindMissingDependencies(analyzerInfos, allIgnorableAssemblyLists, bindingRedirectionService, cancellationToken);
return new AnalyzerDependencyResults(conflicts, missingDependencies);
}
private static ImmutableArray<MissingAnalyzerDependency> FindMissingDependencies(List<AnalyzerInfo> analyzerInfos, List<IIgnorableAssemblyList> ignorableAssemblyLists, IBindingRedirectionService bindingRedirectionService, CancellationToken cancellationToken)
{
var builder = ImmutableArray.CreateBuilder<MissingAnalyzerDependency>();
foreach (var analyzerInfo in analyzerInfos)
{
foreach (var reference in analyzerInfo.References)
{
cancellationToken.ThrowIfCancellationRequested();
var redirectedReference = bindingRedirectionService != null
? bindingRedirectionService.ApplyBindingRedirects(reference)
: reference;
if (!ignorableAssemblyLists.Any(ignorableAssemblyList => ignorableAssemblyList.Includes(redirectedReference)))
{
builder.Add(new MissingAnalyzerDependency(
analyzerInfo.Path,
reference));
}
}
}
return builder.ToImmutable();
}
private static ImmutableArray<AnalyzerDependencyConflict> FindConflictingAnalyzers(List<AnalyzerInfo> analyzerInfos, CancellationToken cancellationToken)
{
var builder = ImmutableArray.CreateBuilder<AnalyzerDependencyConflict>();
foreach (var identityGroup in analyzerInfos.GroupBy(di => di.Identity))
{
var identityGroupArray = identityGroup.ToImmutableArray();
for (var i = 0; i < identityGroupArray.Length; i++)
{
for (var j = i + 1; j < identityGroupArray.Length; j++)
{
cancellationToken.ThrowIfCancellationRequested();
if (identityGroupArray[i].MVID != identityGroupArray[j].MVID)
{
builder.Add(new AnalyzerDependencyConflict(
identityGroup.Key,
identityGroupArray[i].Path,
identityGroupArray[j].Path));
}
}
}
}
return builder.ToImmutable();
}
private static AnalyzerInfo TryReadAnalyzerInfo(string filePath)
{
try
{
using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete);
using var peReader = new PEReader(stream);
var metadataReader = peReader.GetMetadataReader();
var mvid = ReadMvid(metadataReader);
var identity = ReadAssemblyIdentity(metadataReader);
var references = ReadReferences(metadataReader);
return new AnalyzerInfo(filePath, identity, mvid, references);
}
catch { }
return null;
}
private static ImmutableArray<AssemblyIdentity> ReadReferences(SystemMetadataReader metadataReader)
{
var builder = ImmutableArray.CreateBuilder<AssemblyIdentity>();
foreach (var referenceHandle in metadataReader.AssemblyReferences)
{
var reference = metadataReader.GetAssemblyReference(referenceHandle);
var refname = metadataReader.GetString(reference.Name);
var refversion = reference.Version;
var refcultureName = metadataReader.GetString(reference.Culture);
var refpublicKeyOrToken = metadataReader.GetBlobContent(reference.PublicKeyOrToken);
var refflags = reference.Flags;
var refhasPublicKey = (refflags & AssemblyFlags.PublicKey) != 0;
builder.Add(new AssemblyIdentity(refname, refversion, refcultureName, refpublicKeyOrToken, hasPublicKey: refhasPublicKey));
}
return builder.ToImmutable();
}
private static AssemblyIdentity ReadAssemblyIdentity(SystemMetadataReader metadataReader)
{
var assemblyDefinition = metadataReader.GetAssemblyDefinition();
var name = metadataReader.GetString(assemblyDefinition.Name);
var version = assemblyDefinition.Version;
var cultureName = metadataReader.GetString(assemblyDefinition.Culture);
var publicKeyOrToken = metadataReader.GetBlobContent(assemblyDefinition.PublicKey);
var flags = assemblyDefinition.Flags;
var hasPublicKey = (flags & AssemblyFlags.PublicKey) != 0;
return new AssemblyIdentity(name, version, cultureName, publicKeyOrToken, hasPublicKey: hasPublicKey);
}
private static Guid ReadMvid(SystemMetadataReader metadataReader)
{
var mvidHandle = metadataReader.GetModuleDefinition().Mvid;
return metadataReader.GetGuid(mvidHandle);
}
private sealed class AnalyzerInfo
{
public AnalyzerInfo(string filePath, AssemblyIdentity identity, Guid mvid, ImmutableArray<AssemblyIdentity> references)
{
Path = filePath;
Identity = identity;
MVID = mvid;
References = references;
}
public string Path { get; }
public AssemblyIdentity Identity { get; }
public Guid MVID { get; }
public ImmutableArray<AssemblyIdentity> References { get; }
}
}
}
|