|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System.Reflection;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using Microsoft.CodeAnalysis;
namespace Microsoft.NET.Sdk.Razor.Tool
{
internal class DefaultExtensionDependencyChecker : ExtensionDependencyChecker
{
// These are treated as prefixes. So `Microsoft.CodeAnalysis.Razor` would be assumed to work.
private static readonly string[] DefaultIgnoredAssemblies = new string[]
{
"mscorlib",
"netstandard",
"System",
"Microsoft.CodeAnalysis",
"Microsoft.AspNetCore.Razor",
"Microsoft.Extensions.ObjectPool"
};
private readonly ExtensionAssemblyLoader _loader;
private readonly TextWriter _output;
private readonly TextWriter _error;
private readonly string[] _ignoredAssemblies;
public DefaultExtensionDependencyChecker(
ExtensionAssemblyLoader loader,
TextWriter output,
TextWriter error,
string[] ignoredAssemblies = null)
{
_loader = loader;
_output = output;
_error = error;
_ignoredAssemblies = ignoredAssemblies ?? DefaultIgnoredAssemblies;
}
public override bool Check(IEnumerable<string> assmblyFilePaths)
{
try
{
return CheckCore(assmblyFilePaths);
}
catch (Exception ex)
{
_error.WriteLine("Exception performing Extension dependency check:");
_error.WriteLine(ex.ToString());
return false;
}
}
private bool CheckCore(IEnumerable<string> assemblyFilePaths)
{
var items = assemblyFilePaths.Select(a => ExtensionVerificationItem.Create(a)).ToArray();
var assemblies = new HashSet<AssemblyIdentity>(items.Select(i => i.Identity));
for (var i = 0; i < items.Length; i++)
{
var item = items[i];
_output.WriteLine($"Verifying assembly at {item.FilePath}");
if (!Path.IsPathRooted(item.FilePath))
{
_error.WriteLine($"The file path '{item.FilePath}' is not a rooted path. File paths must be absolute and fully-qualified.");
return false;
}
foreach (var reference in item.References)
{
if (_ignoredAssemblies.Any(n => reference.Name.StartsWith(n, StringComparison.Ordinal)))
{
// This is on the allow list, keep going.
continue;
}
if (assemblies.Contains(reference))
{
// This was also provided as a dependency, keep going.
continue;
}
// If we get here we can't resolve this assembly. This is an error.
_error.WriteLine($"Extension assembly '{item.Identity.Name}' depends on '{reference.ToString()} which is missing.");
return false;
}
}
// Assuming we get this far, the set of assemblies we have is at least a coherent set (barring
// version conflicts). Register all of the paths with the loader so they can find each other by
// name.
for (var i = 0; i < items.Length; i++)
{
_loader.AddAssemblyLocation(items[i].FilePath);
}
// Now try to load everything. This has the side effect of resolving all of these items
// in the loader's caches.
for (var i = 0; i < items.Length; i++)
{
var item = items[i];
item.Assembly = _loader.LoadFromPath(item.FilePath);
}
// Third, check that the MVIDs of the files on disk match the MVIDs of the loaded assemblies.
for (var i = 0; i < items.Length; i++)
{
var item = items[i];
if (item.Mvid != item.Assembly.ManifestModule.ModuleVersionId)
{
_error.WriteLine($"Extension assembly '{item.Identity.Name}' at '{item.FilePath}' has a different ModuleVersionId than loaded assembly '{item.Assembly.FullName}'");
return false;
}
}
return true;
}
private class ExtensionVerificationItem
{
public static ExtensionVerificationItem Create(string filePath)
{
using (var peReader = new PEReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
var metadataReader = peReader.GetMetadataReader();
var identity = metadataReader.GetAssemblyIdentity();
var mvid = metadataReader.GetGuid(metadataReader.GetModuleDefinition().Mvid);
var references = metadataReader.GetReferencedAssembliesOrThrow();
return new ExtensionVerificationItem(filePath, identity, mvid, references.ToArray());
}
}
private ExtensionVerificationItem(string filePath, AssemblyIdentity identity, Guid mvid, AssemblyIdentity[] references)
{
FilePath = filePath;
Identity = identity;
Mvid = mvid;
References = references;
}
public string FilePath { get; }
public Assembly Assembly { get; set; }
public AssemblyIdentity Identity { get; }
public Guid Mvid { get; }
public IReadOnlyList<AssemblyIdentity> References { get; }
}
}
}
|