diff --git a/eng/pipelines/runtime-linker-tests.yml b/eng/pipelines/runtime-linker-tests.yml index 9df8042e7af0af..eaf4c68ec9799c 100644 --- a/eng/pipelines/runtime-linker-tests.yml +++ b/eng/pipelines/runtime-linker-tests.yml @@ -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 diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/Directory.Build.props b/src/libraries/System.Runtime.InteropServices.JavaScript/Directory.Build.props index 4733ac5e517b31..d68d22c1b917f8 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/Directory.Build.props +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/Directory.Build.props @@ -3,6 +3,5 @@ Microsoft true - false \ No newline at end of file diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.cs index e86d0b9d8486e2..51a5645b4318c6 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/MSBuildPropertyOptionNames.cs @@ -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); } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs index 444c0ff3ff7a0f..a8b010bdc292d7 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs @@ -36,6 +36,7 @@ public abstract class RequiresAnalyzerBase : DiagnosticAnalyzer private protected virtual ImmutableArray<(Action Action, SyntaxKind[] SyntaxKind)> ExtraSyntaxNodeActions { get; } = ImmutableArray<(Action Action, SyntaxKind[] SyntaxKind)>.Empty; private protected virtual ImmutableArray<(Action Action, SymbolKind[] SymbolKind)> ExtraSymbolActions { get; } = ImmutableArray<(Action Action, SymbolKind[] SymbolKind)>.Empty; + private protected virtual ImmutableArray> ExtraCompilationActions { get; } = ImmutableArray>.Empty; public override void Initialize(AnalysisContext context) { @@ -165,6 +166,9 @@ void CheckMatchingAttributesInInterfaces( } } }); + + foreach (var extraCompilationAction in ExtraCompilationActions) + context.RegisterCompilationAction(extraCompilationAction); } internal void CheckAndCreateRequiresDiagnostic( @@ -386,5 +390,38 @@ ImmutableArray 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); + } + } + } } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresDynamicCodeAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresDynamicCodeAnalyzer.cs index 8e089da80d58ea..a8b6714fb8a282 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresDynamicCodeAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresDynamicCodeAnalyzer.cs @@ -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 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; @@ -163,6 +164,16 @@ private protected override bool IsRequiresCheck(IPropertySymbol propertySymbol, return SymbolEqualityComparer.Default.Equals(propertySymbol, isDynamicCodeSupportedProperty); } + private protected override ImmutableArray> ExtraCompilationActions => + ImmutableArray.Create>((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 }, ..]; diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs index e60eb128484b0d..09d2625d56b0aa 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs @@ -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 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; @@ -76,6 +78,16 @@ protected override bool CreateSpecialIncompatibleMembersDiagnostic( return false; } + private protected override ImmutableArray> ExtraCompilationActions => + ImmutableArray.Create>((context) => + { + CheckReferencedAssemblies( + context, + MSBuildPropertyOptionNames.VerifyReferenceTrimCompatibility, + "IsTrimmable", + s_referenceNotMarkedIsTrimmableRule); + }); + protected override bool VerifyAttributeArguments(AttributeData attribute) => RequiresUnreferencedCodeUtils.VerifyRequiresUnreferencedCodeAttributeArguments(attribute); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props b/src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props index 3284f19567179d..18ae6cb8fd5e8b 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/build/Microsoft.NET.ILLink.Analyzers.props @@ -1,8 +1,10 @@ - - - - + + + + + + diff --git a/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs b/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs index 415f7b6812c56d..364a7974700a98 100644 --- a/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs +++ b/src/tools/illink/src/ILLink.Shared/DiagnosticId.cs @@ -189,6 +189,7 @@ public enum DiagnosticId TypeNameIsNotAssemblyQualified = 2122, RequiresUnreferencedCodeOnEntryPoint = 2123, TypeMapGroupTypeCannotBeStaticallyDetermined = 2124, + ReferenceNotMarkedIsTrimmable = 2125, _EndTrimAnalysisWarningsSentinel, // Single-file diagnostic ids. @@ -208,6 +209,7 @@ public enum DiagnosticId CorrectnessOfAbstractDelegatesCannotBeGuaranteed = 3055, RequiresDynamicCodeOnStaticConstructor = 3056, RequiresDynamicCodeOnEntryPoint = 3057, + ReferenceNotMarkedIsAotCompatible = 3058, _EndAotAnalysisWarningsSentinel, // Feature guard diagnostic ids. diff --git a/src/tools/illink/src/ILLink.Shared/SharedStrings.resx b/src/tools/illink/src/ILLink.Shared/SharedStrings.resx index 40c921ca706cde..aaa57abe34b60b 100644 --- a/src/tools/illink/src/ILLink.Shared/SharedStrings.resx +++ b/src/tools/illink/src/ILLink.Shared/SharedStrings.resx @@ -1239,4 +1239,16 @@ Type name is not assembly qualified. + + Referenced assembly is not marked as trimmable. + + + Referenced assembly '{0}' is not built with `true` and may not be compatible with trimming. + + + Referenced assembly is not marked as AOT-compatible. + + + Referenced assembly '{0}' is not built with `true` and may not be compatible with AOT. + diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj index 8323dd02f82afd..184616fc012c10 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ILLink.RoslynAnalyzer.Tests.csproj @@ -37,16 +37,12 @@ - - - - - + + + diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceAotCompatibilityTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceAotCompatibilityTests.cs new file mode 100644 index 00000000000000..e52afef7b692d2 --- /dev/null +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceAotCompatibilityTests.cs @@ -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( + 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( + 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( + 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(); + } + } +} diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceCompatibilityTestUtils.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceCompatibilityTestUtils.cs new file mode 100644 index 00000000000000..a5169775cc45bc --- /dev/null +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceCompatibilityTestUtils.cs @@ -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.Test CreateTestWithReference(string mainSource, string referenceSource) + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider, new() + { + var referencedMetadata = CreateReferencedMetadata(referenceSource); + var test = new CSharpCodeFixVerifier.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); + } + } + } +} diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceTrimCompatibilityTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceTrimCompatibilityTests.cs new file mode 100644 index 00000000000000..0a0951e51afc77 --- /dev/null +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceTrimCompatibilityTests.cs @@ -0,0 +1,78 @@ +// 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 ILLink.CodeFix; +using VerifyCS = ILLink.RoslynAnalyzer.Tests.CSharpCodeFixVerifier< + ILLink.RoslynAnalyzer.RequiresUnreferencedCodeAnalyzer, + ILLink.CodeFix.RequiresUnreferencedCodeCodeFixProvider>; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace ILLink.RoslynAnalyzer.Tests +{ + public class ReferenceTrimCompatibilityTests + { + [Fact] + public async Task EmitsWarningForReferenceWithoutIsTrimmable_WhenPropertyEnabled() + { + var referencedSource = "public class ReferencedClass { }"; + var testSource = "public class MainClass { ReferencedClass c; }"; + + var test = ReferenceCompatibilityTestUtils.CreateTestWithReference( + testSource, referencedSource); + test.TestState.AnalyzerConfigFiles.Add(("/.editorconfig", Microsoft.CodeAnalysis.Text.SourceText.From($""" + is_global = true + build_property.{ILLink.RoslynAnalyzer.MSBuildPropertyOptionNames.EnableTrimAnalyzer} = true + build_property.{ILLink.RoslynAnalyzer.MSBuildPropertyOptionNames.VerifyReferenceTrimCompatibility} = true + """))); + test.ExpectedDiagnostics.Add(VerifyCS.Diagnostic(DiagnosticId.ReferenceNotMarkedIsTrimmable).WithArguments("ReferencedAssembly")); + await test.RunAsync(); + } + + [Fact] + public async Task DoesNotEmitWarning_WhenVerifyReferenceTrimCompatibilityDisabled() + { + var referencedSource = "public class ReferencedClass { }"; + var testSource = "public class MainClass { ReferencedClass c; }"; + + var test = ReferenceCompatibilityTestUtils.CreateTestWithReference( + testSource, referencedSource); + test.TestState.AnalyzerConfigFiles.Add(("/.editorconfig", Microsoft.CodeAnalysis.Text.SourceText.From($""" + is_global = true + build_property.{ILLink.RoslynAnalyzer.MSBuildPropertyOptionNames.EnableTrimAnalyzer} = true + build_property.{ILLink.RoslynAnalyzer.MSBuildPropertyOptionNames.VerifyReferenceTrimCompatibility} = false + """))); + + await test.RunAsync(); + } + + [Fact] + public async Task DoesNotEmitWarning_WhenReferenceMarkedIsTrimmable() + { + var referencedSource = """ + [assembly: System.Reflection.AssemblyMetadata("IsTrimmable", "True")] + public class ReferencedClass { } + """; + var testSource = "public class MainClass { ReferencedClass c; }"; + + var test = ReferenceCompatibilityTestUtils.CreateTestWithReference( + testSource, referencedSource); + test.TestState.AnalyzerConfigFiles.Add(("/.editorconfig", Microsoft.CodeAnalysis.Text.SourceText.From($""" + is_global = true + build_property.{ILLink.RoslynAnalyzer.MSBuildPropertyOptionNames.EnableTrimAnalyzer} = true + build_property.{ILLink.RoslynAnalyzer.MSBuildPropertyOptionNames.VerifyReferenceTrimCompatibility} = true + """))); + + await test.RunAsync(); + } + } +}