diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs index 9f5bb5b0bd4a5f..cc3cde0f26112f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs @@ -101,7 +101,13 @@ internal static bool IsInRequiresScope(this MethodDesc method, string requiresAt return true; if (method.OwningType is TypeDesc type && TryGetRequiresAttribute(type, requiresAttribute, out attribute)) - return true; + { + if (!ExcludeStatics(attribute.Value)) + return true; + + if (!method.Signature.IsStatic) + return true; + } if (method.GetPropertyForAccessor() is PropertyPseudoDesc property && TryGetRequiresAttribute(property, requiresAttribute, out attribute)) return true; @@ -123,7 +129,13 @@ internal static bool DoesMethodRequire(this MethodDesc method, string requiresAt if ((method.Signature.IsStatic || method.IsConstructor) && method.OwningType is TypeDesc owningType && !owningType.IsArray && TryGetRequiresAttribute(owningType, requiresAttribute, out attribute)) - return true; + { + if (!ExcludeStatics(attribute.Value)) + return true; + + if (method.IsConstructor) + return true; + } if (method.GetPropertyForAccessor() is PropertyPseudoDesc @property && TryGetRequiresAttribute(@property, requiresAttribute, out attribute)) @@ -144,7 +156,7 @@ internal static bool DoesFieldRequire(this FieldDesc field, string requiresAttri return false; } - return TryGetRequiresAttribute(field.OwningType, requiresAttribute, out attribute); + return TryGetRequiresAttribute(field.OwningType, requiresAttribute, out attribute) && !ExcludeStatics(attribute.Value); } internal static bool DoesPropertyRequire(this PropertyPseudoDesc property, string requiresAttribute, [NotNullWhen(returnValue: true)] out CustomAttributeValue? attribute) => @@ -174,6 +186,20 @@ internal static bool DoesMemberRequire(this TypeSystemEntity member, string requ }; } + private static bool ExcludeStatics(CustomAttributeValue attribute) + { + foreach (var namedArgument in attribute.NamedArguments) + { + if (namedArgument.Name == "ExcludeStatics" && + namedArgument.Value is bool excludeStatics && + excludeStatics) + { + return true; + } + } + return false; + } + internal const string RequiresUnreferencedCodeAttribute = nameof(RequiresUnreferencedCodeAttribute); internal const string RequiresDynamicCodeAttribute = nameof(RequiresDynamicCodeAttribute); internal const string RequiresAssemblyFilesAttribute = nameof(RequiresAssemblyFilesAttribute); diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs index 2bbc6f55408258..b5de71ed3bb079 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs @@ -60,6 +60,8 @@ class LinkedMethodEntity : LinkedEntity ".MainMethodWrapper()", ".MainMethodWrapper(String[])", "System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute.__GetFieldHelper(Int32,MethodTable*&)", + "System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute.__GetFieldHelper(Int32,MethodTable*&)", + "System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute.__GetFieldHelper(Int32,MethodTable*&)", "System.Runtime.InteropServices.TypeMapping", "System.Runtime.InteropServices.TypeMapping.GetOrCreateExternalTypeMapping()", "System.Runtime.InteropServices.TypeMapping.GetOrCreateProxyTypeMapping()", diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs index f44456c12efd7c..f8fe337cb5cd34 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs @@ -138,6 +138,12 @@ public IEnumerable GetCommonSourceFiles() .Combine("Support") .Combine("DynamicallyAccessedMembersAttribute.cs"); yield return dam; + + var sharedDir = _testCase.RootCasesDirectory.Parent.Parent + .Combine("src") + .Combine("ILLink.Shared"); + yield return sharedDir.Combine("RequiresDynamicCodeAttribute.cs"); + yield return sharedDir.Combine("RequiresUnreferencedCodeAttribute.cs"); } public virtual IEnumerable GetCommonReferencedAssemblies(NPath workingDirectory) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresISymbolExtensions.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresISymbolExtensions.cs index 7189f424441b1f..cf2e83f4b8f872 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresISymbolExtensions.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresISymbolExtensions.cs @@ -22,8 +22,14 @@ public static bool DoesMemberRequire(this ISymbol member, string requiresAttribu return true; // Also check the containing type - if (member.IsStatic || member.IsConstructor()) - return member.ContainingType.TryGetAttribute(requiresAttribute, out requiresAttributeData); + if ((member.IsStatic || member.IsConstructor()) && member.ContainingType.TryGetAttribute(requiresAttribute, out requiresAttributeData)) + { + if (!ExcludeStatics(requiresAttributeData)) + return true; + + if (member.IsConstructor()) + return true; + } return false; } @@ -33,6 +39,18 @@ public static bool IsInRequiresScope(this ISymbol member, string attributeName) return member.IsInRequiresScope(attributeName, out _); } + private static bool ExcludeStatics(AttributeData attributeData) + { + foreach (var namedArg in attributeData.NamedArguments) + { + if (namedArg.Key == "ExcludeStatics" && namedArg.Value.Value is bool b) + { + return b; + } + } + return false; + } + // TODO: Consider sharing with ILLink IsInRequiresScope method /// /// True if the source of a call is considered to be annotated with the Requires... attribute @@ -58,7 +76,13 @@ public static bool IsInRequiresScope(this ISymbol member, string attributeName, } if (member.ContainingType is ITypeSymbol containingType && containingType.TryGetAttribute(attributeName, out requiresAttribute)) - return true; + { + if (!ExcludeStatics(requiresAttribute)) + return true; + + if (!member.IsStatic) + return true; + } if (member is IMethodSymbol { AssociatedSymbol: { } associated } && associated.TryGetAttribute(attributeName, out requiresAttribute)) return true; diff --git a/src/tools/illink/src/ILLink.Shared/RequiresDynamicCodeAttribute.cs b/src/tools/illink/src/ILLink.Shared/RequiresDynamicCodeAttribute.cs new file mode 100644 index 00000000000000..34768f4d9bb61c --- /dev/null +++ b/src/tools/illink/src/ILLink.Shared/RequiresDynamicCodeAttribute.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if INCLUDE_EXPECTATIONS +using Mono.Linker.Tests.Cases.Expectations.Assertions; +#endif + +#nullable enable + +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Indicates that the specified method requires the ability to generate new code at runtime, + /// for example through . + /// + /// + /// This allows tools to understand which methods are unsafe to call when compiling ahead of time. + /// +#if INCLUDE_EXPECTATIONS + [SkipKeptItemsValidation] +#endif + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class RequiresDynamicCodeAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified message. + /// + /// + /// A message that contains information about the usage of dynamic code. + /// + public RequiresDynamicCodeAttribute(string message) + { + Message = message; + } + + /// + /// Indicates whether the attribute should apply to static members. + /// + public bool ExcludeStatics { get; set; } + + /// + /// Gets a message that contains information about the usage of dynamic code. + /// + public string Message { get; } + + /// + /// Gets or sets an optional URL that contains more information about the method, + /// why it requires dynamic code, and what options a consumer has to deal with it. + /// + public string? Url { get; set; } + } +} diff --git a/src/tools/illink/src/ILLink.Shared/RequiresUnreferencedCodeAttribute.cs b/src/tools/illink/src/ILLink.Shared/RequiresUnreferencedCodeAttribute.cs new file mode 100644 index 00000000000000..5c2c9ce96d7266 --- /dev/null +++ b/src/tools/illink/src/ILLink.Shared/RequiresUnreferencedCodeAttribute.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if INCLUDE_EXPECTATIONS +using Mono.Linker.Tests.Cases.Expectations.Assertions; +#endif + +#nullable enable + +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Indicates that the specified method requires dynamic access to code that is not referenced + /// statically, for example through . + /// + /// + /// This allows tools to understand which methods are unsafe to call when removing unreferenced + /// code from an application. + /// +#if INCLUDE_EXPECTATIONS + [SkipKeptItemsValidation] +#endif + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class RequiresUnreferencedCodeAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified message. + /// + /// + /// A message that contains information about the usage of unreferenced code. + /// + public RequiresUnreferencedCodeAttribute(string message) + { + Message = message; + } + + /// + /// Indicates whether the attribute should apply to static members. + /// + public bool ExcludeStatics { get; set; } + + /// + /// Gets a message that contains information about the usage of unreferenced code. + /// + public string Message { get; } + + /// + /// Gets or sets an optional URL that contains more information about the method, + /// why it requires unreferenced code, and what options a consumer has to deal with it. + /// + public string? Url { get; set; } + } +} diff --git a/src/tools/illink/src/linker/Linker/Annotations.cs b/src/tools/illink/src/linker/Linker/Annotations.cs index df777fc7901b5c..1518bd5407c088 100644 --- a/src/tools/illink/src/linker/Linker/Annotations.cs +++ b/src/tools/illink/src/linker/Linker/Annotations.cs @@ -626,7 +626,13 @@ internal bool IsInRequiresUnreferencedCodeScope(MethodDefinition method, [NotNul return true; if (method.DeclaringType is not null && TryGetLinkerAttribute(method.DeclaringType, out attribute)) - return true; + { + if (!attribute.ExcludeStatics) + return true; + + if (!method.IsStatic) + return true; + } attribute = null; return false; @@ -676,7 +682,13 @@ internal bool DoesMethodRequireUnreferencedCode(MethodDefinition originalMethod, if ((method.IsStatic || method.IsConstructor) && method.DeclaringType is not null && TryGetLinkerAttribute(method.DeclaringType, out attribute)) - return true; + { + if (!attribute.ExcludeStatics) + return true; + + if (method.IsConstructor) + return true; + } } while (context.CompilerGeneratedState.TryGetOwningMethodForCompilerGeneratedMember(method, out method)); attribute = null; @@ -691,7 +703,7 @@ internal bool DoesFieldRequireUnreferencedCode(FieldDefinition field, [NotNullWh return false; } - return TryGetLinkerAttribute(field.DeclaringType, out attribute); + return TryGetLinkerAttribute(field.DeclaringType, out attribute) && !attribute.ExcludeStatics; } /// diff --git a/src/tools/illink/src/linker/Linker/LinkerAttributesInformation.cs b/src/tools/illink/src/linker/Linker/LinkerAttributesInformation.cs index 1aa5abe6a0c217..c343ff3a1ede44 100644 --- a/src/tools/illink/src/linker/Linker/LinkerAttributesInformation.cs +++ b/src/tools/illink/src/linker/Linker/LinkerAttributesInformation.cs @@ -120,20 +120,28 @@ public IEnumerable GetAttributes() where T : Attribute if (customAttribute.HasConstructorArguments && customAttribute.ConstructorArguments[0].Value is string message) { - var ruca = new RequiresUnreferencedCodeAttribute(message); + string? url = null; + bool excludeStatics = false; if (customAttribute.HasProperties) { foreach (var prop in customAttribute.Properties) { if (prop.Name == "Url") { - ruca.Url = prop.Argument.Value as string; - break; + url = prop.Argument.Value as string; + } + else if (prop.Name == "ExcludeStatics" && prop.Argument.Value is true) + { + excludeStatics = true; } } } - return ruca; + return new RequiresUnreferencedCodeAttribute(message) + { + Url = url, + ExcludeStatics = excludeStatics + }; } context.LogWarning((IMemberDefinition)provider, DiagnosticId.AttributeDoesntHaveTheRequiredNumberOfParameters, typeof(RequiresUnreferencedCodeAttribute).FullName ?? ""); diff --git a/src/tools/illink/src/linker/Mono.Linker.csproj b/src/tools/illink/src/linker/Mono.Linker.csproj index af73b43ef4cdcf..35293cd7078a82 100644 --- a/src/tools/illink/src/linker/Mono.Linker.csproj +++ b/src/tools/illink/src/linker/Mono.Linker.csproj @@ -21,6 +21,8 @@ Major false $(NoWarn);NU5131 + + $(NoWarn);CS0436 $(TargetsForTfmSpecificContentInPackage);_AddReferenceAssemblyToPackage $(DefineConstants);ILLINK true diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresCapabilityTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresCapabilityTests.cs index ec1b99aef0f068..da78b488d0c643 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresCapabilityTests.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresCapabilityTests.cs @@ -34,6 +34,12 @@ public Task RequiresAttributeMismatch() return RunTest(nameof(RequiresAttributeMismatch)); } + [Fact] + public Task RequiresExcludeStatics() + { + return RunTest(); + } + [Fact] public Task RequiresCapabilityFromCopiedAssembly() { diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs index 5181d88744efb6..a7a0453742adb9 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs @@ -45,11 +45,20 @@ public static (CompilationWithAnalyzers Compilation, SemanticModel SemanticModel var sources = new List() { src }; sources.AddRange(additionalSources ?? Array.Empty()); TestCaseUtils.GetDirectoryPaths(out string rootSourceDirectory); - var commonSourcePath = Path.Combine(Path.GetDirectoryName(rootSourceDirectory)!, - "Mono.Linker.Tests.Cases.Expectations", - "Support", - "DynamicallyAccessedMembersAttribute.cs"); - sources.Add(CSharpSyntaxTree.ParseText(File.ReadAllText(commonSourcePath), path: commonSourcePath)); + var testDir = Path.GetDirectoryName(rootSourceDirectory)!; + var srcDir = Path.Combine(Path.GetDirectoryName(testDir)!, "src"); + var sharedDir = Path.Combine(srcDir, "ILLink.Shared"); + var commonSourcePaths = new List() + { + Path.Combine(testDir, + "Mono.Linker.Tests.Cases.Expectations", + "Support", + "DynamicallyAccessedMembersAttribute.cs"), + Path.Combine(sharedDir, "RequiresUnreferencedCodeAttribute.cs"), + Path.Combine(sharedDir, "RequiresDynamicCodeAttribute.cs"), + }; + + sources.AddRange(commonSourcePaths.Select(p => CSharpSyntaxTree.ParseText(File.ReadAllText(p), path: p))); var comp = CSharpCompilation.Create( assemblyName: Guid.NewGuid().ToString("N"), syntaxTrees: sources, diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs index 7de145d1423032..baa3a5b96831b0 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs @@ -58,7 +58,7 @@ public static async Task RunTestFile(string suiteName, string testName, bool all testCaseDir = testSuiteDir; testPath = Path.Combine(testSuiteDir, $"{testName}.cs"); } - Assert.True(File.Exists(testPath)); + Assert.True(File.Exists(testPath), $"{testPath} should exist"); var tree = SyntaxFactory.ParseSyntaxTree( SourceText.From(File.OpenRead(testPath), Encoding.UTF8), path: testPath); diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj index 09d87f8f83f020..6211174e391459 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases.Expectations/Mono.Linker.Tests.Cases.Expectations.csproj @@ -5,5 +5,7 @@ + + diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj b/src/tools/illink/test/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj index ccc1d915f1515c..c2f17e501e555c 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Mono.Linker.Tests.Cases.csproj @@ -68,6 +68,8 @@ + + diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresExcludeStatics.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresExcludeStatics.cs new file mode 100644 index 00000000000000..a7651b044b7903 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresExcludeStatics.cs @@ -0,0 +1,233 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Reflection; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; + +namespace Mono.Linker.Tests.Cases.RequiresCapability +{ + [SkipKeptItemsValidation] + [ExpectedNoWarnings] + class RequiresExcludeStatics + { + public static void Main() + { + ClassWithRequires.Test(); + DerivedWithRequiresExcludeStatics.Test(); + DerivedWithoutRequires.Test(); + TestDerivedWithRequires(); + TestAttributeWithRequires(); + GenericWithRequires.Test(); + } + + [RequiresUnreferencedCode("--ClassWithRequires--", ExcludeStatics = true)] + [RequiresDynamicCode("--ClassWithRequires--", ExcludeStatics = true)] + class ClassWithRequires + { + [ExpectedWarning("IL2026", "--Requires--")] + [ExpectedWarning("IL3050", "--Requires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + static ClassWithRequires() + { + Requires(); + } + + [ExpectedWarning("IL2026", "--Requires--")] + [ExpectedWarning("IL3050", "--Requires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + static void StaticMethod() => Requires(); + + [RequiresUnreferencedCode("--AnnotatedStaticMethod--")] + [RequiresDynamicCode("--AnnotatedStaticMethod--")] + static void AnnotatedStaticMethod() => Requires(); + + [RequiresUnreferencedCode("--AnnotatedStaticMethodExcludeStatics--", ExcludeStatics = true)] + [RequiresDynamicCode("--AnnotatedStaticMethodExcludeStatics--", ExcludeStatics = true)] + static void AnnotatedStaticMethodExcludeStatics() => Requires(); + + void InstanceMethod() => Requires(); + + static int StaticField; + + int InstanceField; + + static bool StaticProperty + { + [ExpectedWarning("IL2026", "--Requires--")] + [ExpectedWarning("IL3050", "--Requires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + get + { + Requires(); + return true; + } + [ExpectedWarning("IL2026", "--Requires--")] + [ExpectedWarning("IL3050", "--Requires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + set + { + Requires(); + } + } + + bool InstanceProperty + { + get + { + Requires(); + return true; + } + set + { + Requires(); + } + } + + class Nested + { + [ExpectedWarning("IL2026", "--Requires--")] + [ExpectedWarning("IL3050", "--Requires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + public static void StaticMethod() => Requires(); + + [ExpectedWarning("IL2026", "--Requires--")] + [ExpectedWarning("IL3050", "--Requires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + public void InstanceMethod() => Requires(); + } + + [ExpectedWarning("IL2026", "ClassWithRequires.ClassWithRequires()")] + [ExpectedWarning("IL3050", "ClassWithRequires.ClassWithRequires()", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + [ExpectedWarning("IL2026", "--AnnotatedStaticMethod--")] + [ExpectedWarning("IL3050", "--AnnotatedStaticMethod--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + [ExpectedWarning("IL2026", "--AnnotatedStaticMethodExcludeStatics--")] + [ExpectedWarning("IL3050", "--AnnotatedStaticMethodExcludeStatics--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + public static void Test() + { + StaticMethod(); + StaticField = 42; + _ = StaticProperty; + StaticProperty = true; + + AnnotatedStaticMethod(); + AnnotatedStaticMethodExcludeStatics(); + + var instance = new ClassWithRequires(); + instance.InstanceMethod(); + instance.InstanceField = 42; + _ = instance.InstanceProperty; + instance.InstanceProperty = true; + + Nested.StaticMethod(); + var nestedInstance = new Nested(); + nestedInstance.InstanceMethod(); + } + } + + [RequiresUnreferencedCode("--BaseWithRequires--")] + [RequiresDynamicCode("--BaseWithRequires--")] + class BaseWithRequires + { + protected static void StaticMethod() => Requires(); + } + + [RequiresUnreferencedCode("--DerivedWithRequiresExcludeStatics--", ExcludeStatics = true)] + [RequiresDynamicCode("--DerivedWithRequiresExcludeStatics--", ExcludeStatics = true)] + class DerivedWithRequiresExcludeStatics : BaseWithRequires + { + [ExpectedWarning("IL2026", "--Requires--")] + [ExpectedWarning("IL3050", "--Requires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + static void DerivedStaticMethod() => Requires(); + + [ExpectedWarning("IL2026", "StaticMethod", "--BaseWithRequires--")] + [ExpectedWarning("IL3050", "StaticMethod", "--BaseWithRequires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + public static void Test() + { + StaticMethod(); + DerivedStaticMethod(); + } + } + + [UnexpectedWarning("IL2109", "DerivedWithoutRequires", Tool.Analyzer | Tool.Trimmer, "https://github.com/dotnet/runtime/issues/107660")] + class DerivedWithoutRequires : BaseWithRequires + { + [ExpectedWarning("IL2026", "--Requires--")] + [ExpectedWarning("IL3050", "--Requires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + static void DerivedStaticMethod() => Requires(); + + [ExpectedWarning("IL2026", "StaticMethod", "--BaseWithRequires--")] + [ExpectedWarning("IL3050", "StaticMethod", "--BaseWithRequires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + public static void Test() + { + StaticMethod(); + DerivedStaticMethod(); + } + } + + [RequiresUnreferencedCode("--BaseWithRequiresExcludeStatics--", ExcludeStatics = true)] + [RequiresDynamicCode("--BaseWithRequiresExcludeStatics--", ExcludeStatics = true)] + class BaseWithRequiresExcludeStatics + { + [ExpectedWarning("IL2026", "--Requires--")] + [ExpectedWarning("IL3050", "--Requires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + public static void StaticMethod() => Requires(); + } + + [RequiresUnreferencedCode("--DerivedWithRequiresExcludeStatics--")] + [RequiresDynamicCode("--DerivedWithRequiresExcludeStatics--")] + class DerivedWithRequires : BaseWithRequiresExcludeStatics + { + public static void DerivedStaticMethod() => Requires(); + } + + [ExpectedWarning("IL2026", "DerivedWithRequires")] + [ExpectedWarning("IL3050", "DerivedWithRequires", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + static void TestDerivedWithRequires() + { + DerivedWithRequires.StaticMethod(); + DerivedWithRequires.DerivedStaticMethod(); + } + + [RequiresUnreferencedCode("--AttributeWithRequires--", ExcludeStatics = true)] + [RequiresDynamicCode("--AttributeWithRequires--", ExcludeStatics = true)] + class AttributeWithRequiresAttribute : Attribute + { + } + + [ExpectedWarning("IL2026", "--AttributeWithRequires--", Tool.Analyzer | Tool.Trimmer, "https://github.com/dotnet/runtime/issues/117899")] + [ExpectedWarning("IL3050", "--AttributeWithRequires--", Tool.Analyzer, "NativeAOT Specific warning, https://github.com/dotnet/runtime/issues/117899")] + [AttributeWithRequires] + static void TestAttributeWithRequires() + { + } + + [RequiresUnreferencedCode("--GenericWithRequires--", ExcludeStatics = true)] + [RequiresDynamicCode("--GenericWithRequires--", ExcludeStatics = true)] + class GenericWithRequires + { + class Requires<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] T> + { + } + + [UnexpectedWarning("IL2091", Tool.Trimmer, "https://github.com/dotnet/runtime/issues/113249")] + static Requires StaticField; + + [UnexpectedWarning("IL2091", Tool.Trimmer, "https://github.com/dotnet/runtime/issues/113249")] + Requires InstanceField; + + [ExpectedWarning("IL2091", "PublicFields", "Requires")] + [ExpectedWarning("IL2091", "PublicFields", "Requires")] + [ExpectedWarning("IL2026", "--GenericWithRequires--")] + [ExpectedWarning("IL3050", "--GenericWithRequires--", Tool.Analyzer | Tool.NativeAot, "NativeAOT Specific warning")] + public static void Test() + { + StaticField = new Requires(); + var instance = new GenericWithRequires(); + instance.InstanceField = new Requires(); + } + } + + [RequiresUnreferencedCode("--Requires--")] + [RequiresDynamicCode("--Requires--")] + static void Requires() + { + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnClass.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnClass.cs index ec8d7a1b92a313..967f9b2c6eed39 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnClass.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresOnClass.cs @@ -82,6 +82,8 @@ public static void TestSuppressions(Type[] types) void LocalFunction(int a) { } LocalFunction(2); + + AttributedMethod(); } // The attribute would generate warning, but it is suppressed due to the Requires on the type @@ -1322,7 +1324,6 @@ public class ClassWithRequires { public static RequiresAll field; - // Instance fields get generic warnings but static fields don't. [UnexpectedWarning("IL2091", Tool.Trimmer, "https://github.com/dotnet/runtime/issues/108523")] public RequiresAll instanceField; diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs index 99c745a2731772..4ce4b210d6675b 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs @@ -157,6 +157,12 @@ public IEnumerable GetCommonSourceFiles() .Combine("Mono.Linker.Tests.Cases.Expectations") .Combine("Support") .Combine("DynamicallyAccessedMembersAttribute.cs"); + + var sharedDir = _testCase.RootCasesDirectory.Parent.Parent + .Combine("src") + .Combine("ILLink.Shared"); + yield return sharedDir.Combine("RequiresDynamicCodeAttribute.cs"); + yield return sharedDir.Combine("RequiresUnreferencedCodeAttribute.cs"); } public virtual IEnumerable GetCommonReferencedAssemblies(NPath workingDirectory)