From 9f98af5130f1812ed52b8ad06c11cd1abb5d2968 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Fri, 8 Nov 2019 20:48:47 -0500 Subject: [PATCH 1/3] Use stripped-down resolver for perf (no GAC, deferred reading mode) --- .../AssemblyResolver.cs | 73 +++++++++++++++++++ .../Program.cs | 6 +- 2 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 TunnelVisionLabs.ReferenceAssemblyAnnotator/AssemblyResolver.cs diff --git a/TunnelVisionLabs.ReferenceAssemblyAnnotator/AssemblyResolver.cs b/TunnelVisionLabs.ReferenceAssemblyAnnotator/AssemblyResolver.cs new file mode 100644 index 0000000..7a7cc9c --- /dev/null +++ b/TunnelVisionLabs.ReferenceAssemblyAnnotator/AssemblyResolver.cs @@ -0,0 +1,73 @@ +// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace TunnelVisionLabs.ReferenceAssemblyAnnotator +{ + using System.Collections.Generic; + using System.IO; + using Mono.Cecil; + + internal sealed class AssemblyResolver : IAssemblyResolver + { + private static readonly ReaderParameters DefaultReaderParameters = new ReaderParameters(ReadingMode.Deferred); + + private readonly string _searchDirectory; + private readonly Dictionary _assembliesByFileName = new Dictionary(); + + public AssemblyResolver(string searchDirectory) + { + _searchDirectory = searchDirectory; + } + + public AssemblyDefinition? Resolve(AssemblyNameReference name) + { + return Resolve(name, DefaultReaderParameters); + } + + public AssemblyDefinition? Resolve(AssemblyNameReference name, ReaderParameters parameters) + { + if (!_assembliesByFileName.TryGetValue(name.Name, out var assembly)) + { + var stream = OpenReadIfExists(Path.Combine(_searchDirectory, name.Name + ".dll")); + + assembly = stream is null ? null : AssemblyDefinition.ReadAssembly(stream, parameters); + + _assembliesByFileName.Add(name.Name, assembly); + } + + return assembly is object && Matches(assembly.Name, name) ? assembly : null; + } + + private static FileStream? OpenReadIfExists(string path) + { + try + { + return File.OpenRead(path); + } + catch (FileNotFoundException) + { + return null; + } + } + + /// + /// The point of this method is to be a high-performance sanity check, not to reproduce .NET assembly loading + /// behavior. + /// + private static bool Matches(AssemblyNameDefinition definition, AssemblyNameReference reference) + { + return definition.Name == reference.Name + && definition.Version == reference.Version; + } + + public void Dispose() + { + foreach (var loadedAssembly in _assembliesByFileName.Values) + { + loadedAssembly?.Dispose(); + } + + _assembliesByFileName.Clear(); + } + } +} diff --git a/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs b/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs index 6f8c0be..ecaa480 100644 --- a/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs +++ b/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs @@ -15,8 +15,7 @@ internal class Program { internal static void Main(SuppressibleLoggingHelper? log, string referenceAssembly, string annotatedReferenceAssembly, string outputAssembly) { - var assemblyResolver = new DefaultAssemblyResolver(); - assemblyResolver.AddSearchDirectory(Path.GetDirectoryName(referenceAssembly)); + using var assemblyResolver = new AssemblyResolver(Path.GetDirectoryName(Path.GetFullPath(referenceAssembly))!); using var assemblyDefinition = AssemblyDefinition.ReadAssembly(referenceAssembly, new ReaderParameters(ReadingMode.Deferred) { AssemblyResolver = assemblyResolver }); foreach (var module in assemblyDefinition.Modules) @@ -28,8 +27,7 @@ internal static void Main(SuppressibleLoggingHelper? log, string referenceAssemb } } - var annotatedAssemblyResolver = new DefaultAssemblyResolver(); - annotatedAssemblyResolver.AddSearchDirectory(Path.GetDirectoryName(annotatedReferenceAssembly)); + using var annotatedAssemblyResolver = new AssemblyResolver(Path.GetDirectoryName(Path.GetFullPath(annotatedReferenceAssembly))!); using var annotatedAssemblyDefinition = AssemblyDefinition.ReadAssembly(annotatedReferenceAssembly, new ReaderParameters(ReadingMode.Deferred) { AssemblyResolver = annotatedAssemblyResolver }); var wellKnownTypes = new WellKnownTypes(assemblyDefinition); From 5c9d5281f90d0e499ea11be0573f296c00bdd0e0 Mon Sep 17 00:00:00 2001 From: jnm2 Date: Mon, 30 Dec 2019 20:55:17 -0500 Subject: [PATCH 2/3] Report warning rather than crashing with NRE --- TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs b/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs index ecaa480..56d5441 100644 --- a/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs +++ b/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs @@ -25,6 +25,12 @@ internal static void Main(SuppressibleLoggingHelper? log, string referenceAssemb log?.LogWarning("RA1000", "Skipping mixed-mode implementation assembly '{0}'", assemblyDefinition.Name); return; } + + if (module.TypeSystem.Object.Resolve() is null) + { + log?.LogWarning("RA1001", "Cannot resolve core library for assembly '{0}', skipping", assemblyDefinition.Name); + return; + } } using var annotatedAssemblyResolver = new AssemblyResolver(Path.GetDirectoryName(Path.GetFullPath(annotatedReferenceAssembly))!); From bc2840b0a593e6c843c75d8c22373165add0640b Mon Sep 17 00:00:00 2001 From: jnm2 Date: Tue, 31 Dec 2019 21:23:50 -0500 Subject: [PATCH 3/3] Enable core library and other dependencies to be loaded --- CecilBasedAnnotator/Program.cs | 9 ++++++++- CecilBasedAnnotator/Properties/launchSettings.json | 10 +++++----- .../AnnotatorBuildTask.cs | 10 +++++++++- .../AssemblyResolver.cs | 14 +++++++++----- .../Program.cs | 7 ++++--- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/CecilBasedAnnotator/Program.cs b/CecilBasedAnnotator/Program.cs index 9658dcc..a4d924c 100644 --- a/CecilBasedAnnotator/Program.cs +++ b/CecilBasedAnnotator/Program.cs @@ -3,11 +3,18 @@ namespace CecilBasedAnnotator { + using System.Collections.Immutable; + internal class Program { private static void Main(string[] args) { - TunnelVisionLabs.ReferenceAssemblyAnnotator.Program.Main(log: null, args[0], args[1], args[2]); + TunnelVisionLabs.ReferenceAssemblyAnnotator.Program.Main( + log: null, + referenceAssembly: args[0], + targetFrameworkDirectories: args[1].Split(';').ToImmutableArray(), + annotatedReferenceAssembly: args[2], + outputAssembly: args[3]); } } } diff --git a/CecilBasedAnnotator/Properties/launchSettings.json b/CecilBasedAnnotator/Properties/launchSettings.json index 2160454..fce9750 100644 --- a/CecilBasedAnnotator/Properties/launchSettings.json +++ b/CecilBasedAnnotator/Properties/launchSettings.json @@ -1,12 +1,12 @@ { "profiles": { - "mscorlib": { + "Microsoft.Win32.Primitives (netstandard1.6)": { "commandName": "Project", - "commandLineArgs": "\"$(NuGetPackageRoot)\\microsoft.netframework.referenceassemblies.net45\\1.0.0-preview.2\\build\\.NETFramework\\v4.5\\mscorlib.dll\" \"$(NuGetPackageRoot)\\microsoft.netcore.app.ref\\3.0.0-preview8-28405-07\\ref\\netcoreapp3.0\\mscorlib.dll\" mscorlib2.dll" + "commandLineArgs": "\"$(NuGetPackageRoot)\\microsoft.win32.primitives\\4.3.0\\ref\\netstandard1.3\\Microsoft.Win32.Primitives.dll\" \"$(NuGetPackageRoot)\\microsoft.win32.primitives\\4.3.0\\ref\\netstandard1.3\\;$(NuGetPackageRoot)\\system.runtime\\4.3.0\\ref\\netstandard1.5\\\\\" \"$(NuGetPackageRoot)\\microsoft.netcore.app.ref\\3.0.0\\ref\\netcoreapp3.0\\Microsoft.Win32.Primitives.dll\" Microsoft.Win32.Primitives.dll" }, - "System": { + "System.ComponentModel.DataAnnotations (netcoreapp2.0)": { "commandName": "Project", - "commandLineArgs": "\"$(NuGetPackageRoot)\\microsoft.netframework.referenceassemblies.net45\\1.0.0-preview.2\\build\\.NETFramework\\v4.5\\System.dll\" \"$(NuGetPackageRoot)\\microsoft.netcore.app.ref\\3.0.0-preview8-28405-07\\ref\\netcoreapp3.0\\System.dll\" System2.dll" + "commandLineArgs": "\"$(NuGetPackageRoot)\\microsoft.netcore.app\\2.0.0\\ref\\netcoreapp2.0\\System.ComponentModel.DataAnnotations.dll\" \"$(NuGetPackageRoot)\\microsoft.netcore.app\\2.0.0\\ref\\netcoreapp2.0\\\\\" \"$(NuGetPackageRoot)\\microsoft.netcore.app.ref\\3.0.0\\ref\\netcoreapp3.0\\System.ComponentModel.DataAnnotations.dll\" System.ComponentModel.DataAnnotations.dll" } } -} +} \ No newline at end of file diff --git a/TunnelVisionLabs.ReferenceAssemblyAnnotator/AnnotatorBuildTask.cs b/TunnelVisionLabs.ReferenceAssemblyAnnotator/AnnotatorBuildTask.cs index 570b2d9..f992064 100644 --- a/TunnelVisionLabs.ReferenceAssemblyAnnotator/AnnotatorBuildTask.cs +++ b/TunnelVisionLabs.ReferenceAssemblyAnnotator/AnnotatorBuildTask.cs @@ -4,6 +4,7 @@ namespace TunnelVisionLabs.ReferenceAssemblyAnnotator { using System; + using System.Collections.Immutable; using System.IO; using System.Linq; using Microsoft.Build.Framework; @@ -96,7 +97,14 @@ public override bool Execute() Directory.CreateDirectory(OutputPath); string outputAssembly = Path.Combine(OutputPath, Path.GetFileName(unannotatedReferenceAssembly)); - Program.Main(log, unannotatedReferenceAssembly, annotatedReferenceAssembly, outputAssembly); + + Program.Main( + log, + unannotatedReferenceAssembly, + TargetFrameworkDirectories.Select(item => item.ItemSpec).ToImmutableArray(), + annotatedReferenceAssembly, + outputAssembly); + GeneratedAssemblies = new[] { new TaskItem(outputAssembly) }; string sourceDocumentation = Path.ChangeExtension(unannotatedReferenceAssembly, ".xml"); diff --git a/TunnelVisionLabs.ReferenceAssemblyAnnotator/AssemblyResolver.cs b/TunnelVisionLabs.ReferenceAssemblyAnnotator/AssemblyResolver.cs index 7a7cc9c..7849716 100644 --- a/TunnelVisionLabs.ReferenceAssemblyAnnotator/AssemblyResolver.cs +++ b/TunnelVisionLabs.ReferenceAssemblyAnnotator/AssemblyResolver.cs @@ -4,19 +4,21 @@ namespace TunnelVisionLabs.ReferenceAssemblyAnnotator { using System.Collections.Generic; + using System.Collections.Immutable; using System.IO; + using System.Linq; using Mono.Cecil; internal sealed class AssemblyResolver : IAssemblyResolver { private static readonly ReaderParameters DefaultReaderParameters = new ReaderParameters(ReadingMode.Deferred); - private readonly string _searchDirectory; + private readonly ImmutableArray _searchDirectories; private readonly Dictionary _assembliesByFileName = new Dictionary(); - public AssemblyResolver(string searchDirectory) + public AssemblyResolver(ImmutableArray searchDirectories) { - _searchDirectory = searchDirectory; + _searchDirectories = searchDirectories; } public AssemblyDefinition? Resolve(AssemblyNameReference name) @@ -28,7 +30,9 @@ public AssemblyResolver(string searchDirectory) { if (!_assembliesByFileName.TryGetValue(name.Name, out var assembly)) { - var stream = OpenReadIfExists(Path.Combine(_searchDirectory, name.Name + ".dll")); + var stream = _searchDirectories + .Select(directory => OpenReadIfExists(Path.Combine(directory, name.Name + ".dll"))) + .FirstOrDefault(s => s is object); assembly = stream is null ? null : AssemblyDefinition.ReadAssembly(stream, parameters); @@ -57,7 +61,7 @@ public AssemblyResolver(string searchDirectory) private static bool Matches(AssemblyNameDefinition definition, AssemblyNameReference reference) { return definition.Name == reference.Name - && definition.Version == reference.Version; + && definition.Version >= reference.Version; } public void Dispose() diff --git a/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs b/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs index 56d5441..a79fc55 100644 --- a/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs +++ b/TunnelVisionLabs.ReferenceAssemblyAnnotator/Program.cs @@ -5,6 +5,7 @@ namespace TunnelVisionLabs.ReferenceAssemblyAnnotator { using System; using System.Collections.Generic; + using System.Collections.Immutable; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -13,9 +14,9 @@ namespace TunnelVisionLabs.ReferenceAssemblyAnnotator internal class Program { - internal static void Main(SuppressibleLoggingHelper? log, string referenceAssembly, string annotatedReferenceAssembly, string outputAssembly) + internal static void Main(SuppressibleLoggingHelper? log, string referenceAssembly, ImmutableArray targetFrameworkDirectories, string annotatedReferenceAssembly, string outputAssembly) { - using var assemblyResolver = new AssemblyResolver(Path.GetDirectoryName(Path.GetFullPath(referenceAssembly))!); + using var assemblyResolver = new AssemblyResolver(targetFrameworkDirectories); using var assemblyDefinition = AssemblyDefinition.ReadAssembly(referenceAssembly, new ReaderParameters(ReadingMode.Deferred) { AssemblyResolver = assemblyResolver }); foreach (var module in assemblyDefinition.Modules) @@ -33,7 +34,7 @@ internal static void Main(SuppressibleLoggingHelper? log, string referenceAssemb } } - using var annotatedAssemblyResolver = new AssemblyResolver(Path.GetDirectoryName(Path.GetFullPath(annotatedReferenceAssembly))!); + using var annotatedAssemblyResolver = new AssemblyResolver(ImmutableArray.Create(Path.GetDirectoryName(Path.GetFullPath(annotatedReferenceAssembly))!)); using var annotatedAssemblyDefinition = AssemblyDefinition.ReadAssembly(annotatedReferenceAssembly, new ReaderParameters(ReadingMode.Deferred) { AssemblyResolver = annotatedAssemblyResolver }); var wellKnownTypes = new WellKnownTypes(assemblyDefinition);