Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion eng/pipelines/runtime-linker-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ extends:
or(
eq(stageDependencies.EvaluatePaths.evaluate_paths.outputs['SetPathVars_tools_illink.containsChange'], true),
eq(variables['isRollingBuild'], true))
buildArgs: -s tools.illinktests -test -c $(_BuildConfig)
# Build libs.sfx subset first so that Roslyn analyzer tests can use live ref pack
buildArgs: -s libs.sfx -c $(_BuildConfig)
postBuildSteps:
- script: $(Build.SourcesDirectory)$(dir)build$(scriptExt) -ci -arch ${{ parameters.archType }} $(_osParameter) -s tools.illinktests -test -c $(_BuildConfig) $(crossArg) $(_officialBuildParameter)
displayName: Run ILLink Tests

#
# Build Release config vertical for Windows, Linux, and OSX
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
<PropertyGroup>
<StrongNameKeyId>Microsoft</StrongNameKeyId>
<IncludePlatformAttributes>true</IncludePlatformAttributes>
<IsAotCompatible>false</IsAotCompatible>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ public static class MSBuildPropertyOptionNames
public const string IncludeAllContentForSelfExtract = nameof(IncludeAllContentForSelfExtract);
public const string EnableTrimAnalyzer = nameof(EnableTrimAnalyzer);
public const string EnableAotAnalyzer = nameof(EnableAotAnalyzer);
public const string VerifyReferenceAotCompatibility = nameof(VerifyReferenceAotCompatibility);
public const string VerifyReferenceTrimCompatibility = nameof(VerifyReferenceTrimCompatibility);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public abstract class RequiresAnalyzerBase : DiagnosticAnalyzer

private protected virtual ImmutableArray<(Action<SyntaxNodeAnalysisContext> Action, SyntaxKind[] SyntaxKind)> ExtraSyntaxNodeActions { get; } = ImmutableArray<(Action<SyntaxNodeAnalysisContext> Action, SyntaxKind[] SyntaxKind)>.Empty;
private protected virtual ImmutableArray<(Action<SymbolAnalysisContext> Action, SymbolKind[] SymbolKind)> ExtraSymbolActions { get; } = ImmutableArray<(Action<SymbolAnalysisContext> Action, SymbolKind[] SymbolKind)>.Empty;
private protected virtual ImmutableArray<Action<CompilationAnalysisContext>> ExtraCompilationActions { get; } = ImmutableArray<Action<CompilationAnalysisContext>>.Empty;

public override void Initialize(AnalysisContext context)
{
Expand Down Expand Up @@ -165,6 +166,9 @@ void CheckMatchingAttributesInInterfaces(
}
}
});

foreach (var extraCompilationAction in ExtraCompilationActions)
context.RegisterCompilationAction(extraCompilationAction);
}

internal void CheckAndCreateRequiresDiagnostic(
Expand Down Expand Up @@ -386,5 +390,38 @@ ImmutableArray<MultiValue> arguments
{
return false;
}

protected void CheckReferencedAssemblies(
CompilationAnalysisContext context,
string msbuildPropertyName,
string assemblyMetadataName,
DiagnosticDescriptor diagnosticDescriptor)
{
var options = context.Options;
if (!IsAnalyzerEnabled(options))
return;

if (!options.IsMSBuildPropertyValueTrue(msbuildPropertyName))
return;

foreach (var reference in context.Compilation.References)
{
var refAssembly = context.Compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (refAssembly is null)
continue;

var assemblyMetadata = refAssembly.GetAttributes().FirstOrDefault(attr =>
attr.AttributeClass?.Name == "AssemblyMetadataAttribute" &&
attr.ConstructorArguments.Length == 2 &&
attr.ConstructorArguments[0].Value?.ToString() == assemblyMetadataName &&
string.Equals(attr.ConstructorArguments[1].Value?.ToString(), "True", StringComparison.OrdinalIgnoreCase));

if (assemblyMetadata is null)
{
var diag = Diagnostic.Create(diagnosticDescriptor, Location.None, refAssembly.Name);
context.ReportDiagnostic(diag);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ public sealed class RequiresDynamicCodeAnalyzer : RequiresAnalyzerBase
private static readonly DiagnosticDescriptor s_requiresDynamicCodeOnEntryPoint = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresDynamicCodeOnEntryPoint);
private static readonly DiagnosticDescriptor s_requiresDynamicCodeRule = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresDynamicCode);
private static readonly DiagnosticDescriptor s_requiresDynamicCodeAttributeMismatch = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresDynamicCodeAttributeMismatch);
private static readonly DiagnosticDescriptor s_referenceNotMarkedIsAotCompatibleRule = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.ReferenceNotMarkedIsAotCompatible);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(s_requiresDynamicCodeRule, s_requiresDynamicCodeAttributeMismatch, s_requiresDynamicCodeOnStaticCtor, s_requiresDynamicCodeOnEntryPoint);
ImmutableArray.Create(s_requiresDynamicCodeRule, s_requiresDynamicCodeAttributeMismatch, s_requiresDynamicCodeOnStaticCtor, s_requiresDynamicCodeOnEntryPoint, s_referenceNotMarkedIsAotCompatibleRule);

