Skip to content

Commit b019cbd

Browse files
sbomerCopilot
andauthored
Add ExcludeStatics support to trimming tools (#117904)
Adds ILLink/ILC/analyzer implementation for ExcludeStatics on RequiresUnreferencedCode and RequiresDynamicCode. --------- Co-authored-by: Copilot <[email protected]>
1 parent 115a89d commit b019cbd

File tree

17 files changed

+476
-20
lines changed

17 files changed

+476
-20
lines changed

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,13 @@ internal static bool IsInRequiresScope(this MethodDesc method, string requiresAt
101101
return true;
102102

103103
if (method.OwningType is TypeDesc type && TryGetRequiresAttribute(type, requiresAttribute, out attribute))
104-
return true;
104+
{
105+
if (!ExcludeStatics(attribute.Value))
106+
return true;
107+
108+
if (!method.Signature.IsStatic)
109+
return true;
110+
}
105111

106112
if (method.GetPropertyForAccessor() is PropertyPseudoDesc property && TryGetRequiresAttribute(property, requiresAttribute, out attribute))
107113
return true;
@@ -123,7 +129,13 @@ internal static bool DoesMethodRequire(this MethodDesc method, string requiresAt
123129

124130
if ((method.Signature.IsStatic || method.IsConstructor) && method.OwningType is TypeDesc owningType &&
125131
!owningType.IsArray && TryGetRequiresAttribute(owningType, requiresAttribute, out attribute))
126-
return true;
132+
{
133+
if (!ExcludeStatics(attribute.Value))
134+
return true;
135+
136+
if (method.IsConstructor)
137+
return true;
138+
}
127139

128140
if (method.GetPropertyForAccessor() is PropertyPseudoDesc @property
129141
&& TryGetRequiresAttribute(@property, requiresAttribute, out attribute))
@@ -144,7 +156,7 @@ internal static bool DoesFieldRequire(this FieldDesc field, string requiresAttri
144156
return false;
145157
}
146158

147-
return TryGetRequiresAttribute(field.OwningType, requiresAttribute, out attribute);
159+
return TryGetRequiresAttribute(field.OwningType, requiresAttribute, out attribute) && !ExcludeStatics(attribute.Value);
148160
}
149161

150162
internal static bool DoesPropertyRequire(this PropertyPseudoDesc property, string requiresAttribute, [NotNullWhen(returnValue: true)] out CustomAttributeValue<TypeDesc>? attribute) =>
@@ -174,6 +186,20 @@ internal static bool DoesMemberRequire(this TypeSystemEntity member, string requ
174186
};
175187
}
176188

189+
private static bool ExcludeStatics(CustomAttributeValue<TypeDesc> attribute)
190+
{
191+
foreach (var namedArgument in attribute.NamedArguments)
192+
{
193+
if (namedArgument.Name == "ExcludeStatics" &&
194+
namedArgument.Value is bool excludeStatics &&
195+
excludeStatics)
196+
{
197+
return true;
198+
}
199+
}
200+
return false;
201+
}
202+
177203
internal const string RequiresUnreferencedCodeAttribute = nameof(RequiresUnreferencedCodeAttribute);
178204
internal const string RequiresDynamicCodeAttribute = nameof(RequiresDynamicCodeAttribute);
179205
internal const string RequiresAssemblyFilesAttribute = nameof(RequiresAssemblyFilesAttribute);

src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/AssemblyChecker.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ class LinkedMethodEntity : LinkedEntity
6060
"<Module>.MainMethodWrapper()",
6161
"<Module>.MainMethodWrapper(String[])",
6262
"System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute.__GetFieldHelper(Int32,MethodTable*&)",
63+
"System.Diagnostics.CodeAnalysis.RequiresDynamicCodeAttribute.__GetFieldHelper(Int32,MethodTable*&)",
64+
"System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute.__GetFieldHelper(Int32,MethodTable*&)",
6365
"System.Runtime.InteropServices.TypeMapping",
6466
"System.Runtime.InteropServices.TypeMapping.GetOrCreateExternalTypeMapping<TTypeMapGroup>()",
6567
"System.Runtime.InteropServices.TypeMapping.GetOrCreateProxyTypeMapping<TTypeMapGroup>()",

src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/TestCaseCompilationMetadataProvider.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ public IEnumerable<NPath> GetCommonSourceFiles()
138138
.Combine("Support")
139139
.Combine("DynamicallyAccessedMembersAttribute.cs");
140140
yield return dam;
141+
142+
var sharedDir = _testCase.RootCasesDirectory.Parent.Parent
143+
.Combine("src")
144+
.Combine("ILLink.Shared");
145+
yield return sharedDir.Combine("RequiresDynamicCodeAttribute.cs");
146+
yield return sharedDir.Combine("RequiresUnreferencedCodeAttribute.cs");
141147
}
142148

143149
public virtual IEnumerable<string> GetCommonReferencedAssemblies(NPath workingDirectory)

