Skip to content

Commit

Permalink
Load analyzer assemlbies in their own AssemblyLoadContext
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeRobich committed Aug 6, 2020
1 parent 4f72039 commit f973fe3
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 67 deletions.
4 changes: 2 additions & 2 deletions src/Analyzers/AnalyzerFinderHelpers.cs
Expand Up @@ -12,7 +12,7 @@ namespace Microsoft.CodeAnalysis.Tools.Analyzers
{
internal static class AnalyzerFinderHelpers
{
public static (ImmutableArray<DiagnosticAnalyzer> Analyzers, ImmutableArray<CodeFixProvider> Fixers) LoadAnalyzersAndFixers(IEnumerable<Assembly> assemblies)
public static AnalyzersAndFixers LoadAnalyzersAndFixers(IEnumerable<Assembly> assemblies)
{
var types = assemblies
.SelectMany(assembly => assembly.GetTypes()
Expand All @@ -32,7 +32,7 @@ public static (ImmutableArray<DiagnosticAnalyzer> Analyzers, ImmutableArray<Code
.OfType<DiagnosticAnalyzer>()
.ToImmutableArray();

return (diagnosticAnalyzers, codeFixProviders);
return new AnalyzersAndFixers(diagnosticAnalyzers, codeFixProviders);
}
}
}
49 changes: 17 additions & 32 deletions src/Analyzers/AnalyzerFormatter.cs
Expand Up @@ -16,8 +16,6 @@ namespace Microsoft.CodeAnalysis.Tools.Analyzers
{
internal class AnalyzerFormatter : ICodeFormatter
{
private static readonly ImmutableArray<string> s_supportedLanguages = ImmutableArray.Create(LanguageNames.CSharp, LanguageNames.VisualBasic);

private readonly string _name;
private readonly IAnalyzerInformationProvider _informationProvider;
private readonly IAnalyzerRunner _runner;
Expand All @@ -43,8 +41,8 @@ internal class AnalyzerFormatter : ICodeFormatter
List<FormattedFile> formattedFiles,
CancellationToken cancellationToken)
{
var (analyzers, fixers) = _informationProvider.GetAnalyzersAndFixers(solution, formatOptions, logger);
if (analyzers.IsEmpty && fixers.IsEmpty)
var projectAnalyzersAndFixers = _informationProvider.GetAnalyzersAndFixers(solution, formatOptions, logger);
if (projectAnalyzersAndFixers.IsEmpty)
{
return solution;
}
Expand All @@ -60,7 +58,8 @@ internal class AnalyzerFormatter : ICodeFormatter
var severity = _informationProvider.GetSeverity(formatOptions);

// Filter to analyzers that report diagnostics with equal or greater severity.
var projectAnalyzers = await FilterBySeverityAsync(solution.Projects, analyzers, formattablePaths, severity, cancellationToken).ConfigureAwait(false);
var projectAnalyzers = await FilterBySeverityAsync(projectAnalyzersAndFixers, formattablePaths, severity, cancellationToken).ConfigureAwait(false);
var allFixers = projectAnalyzersAndFixers.Values.SelectMany(analyzersAndFixers => analyzersAndFixers.Fixers).ToImmutableArray();

// Determine which diagnostics are being reported for each project.
var projectDiagnostics = await GetProjectDiagnosticsAsync(solution, projectAnalyzers, formattablePaths, formatOptions, severity, logger, formattedFiles, cancellationToken).ConfigureAwait(false);
Expand All @@ -71,7 +70,7 @@ internal class AnalyzerFormatter : ICodeFormatter
logger.LogTrace(Resources.Fixing_diagnostics);

// Run each analyzer individually and apply fixes if possible.
solution = await FixDiagnosticsAsync(solution, analyzers, fixers, projectDiagnostics, formattablePaths, severity, logger, cancellationToken).ConfigureAwait(false);
solution = await FixDiagnosticsAsync(solution, projectAnalyzers, allFixers, projectDiagnostics, formattablePaths, severity, logger, cancellationToken).ConfigureAwait(false);

var fixDiagnosticsMS = analysisStopwatch.ElapsedMilliseconds - projectDiagnosticsMS;
logger.LogTrace(Resources.Complete_in_0_ms, fixDiagnosticsMS);
Expand Down Expand Up @@ -138,8 +137,8 @@ static void LogDiagnosticLocations(Solution solution, IEnumerable<Diagnostic> di

private async Task<Solution> FixDiagnosticsAsync(
Solution solution,
ImmutableArray<DiagnosticAnalyzer> allAnalyzers,
ImmutableArray<CodeFixProvider> allCodefixes,
ImmutableDictionary<Project, ImmutableArray<DiagnosticAnalyzer>> projectAnalyzers,
ImmutableArray<CodeFixProvider> allFixers,
ImmutableDictionary<ProjectId, ImmutableHashSet<string>> projectDiagnostics,
ImmutableHashSet<string> formattablePaths,
DiagnosticSeverity severity,
Expand All @@ -153,14 +152,11 @@ static void LogDiagnosticLocations(Solution solution, IEnumerable<Diagnostic> di
return solution;
}

// Build maps between diagnostic id and the associated analyzers and codefixes
var analyzersByIdAndLanguage = CreateAnalyzerMap(reportedDiagnostics, allAnalyzers);
var fixersById = CreateFixerMap(reportedDiagnostics, allCodefixes);
var fixersById = CreateFixerMap(reportedDiagnostics, allFixers);

// We need to run each codefix iteratively so ensure that all diagnostics are found and fixed.
foreach (var diagnosticId in reportedDiagnostics)
{
var analyzersByLanguage = analyzersByIdAndLanguage[diagnosticId];
var codefixes = fixersById[diagnosticId];

// If there is no codefix, there is no reason to run analysis again.
Expand All @@ -174,12 +170,15 @@ static void LogDiagnosticLocations(Solution solution, IEnumerable<Diagnostic> di
foreach (var project in solution.Projects)
{
// Only run analysis on projects that had previously reported the diagnostic
if (!projectDiagnostics.TryGetValue(project.Id, out var diagnosticIds))
if (!projectDiagnostics.TryGetValue(project.Id, out var diagnosticIds)
|| !diagnosticIds.Contains(diagnosticId))
{
continue;
}

var analyzers = analyzersByLanguage[project.Language];
var analyzers = projectAnalyzers[project]
.Where(analyzer => analyzer.SupportedDiagnostics.Any(descriptor => descriptor.Id == diagnosticId))
.ToImmutableArray();
await _runner.RunCodeAnalysisAsync(result, analyzers, project, formattablePaths, severity, logger, cancellationToken).ConfigureAwait(false);
}

Expand All @@ -199,20 +198,6 @@ static void LogDiagnosticLocations(Solution solution, IEnumerable<Diagnostic> di

return solution;

static ImmutableDictionary<string, ImmutableDictionary<string, ImmutableArray<DiagnosticAnalyzer>>> CreateAnalyzerMap(
ImmutableArray<string> diagnosticIds,
ImmutableArray<DiagnosticAnalyzer> analyzers)
{
return diagnosticIds.ToImmutableDictionary(
id => id,
id => s_supportedLanguages.ToImmutableDictionary(
language => language,
language => analyzers
.Where(analyzer => DoesAnalyzerSupportLanguage(analyzer, language))
.Where(analyzer => analyzer.SupportedDiagnostics.Any(diagnostic => diagnostic.Id == id))
.ToImmutableArray()));
}

static ImmutableDictionary<string, ImmutableArray<CodeFixProvider>> CreateFixerMap(
ImmutableArray<string> diagnosticIds,
ImmutableArray<CodeFixProvider> fixers)
Expand All @@ -226,21 +211,21 @@ static void LogDiagnosticLocations(Solution solution, IEnumerable<Diagnostic> di
}

internal static async Task<ImmutableDictionary<Project, ImmutableArray<DiagnosticAnalyzer>>> FilterBySeverityAsync(
IEnumerable<Project> projects,
ImmutableArray<DiagnosticAnalyzer> allAnalyzers,
ImmutableDictionary<Project, AnalyzersAndFixers> projectAnalyzersAndFixers,
ImmutableHashSet<string> formattablePaths,
DiagnosticSeverity minimumSeverity,
CancellationToken cancellationToken)
{
// We only want to run analyzers for each project that have the potential for reporting a diagnostic with
// a severity equal to or greater than specified.
var projectAnalyzers = ImmutableDictionary.CreateBuilder<Project, ImmutableArray<DiagnosticAnalyzer>>();
foreach (var project in projects)
foreach (var project in projectAnalyzersAndFixers.Keys)
{
var analyzers = ImmutableArray.CreateBuilder<DiagnosticAnalyzer>();

// Filter analyzers by project's language
var filteredAnalyzer = allAnalyzers.Where(analyzer => DoesAnalyzerSupportLanguage(analyzer, project.Language));
var filteredAnalyzer = projectAnalyzersAndFixers[project].Analyzers
.Where(analyzer => DoesAnalyzerSupportLanguage(analyzer, project.Language));
foreach (var analyzer in filteredAnalyzer)
{
// Always run naming style analyzers because we cannot determine potential severity.
Expand Down
79 changes: 69 additions & 10 deletions src/Analyzers/AnalyzerReferenceInformationProvider.cs
@@ -1,35 +1,94 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Runtime.Loader;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools.Analyzers
{
internal class AnalyzerReferenceInformationProvider : IAnalyzerInformationProvider
{
public (ImmutableArray<DiagnosticAnalyzer> Analyzers, ImmutableArray<CodeFixProvider> Fixers) GetAnalyzersAndFixers(
public ImmutableDictionary<Project, AnalyzersAndFixers> GetAnalyzersAndFixers(
Solution solution,
FormatOptions formatOptions,
ILogger logger)
{
if (!formatOptions.FixAnalyzers)
{
return (ImmutableArray<DiagnosticAnalyzer>.Empty, ImmutableArray<CodeFixProvider>.Empty);
return ImmutableDictionary<Project, AnalyzersAndFixers>.Empty;
}

var assemblies = solution.Projects
.SelectMany(project => project.AnalyzerReferences.Select(reference => reference.FullPath))
.Distinct()
.Select(path => Assembly.LoadFrom(path));
return solution.Projects
.ToImmutableDictionary(project => project, GetAnalyzersAndFixers);
}

private AnalyzersAndFixers GetAnalyzersAndFixers(Project project)
{
var analyzerAssemblies = project.AnalyzerReferences
.Select(reference => TryLoadAssemblyFrom(reference.FullPath))
.OfType<Assembly>()
.ToImmutableArray();

return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(analyzerAssemblies);
}

private Assembly? TryLoadAssemblyFrom(string? path)
{
// Since we are not deploying these assemblies we need to ensure the files exist.
if (path is null || !File.Exists(path))
{
return null;
}

try
{
var context = new AnalyzerLoadContext(Path.GetDirectoryName(path));

// First try loading the assembly from disk.
return context.LoadFromAssemblyPath(path);
}
catch { }

return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
// Give up.
return null;
}

public DiagnosticSeverity GetSeverity(FormatOptions formatOptions) => formatOptions.AnalyzerSeverity;

internal sealed class AnalyzerLoadContext : AssemblyLoadContext
{
private readonly string _assemblyFolderPath;

public AnalyzerLoadContext(string assemblyFolderPath)
{
_assemblyFolderPath = assemblyFolderPath;
}

protected override Assembly Load(AssemblyName assemblyName)
{
// Since we build against .NET Core 2.1 we do not have access to the
// AssemblyDependencyResolver which resolves depenendency assembly paths
// from AssemblyName by using the .deps.json.

// We will instead do the simplest thing by looking for the requested assembly
// by name in the same folder as the assembly being loaded.
var possibleAssemblyFileName = $"{assemblyName.Name}.dll";
var possibleAssemblyPath = Path.Combine(_assemblyFolderPath, possibleAssemblyFileName);
try
{
if (File.Exists(possibleAssemblyPath))
{
return LoadFromAssemblyPath(possibleAssemblyPath);
}
}
catch { }

// Try to load the requested assembly from the default load context.
return AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName);
}
}
}
}
28 changes: 28 additions & 0 deletions src/Analyzers/AnalyzersAndFixers.cs
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.CodeAnalysis.Tools.Analyzers
{
internal struct AnalyzersAndFixers
{
public ImmutableArray<DiagnosticAnalyzer> Analyzers { get; }
public ImmutableArray<CodeFixProvider> Fixers { get; }

public AnalyzersAndFixers(ImmutableArray<DiagnosticAnalyzer> analyzers, ImmutableArray<CodeFixProvider> fixers)
{
Analyzers = analyzers;
Fixers = fixers;
}

public void Deconstruct(
out ImmutableArray<DiagnosticAnalyzer> analyzers,
out ImmutableArray<CodeFixProvider> fixers)
{
analyzers = Analyzers;
fixers = Fixers;
}
}
}
10 changes: 5 additions & 5 deletions src/Analyzers/CodeStyleInformationProvider.cs
Expand Up @@ -4,8 +4,6 @@
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools.Analyzers
Expand All @@ -18,14 +16,14 @@ internal class CodeStyleInformationProvider : IAnalyzerInformationProvider
private readonly string _featuresCSharpPath = Path.Combine(s_executingPath, "Microsoft.CodeAnalysis.CSharp.Features.dll");
private readonly string _featuresVisualBasicPath = Path.Combine(s_executingPath, "Microsoft.CodeAnalysis.VisualBasic.Features.dll");

public (ImmutableArray<DiagnosticAnalyzer> Analyzers, ImmutableArray<CodeFixProvider> Fixers) GetAnalyzersAndFixers(
public ImmutableDictionary<Project, AnalyzersAndFixers> GetAnalyzersAndFixers(
Solution solution,
FormatOptions options,
ILogger logger)
{
if (!options.FixCodeStyle)
{
return (ImmutableArray<DiagnosticAnalyzer>.Empty, ImmutableArray<CodeFixProvider>.Empty);
return ImmutableDictionary<Project, AnalyzersAndFixers>.Empty;
}

var assemblies = new[]
Expand All @@ -35,7 +33,9 @@ internal class CodeStyleInformationProvider : IAnalyzerInformationProvider
_featuresVisualBasicPath
}.Select(path => Assembly.LoadFrom(path));

return AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
var analyzersAndFixers = AnalyzerFinderHelpers.LoadAnalyzersAndFixers(assemblies);
return solution.Projects
.ToImmutableDictionary(project => project, project => analyzersAndFixers);
}

public DiagnosticSeverity GetSeverity(FormatOptions formatOptions) => formatOptions.CodeStyleSeverity;
Expand Down
4 changes: 1 addition & 3 deletions src/Analyzers/Interfaces/IAnalyzerInformationProvider.cs
@@ -1,8 +1,6 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.Extensions.Logging;

namespace Microsoft.CodeAnalysis.Tools.Analyzers
Expand All @@ -11,7 +9,7 @@ internal interface IAnalyzerInformationProvider
{
DiagnosticSeverity GetSeverity(FormatOptions formatOptions);

(ImmutableArray<DiagnosticAnalyzer> Analyzers, ImmutableArray<CodeFixProvider> Fixers) GetAnalyzersAndFixers(
ImmutableDictionary<Project, AnalyzersAndFixers> GetAnalyzersAndFixers(
Solution solution,
FormatOptions formatOptions,
ILogger logger);
Expand Down

0 comments on commit f973fe3

Please sign in to comment.