private protected override string RequiresAttributeName => RequiresDynamicCodeAttribute;

Expand Down Expand Up @@ -163,6 +164,16 @@ private protected override bool IsRequiresCheck(IPropertySymbol propertySymbol,
return SymbolEqualityComparer.Default.Equals(propertySymbol, isDynamicCodeSupportedProperty);
}

private protected override ImmutableArray<Action<CompilationAnalysisContext>> ExtraCompilationActions =>
ImmutableArray.Create<Action<CompilationAnalysisContext>>((context) =>
{
CheckReferencedAssemblies(
context,
MSBuildPropertyOptionNames.VerifyReferenceAotCompatibility,
"IsAotCompatible",
s_referenceNotMarkedIsAotCompatibleRule);
});

protected override bool VerifyAttributeArguments(AttributeData attribute) =>
attribute.ConstructorArguments.Length >= 1 && attribute.ConstructorArguments is [{ Type.SpecialType: SpecialType.System_String }, ..];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ public sealed class RequiresUnreferencedCodeAnalyzer : RequiresAnalyzerBase
private static readonly DiagnosticDescriptor s_requiresUnreferencedCodeOnStaticCtor = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresUnreferencedCodeOnStaticConstructor);
private static readonly DiagnosticDescriptor s_requiresUnreferencedCodeOnEntryPoint = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.RequiresUnreferencedCodeOnEntryPoint);

private static readonly DiagnosticDescriptor s_referenceNotMarkedIsTrimmableRule = DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.ReferenceNotMarkedIsTrimmable);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(s_makeGenericMethodRule, s_makeGenericTypeRule, s_requiresUnreferencedCodeRule, s_requiresUnreferencedCodeAttributeMismatch, s_requiresUnreferencedCodeOnStaticCtor, s_requiresUnreferencedCodeOnEntryPoint);
ImmutableArray.Create(s_makeGenericMethodRule, s_makeGenericTypeRule, s_requiresUnreferencedCodeRule, s_requiresUnreferencedCodeAttributeMismatch, s_requiresUnreferencedCodeOnStaticCtor, s_requiresUnreferencedCodeOnEntryPoint, s_referenceNotMarkedIsTrimmableRule);

private protected override string RequiresAttributeName => RequiresUnreferencedCodeAttribute;

Expand Down Expand Up @@ -76,6 +78,16 @@ protected override bool CreateSpecialIncompatibleMembersDiagnostic(
return false;
}

private protected override ImmutableArray<Action<CompilationAnalysisContext>> ExtraCompilationActions =>
ImmutableArray.Create<Action<CompilationAnalysisContext>>((context) =>
{
CheckReferencedAssemblies(
context,
MSBuildPropertyOptionNames.VerifyReferenceTrimCompatibility,
"IsTrimmable",
s_referenceNotMarkedIsTrimmableRule);
});