src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresISymbolExtensions.cs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,14 @@ public static bool DoesMemberRequire(this ISymbol member, string requiresAttribu
2222
return true;
2323

2424
// Also check the containing type
25-
if (member.IsStatic || member.IsConstructor())
26-
return member.ContainingType.TryGetAttribute(requiresAttribute, out requiresAttributeData);
25+
if ((member.IsStatic || member.IsConstructor()) && member.ContainingType.TryGetAttribute(requiresAttribute, out requiresAttributeData))
26+
{
27+
if (!ExcludeStatics(requiresAttributeData))
28+
return true;
29+
30+
if (member.IsConstructor())
31+
return true;
32+
}
2733

2834
return false;
2935
}
@@ -33,6 +39,18 @@ public static bool IsInRequiresScope(this ISymbol member, string attributeName)
3339
return member.IsInRequiresScope(attributeName, out _);
3440
}
3541

42+
private static bool ExcludeStatics(AttributeData attributeData)
43+
{
44+
foreach (var namedArg in attributeData.NamedArguments)
45+
{
46+
if (namedArg.Key == "ExcludeStatics" && namedArg.Value.Value is bool b)
47+
{
48+
return b;
49+
}
50+
}
51+
return false;
52+
}
53+
3654
// TODO: Consider sharing with ILLink IsInRequiresScope method
3755
/// <summary>
3856
/// 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,
5876
}
5977

6078
if (member.ContainingType is ITypeSymbol containingType && containingType.TryGetAttribute(attributeName, out requiresAttribute))
61-
return true;
79+
{
80+
if (!ExcludeStatics(requiresAttribute))
81+
return true;
82+
83+
if (!member.IsStatic)
84+
return true;
85+
}
6286

6387
if (member is IMethodSymbol { AssociatedSymbol: { } associated } && associated.TryGetAttribute(attributeName, out requiresAttribute))
6488
return true;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#if INCLUDE_EXPECTATIONS
5+
using Mono.Linker.Tests.Cases.Expectations.Assertions;
6+
#endif
7+
8+
#nullable enable
9+
10+
namespace System.Diagnostics.CodeAnalysis
11+
{
12+
/// <summary>
13+
/// Indicates that the specified method requires the ability to generate new code at runtime,
14+
/// for example through <see cref="Reflection"/>.
15+
/// </summary>
16+
/// <remarks>
17+
/// This allows tools to understand which methods are unsafe to call when compiling ahead of time.
18+
/// </remarks>
19+
#if INCLUDE_EXPECTATIONS
20+
[SkipKeptItemsValidation]
21+
#endif
22+
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)]
23+
#if SYSTEM_PRIVATE_CORELIB
24+
public
25+
#else
26+
internal
27+
#endif
28+
sealed class RequiresDynamicCodeAttribute : Attribute
29+
{
30+
/// <summary>
31+
/// Initializes a new instance of the <see cref="RequiresDynamicCodeAttribute"/> class
32+
/// with the specified message.
33+
/// </summary>
34+
/// <param name="message">
35+
/// A message that contains information about the usage of dynamic code.
36+
/// </param>
37+
public RequiresDynamicCodeAttribute(string message)
38+
{
39+
Message = message;
40+
}
41+
42+
/// <summary>
43+
/// Indicates whether the attribute should apply to static members.
44+
/// </summary>
45+
public bool ExcludeStatics { get; set; }
46+
47+
/// <summary>
48+
/// Gets a message that contains information about the usage of dynamic code.
49+
/// </summary>
50+
public string Message { get; }
51+
52+
/// <summary>
53+
/// Gets or sets an optional URL that contains more information about the method,
54+
/// why it requires dynamic code, and what options a consumer has to deal with it.
55+
/// </summary>
56+
public string? Url { get; set; }
57+
}
58+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#if INCLUDE_EXPECTATIONS
5+
using Mono.Linker.Tests.Cases.Expectations.Assertions;
6+
#endif
7+
8+
#nullable enable
9+
10+
namespace System.Diagnostics.CodeAnalysis
11+
{
12+
/// <summary>
13+
/// Indicates that the specified method requires dynamic access to code that is not referenced
14+
/// statically, for example through <see cref="Reflection"/>.
15+
/// </summary>
16+
/// <remarks>
17+
/// This allows tools to understand which methods are unsafe to call when removing unreferenced
18+
/// code from an application.
19+
/// </remarks>
20+
#if INCLUDE_EXPECTATIONS
21+
[SkipKeptItemsValidation]
22+
#endif
23+
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class, Inherited = false)]
24+
#if SYSTEM_PRIVATE_CORELIB
25+
public
26+
#else
27+
internal
28+
#endif
29+
sealed class RequiresUnreferencedCodeAttribute : Attribute
30+
{
31+
/// <summary>
32+
/// Initializes a new instance of the <see cref="RequiresUnreferencedCodeAttribute"/> class
33+
/// with the specified message.
34+
/// </summary>
35+
/// <param name="message">
36+
/// A message that contains information about the usage of unreferenced code.
37+
/// </param>
38+
public RequiresUnreferencedCodeAttribute(string message)
39+
{
40+
Message = message;
41+
}
42+
43+
/// <summary>
44+
/// Indicates whether the attribute should apply to static members.
45+
/// </summary>
46+
public bool ExcludeStatics { get; set; }
47+
48+
/// <summary>
49+
/// Gets a message that contains information about the usage of unreferenced code.
50+
/// </summary>
51+
public string Message { get; }
52+
53+
/// <summary>
54+
/// Gets or sets an optional URL that contains more information about the method,
55+
/// why it requires unreferenced code, and what options a consumer has to deal with it.
56+
/// </summary>
57+
public string? Url { get; set; }
58+
}
59+
}

