-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
DefaultExtensionDependencyChecker.cs
155 lines (132 loc) · 5.88 KB
/
DefaultExtensionDependencyChecker.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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
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; }
}
}
}