protected override bool VerifyAttributeArguments(AttributeData attribute) =>
RequiresUnreferencedCodeUtils.VerifyRequiresUnreferencedCodeAttributeArguments(attribute);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<Project>
<ItemGroup>
<CompilerVisibleProperty Include="EnableAotAnalyzer"/>
<CompilerVisibleProperty Include="EnableSingleFileAnalyzer"/>
<CompilerVisibleProperty Include="EnableTrimAnalyzer"/>
<CompilerVisibleProperty Include="IncludeAllContentForSelfExtract"/>
<CompilerVisibleProperty Include="EnableAotAnalyzer" />
<CompilerVisibleProperty Include="EnableSingleFileAnalyzer" />
<CompilerVisibleProperty Include="EnableTrimAnalyzer" />
<CompilerVisibleProperty Include="IncludeAllContentForSelfExtract" />
<CompilerVisibleProperty Include="VerifyReferenceTrimCompatibility" />
<CompilerVisibleProperty Include="VerifyReferenceAotCompatibility" />
</ItemGroup>
</Project>
2 changes: 2 additions & 0 deletions src/tools/illink/src/ILLink.Shared/DiagnosticId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ public enum DiagnosticId
TypeNameIsNotAssemblyQualified = 2122,
RequiresUnreferencedCodeOnEntryPoint = 2123,
TypeMapGroupTypeCannotBeStaticallyDetermined = 2124,
ReferenceNotMarkedIsTrimmable = 2125,
_EndTrimAnalysisWarningsSentinel,

// Single-file diagnostic ids.
Expand All @@ -208,6 +209,7 @@ public enum DiagnosticId
CorrectnessOfAbstractDelegatesCannotBeGuaranteed = 3055,
RequiresDynamicCodeOnStaticConstructor = 3056,
RequiresDynamicCodeOnEntryPoint = 3057,
ReferenceNotMarkedIsAotCompatible = 3058,
_EndAotAnalysisWarningsSentinel,

// Feature guard diagnostic ids.
Expand Down
12 changes: 12 additions & 0 deletions src/tools/illink/src/ILLink.Shared/SharedStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1239,4 +1239,16 @@
<data name="TypeNameIsNotAssemblyQualifiedTitle" xml:space="preserve">
<value>Type name is not assembly qualified.</value>
</data>
<data name="ReferenceNotMarkedIsTrimmableTitle" xml:space="preserve">
<value>Referenced assembly is not marked as trimmable.</value>
</data>
<data name="ReferenceNotMarkedIsTrimmableMessage" xml:space="preserve">
<value>Referenced assembly '{0}' is not built with `<IsTrimmable>true</IsTrimmable>` and may not be compatible with trimming.</value>
</data>
<data name="ReferenceNotMarkedIsAotCompatibleTitle" xml:space="preserve">
<value>Referenced assembly is not marked as AOT-compatible.</value>
</data>
<data name="ReferenceNotMarkedIsAotCompatibleMessage" xml:space="preserve">
<value>Referenced assembly '{0}' is not built with `<IsAotCompatible>true</IsAotCompatible>` and may not be compatible with AOT.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,12 @@
<!-- The analyzer tests require the reference assemblies that are compiled against.
Copy them to the output/publish directory so that tests still work on a different
machine (i.e. when testing with Helix). -->
<Target Name="CopyReferenceAssembliesToOutputDir"
DependsOnTargets="FindReferenceAssembliesForReferences"
BeforeTargets="AssignTargetPaths;GetCopyToPublishDirectoryItems">
<ItemGroup>
<None Include="@(ReferencePathWithRefAssemblies->WithMetadataValue('FrameworkReferenceName', 'Microsoft.NETCore.App'))"
CopyToOutputDirectory="PreserveNewest"
CopyToPublishDirectory="PreserveNewest"
Link="live-ref-pack\%(RecursiveDir)%(Filename)%(Extension)"
Visible="false" />
</ItemGroup>
</Target>
<ItemGroup>
<None Include="$(MicrosoftNetCoreAppRefPackRefDir)**/*.dll"
CopyToOutputDirectory="PreserveNewest"
CopyToPublishDirectory="PreserveNewest"
LinkBase="live-ref-pack/"
Visible="false" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using ILLink.Shared;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
using System.IO;
using VerifyCS = ILLink.RoslynAnalyzer.Tests.CSharpCodeFixVerifier<
ILLink.RoslynAnalyzer.RequiresDynamicCodeAnalyzer,
ILLink.CodeFix.RequiresDynamicCodeCodeFixProvider>;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using ILLink.CodeFix;