src/tools/illink/src/linker/Linker/Annotations.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,13 @@ internal bool IsInRequiresUnreferencedCodeScope(MethodDefinition method, [NotNul
626626
return true;
627627

628628
if (method.DeclaringType is not null && TryGetLinkerAttribute(method.DeclaringType, out attribute))
629-
return true;
629+
{
630+
if (!attribute.ExcludeStatics)
631+
return true;
632+
633+
if (!method.IsStatic)
634+
return true;
635+
}
630636

631637
attribute = null;
632638
return false;
@@ -676,7 +682,13 @@ internal bool DoesMethodRequireUnreferencedCode(MethodDefinition originalMethod,
676682

677683
if ((method.IsStatic || method.IsConstructor) && method.DeclaringType is not null &&
678684
TryGetLinkerAttribute(method.DeclaringType, out attribute))
679-
return true;
685+
{
686+
if (!attribute.ExcludeStatics)
687+
return true;
688+
689+
if (method.IsConstructor)
690+
return true;
691+
}
680692
} while (context.CompilerGeneratedState.TryGetOwningMethodForCompilerGeneratedMember(method, out method));
681693

682694
attribute = null;
@@ -691,7 +703,7 @@ internal bool DoesFieldRequireUnreferencedCode(FieldDefinition field, [NotNullWh
691703
return false;
692704
}
693705

694-
return TryGetLinkerAttribute(field.DeclaringType, out attribute);
706+
return TryGetLinkerAttribute(field.DeclaringType, out attribute) && !attribute.ExcludeStatics;
695707
}
696708

697709
/// <Summary>

src/tools/illink/src/linker/Linker/LinkerAttributesInformation.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,20 +120,28 @@ public IEnumerable<T> GetAttributes<T>() where T : Attribute
120120

121121
if (customAttribute.HasConstructorArguments && customAttribute.ConstructorArguments[0].Value is string message)
122122
{
123-
var ruca = new RequiresUnreferencedCodeAttribute(message);
123+
string? url = null;
124+
bool excludeStatics = false;
124125
if (customAttribute.HasProperties)
125126
{
126127
foreach (var prop in customAttribute.Properties)
127128
{
128129
if (prop.Name == "Url")
129130
{
130-
ruca.Url = prop.Argument.Value as string;
131-
break;
131+
url = prop.Argument.Value as string;
132+
}
133+
else if (prop.Name == "ExcludeStatics" && prop.Argument.Value is true)
134+
{
135+
excludeStatics = true;
132136
}
133137
}
134138
}
135139

136-
return ruca;
140+
return new RequiresUnreferencedCodeAttribute(message)
141+
{
142+
Url = url,
143+
ExcludeStatics = excludeStatics
144+
};
137145
}
138146

139147
context.LogWarning((IMemberDefinition)provider, DiagnosticId.AttributeDoesntHaveTheRequiredNumberOfParameters, typeof(RequiresUnreferencedCodeAttribute).FullName ?? "");

src/tools/illink/src/linker/Mono.Linker.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
<RollForward>Major</RollForward>
2222
<UseAppHost>false</UseAppHost>
2323
<NoWarn>$(NoWarn);NU5131</NoWarn>
24+
<!-- Allow overriding RequiresUnreferencedCodeAttribute -->
25+
<NoWarn>$(NoWarn);CS0436</NoWarn>
2426
<TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);_AddReferenceAssemblyToPackage</TargetsForTfmSpecificContentInPackage>
2527
<DefineConstants>$(DefineConstants);ILLINK</DefineConstants>
2628
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/RequiresCapabilityTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ public Task RequiresAttributeMismatch()
3434
return RunTest(nameof(RequiresAttributeMismatch));
3535
}
3636

37+
[Fact]
38+
public Task RequiresExcludeStatics()
39+
{
40+
return RunTest();
41+
}
42+
3743
[Fact]
3844
public Task RequiresCapabilityFromCopiedAssembly()
3945
{

0 commit comments

Comments
 (0)