namespace ILLink.RoslynAnalyzer.Tests
{
public class ReferenceAotCompatibilityTests
{
[Fact]
public async Task EmitsWarningForReferenceWithoutIsAotCompatible_WhenPropertyEnabled()
{
var referencedSource = "public class ReferencedClass { }";
var testSource = "public class MainClass { ReferencedClass c; }";

var test = ReferenceCompatibilityTestUtils.CreateTestWithReference<RequiresDynamicCodeAnalyzer, RequiresDynamicCodeCodeFixProvider>(
testSource, referencedSource);
test.TestState.AnalyzerConfigFiles.Add(("/.editorconfig", Microsoft.CodeAnalysis.Text.SourceText.From($"""
is_global = true
build_property.{ILLink.RoslynAnalyzer.MSBuildPropertyOptionNames.EnableAotAnalyzer} = true
build_property.VerifyReferenceAotCompatibility = true
""")));
test.ExpectedDiagnostics.Add(VerifyCS.Diagnostic(DiagnosticId.ReferenceNotMarkedIsAotCompatible).WithArguments("ReferencedAssembly"));
await test.RunAsync();
}

[Fact]
public async Task DoesNotEmitWarning_WhenVerifyReferenceAotCompatibilityDisabled()
{
var referencedSource = "public class ReferencedClass { }";
var testSource = "public class MainClass { ReferencedClass c; }";

var test = ReferenceCompatibilityTestUtils.CreateTestWithReference<RequiresDynamicCodeAnalyzer, RequiresDynamicCodeCodeFixProvider>(
testSource, referencedSource);
test.TestState.AnalyzerConfigFiles.Add(("/.editorconfig", Microsoft.CodeAnalysis.Text.SourceText.From($"""
is_global = true
build_property.{ILLink.RoslynAnalyzer.MSBuildPropertyOptionNames.EnableAotAnalyzer} = true
build_property.VerifyReferenceAotCompatibility = false
""")));
await test.RunAsync();
}

[Fact]
public async Task DoesNotEmitWarning_WhenReferenceMarkedIsAotCompatible()
{
var referencedSource = """
[assembly: System.Reflection.AssemblyMetadata("IsAotCompatible", "True")]
public class ReferencedClass { }
""";
var testSource = "public class MainClass { ReferencedClass c; }";

var test = ReferenceCompatibilityTestUtils.CreateTestWithReference<RequiresDynamicCodeAnalyzer, RequiresDynamicCodeCodeFixProvider>(
testSource, referencedSource);
test.TestState.AnalyzerConfigFiles.Add(("/.editorconfig", Microsoft.CodeAnalysis.Text.SourceText.From($"""
is_global = true
build_property.{ILLink.RoslynAnalyzer.MSBuildPropertyOptionNames.EnableAotAnalyzer} = true
build_property.VerifyReferenceAotCompatibility = true
""")));
await test.RunAsync();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks;
using ILLink.Shared;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
using System.IO;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace ILLink.RoslynAnalyzer.Tests
{
public class ReferenceCompatibilityTestUtils
{
public static CSharpCodeFixVerifier<TAnalyzer, TCodeFix>.Test CreateTestWithReference<TAnalyzer, TCodeFix>(string mainSource, string referenceSource)
where TAnalyzer : DiagnosticAnalyzer, new()
where TCodeFix : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider, new()
{
var referencedMetadata = CreateReferencedMetadata(referenceSource);
var test = new CSharpCodeFixVerifier<TAnalyzer, TCodeFix>.Test
{
TestCode = mainSource
};
test.SolutionTransforms.Add((solution, projectId) =>
{
var project = solution.GetProject(projectId);
if (project is null)
return solution;
project = project.AddMetadataReference(referencedMetadata);
return project.Solution;
});
return test;

static MetadataReference CreateReferencedMetadata(string referencedSource)
{
var refs = SourceGenerators.Tests.LiveReferencePack.GetMetadataReferences();
var referencedCompilation = CSharpCompilation.Create(
"ReferencedAssembly",
new[] { SyntaxFactory.ParseSyntaxTree(referencedSource) },
refs,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
var referencedImage = new MemoryStream();
referencedCompilation.Emit(referencedImage);
referencedImage.Position = 0;
return MetadataReference.CreateFromStream(referencedImage);
}
}
}
}
Loading
Loading