From 004bcffa8d3650ba2cb7ef7235a31c0a99e23cc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 16:21:54 +0000 Subject: [PATCH 1/9] Initial plan From 00f3516aae321c6124906371d28e70590bed4949 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 16:42:26 +0000 Subject: [PATCH 2/9] Add failing ILLink test for new()-constrained generic attribute argument Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com> --- ...nericAttributeArgumentConstructorIsKept.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs new file mode 100644 index 00000000000000..4f3735aa6fb715 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs @@ -0,0 +1,40 @@ +using System; +using Mono.Linker.Tests.Cases.Expectations.Assertions; + +namespace Mono.Linker.Tests.Cases.Attributes +{ + public class NewConstrainedGenericAttributeArgumentConstructorIsKept + { + public static void Main() + { + // Accessing the attributes via reflection forces the attribute (and its + // generic instantiation) to be kept. The new() constraint on the attribute's + // type parameter requires the public parameterless constructor of the + // generic argument to be preserved, otherwise the runtime throws a + // TypeLoadException when materializing the attribute. + typeof(CheckBox).GetCustomAttributes(false); + } + + [Kept] + public class Handler + { + [Kept] + public Handler() { } + } + + [Kept] + [KeptBaseType(typeof(Attribute))] + class MyAttribute : Attribute where T : new() + { + [Kept] + public MyAttribute() { } + } + + [Kept] + [KeptAttributeAttribute(typeof(MyAttribute))] + [My] + class CheckBox + { + } + } +} From 732780f7c46eeb2eee7f49f9761ef0626c3fc33f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 16:57:54 +0000 Subject: [PATCH 3/9] Process generic argument dataflow for custom attribute types to keep new()-constrained ctor Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com> --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 8 ++++++++ ...onstrainedGenericAttributeArgumentConstructorIsKept.cs | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index fdd086d0dd03b0..7263f133a03d53 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1188,6 +1188,14 @@ protected internal virtual void MarkCustomAttribute(CustomAttribute ca, in Depen MarkCustomAttributeArguments(ca, origin); TypeReference constructor_type = ca.Constructor.DeclaringType; + + // The attribute type can be a generic instantiation (e.g. [MyAttribute]). + // Process the generic argument data flow for it so that requirements coming from the + // generic parameters (e.g. a new() constraint or DynamicallyAccessedMembers annotations) + // are satisfied on the generic arguments. Otherwise the attribute could fail to construct + // at runtime (for example a new() constraint would be left unsatisfiable). + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); + TypeDefinition? type = Context.Resolve(constructor_type); if (type == null) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs index 4f3735aa6fb715..3dff3e33d8235e 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using Mono.Linker.Tests.Cases.Expectations.Assertions; namespace Mono.Linker.Tests.Cases.Attributes @@ -24,7 +25,7 @@ public Handler() { } [Kept] [KeptBaseType(typeof(Attribute))] - class MyAttribute : Attribute where T : new() + class MyAttribute<[KeptGenericParamAttributes(GenericParameterAttributes.DefaultConstructorConstraint)] T> : Attribute where T : new() { [Kept] public MyAttribute() { } From 72898f2d89a9315817225ef8973da72c80af41eb Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 29 May 2026 10:15:06 -0700 Subject: [PATCH 4/9] Apply suggestion from @jtschuster --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 7263f133a03d53..5f51ba8d58cf24 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1188,12 +1188,6 @@ protected internal virtual void MarkCustomAttribute(CustomAttribute ca, in Depen MarkCustomAttributeArguments(ca, origin); TypeReference constructor_type = ca.Constructor.DeclaringType; - - // The attribute type can be a generic instantiation (e.g. [MyAttribute]). - // Process the generic argument data flow for it so that requirements coming from the - // generic parameters (e.g. a new() constraint or DynamicallyAccessedMembers annotations) - // are satisfied on the generic arguments. Otherwise the attribute could fail to construct - // at runtime (for example a new() constraint would be left unsatisfiable). GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); TypeDefinition? type = Context.Resolve(constructor_type); From 69ba2bfaecd2aad08ba479ec37199f664015546e Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 29 May 2026 10:16:17 -0700 Subject: [PATCH 5/9] Apply suggestion from @jtschuster --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 5f51ba8d58cf24..ef6c7edf3fc61e 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1187,9 +1187,8 @@ protected internal virtual void MarkCustomAttribute(CustomAttribute ca, in Depen MarkCustomAttributeArguments(ca, origin); - TypeReference constructor_type = ca.Constructor.DeclaringType; GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); - +TypeReference constructor_type = ca.Constructor.DeclaringType; TypeDefinition? type = Context.Resolve(constructor_type); if (type == null) From 9cb20b93a3f284fc0f502d38ab663e3cb58e5511 Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 29 May 2026 10:16:46 -0700 Subject: [PATCH 6/9] Apply suggestion from @jtschuster --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index ef6c7edf3fc61e..fae4da6750f6f3 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1188,7 +1188,7 @@ protected internal virtual void MarkCustomAttribute(CustomAttribute ca, in Depen MarkCustomAttributeArguments(ca, origin); GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); -TypeReference constructor_type = ca.Constructor.DeclaringType; + TypeReference constructor_type = ca.Constructor.DeclaringType; TypeDefinition? type = Context.Resolve(constructor_type); if (type == null) From 9dfc88120c0e011fe566999f3b2b19b1c284681e Mon Sep 17 00:00:00 2001 From: Jackson Schuster <36744439+jtschuster@users.noreply.github.com> Date: Fri, 29 May 2026 11:08:39 -0700 Subject: [PATCH 7/9] Apply suggestion from @jtschuster --- src/tools/illink/src/linker/Linker.Steps/MarkStep.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index fae4da6750f6f3..5f51ba8d58cf24 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1187,8 +1187,9 @@ protected internal virtual void MarkCustomAttribute(CustomAttribute ca, in Depen MarkCustomAttributeArguments(ca, origin); - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); TypeReference constructor_type = ca.Constructor.DeclaringType; + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); + TypeDefinition? type = Context.Resolve(constructor_type); if (type == null) From 4d726194cb537f5c92944f42033d1df02e093511 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 29 May 2026 19:07:41 +0000 Subject: [PATCH 8/9] Address PR feedback on generic attribute dataflow coverage Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com> --- .../Linker.Dataflow/DiagnosticUtilities.cs | 14 ++-- .../src/linker/Linker.Steps/MarkStep.cs | 3 +- .../Linker/MemberReferenceExtensions.cs | 6 ++ .../Dependencies/GenericAttributesDataFlow.il | 46 +++++++++++++ .../Attributes/GenericAttributes.cs | 69 +++++++++++++++++++ ...nericAttributeArgumentConstructorIsKept.cs | 41 ----------- 6 files changed, 133 insertions(+), 46 deletions(-) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il delete mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs diff --git a/src/tools/illink/src/linker/Linker.Dataflow/DiagnosticUtilities.cs b/src/tools/illink/src/linker/Linker.Dataflow/DiagnosticUtilities.cs index 6b1bebb31dfdb2..c6e15e51e6cf2c 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/DiagnosticUtilities.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/DiagnosticUtilities.cs @@ -10,10 +10,16 @@ static class DiagnosticUtilities internal static string GetParameterNameForErrorMessage(ParameterDefinition parameterDefinition) => string.IsNullOrEmpty(parameterDefinition.Name) ? $"#{parameterDefinition.Index}" : parameterDefinition.Name; - internal static string GetGenericParameterDeclaringMemberDisplayName(GenericParameter genericParameter) => - genericParameter.DeclaringMethod != null ? - genericParameter.DeclaringMethod.GetDisplayName() : - genericParameter.DeclaringType.GetDisplayName(); + internal static string GetGenericParameterDeclaringMemberDisplayName(GenericParameter genericParameter) + { + if (genericParameter.DeclaringMethod is MethodReference declaringMethod) + return declaringMethod.Name; + + if (genericParameter.DeclaringType is TypeReference declaringType) + return declaringType.Name; + + return genericParameter.Name; + } internal static string GetMethodSignatureDisplayName(IMethodSignature methodSignature) => (methodSignature is MethodReference method) ? method.GetDisplayName() : (methodSignature.ToString() ?? string.Empty); diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 5f51ba8d58cf24..4b08374f7f48ab 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -1188,7 +1188,8 @@ protected internal virtual void MarkCustomAttribute(CustomAttribute ca, in Depen MarkCustomAttributeArguments(ca, origin); TypeReference constructor_type = ca.Constructor.DeclaringType; - GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); + if (GenericArgumentDataFlow.RequiresGenericArgumentDataFlow(Context.Annotations.FlowAnnotations, constructor_type)) + GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(in origin, this, Context, constructor_type); TypeDefinition? type = Context.Resolve(constructor_type); diff --git a/src/tools/illink/src/linker/Linker/MemberReferenceExtensions.cs b/src/tools/illink/src/linker/Linker/MemberReferenceExtensions.cs index 71be73a49db94c..7480ec33a19e84 100644 --- a/src/tools/illink/src/linker/Linker/MemberReferenceExtensions.cs +++ b/src/tools/illink/src/linker/Linker/MemberReferenceExtensions.cs @@ -30,7 +30,13 @@ public static string GetDisplayName(this MemberReference member) public static string GetNamespaceDisplayName(this MemberReference member) { + if (member == null) + return string.Empty; + var type = member is TypeReference typeReference ? typeReference : member.DeclaringType; + if (type == null) + return string.Empty; + while (type.DeclaringType != null) type = type.DeclaringType; diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il new file mode 100644 index 00000000000000..47a3209e01bf2d --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il @@ -0,0 +1,46 @@ +.assembly extern System.Runtime +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) +} +.assembly 'GenericAttributesDataFlow' +{ + .hash algorithm 0x00008004 + .ver 1:0:0:0 +} +.module GenericAttributesDataFlow.dll + +.namespace Mono.Linker.Tests.Cases.Attributes.Dependencies +{ + .class public auto ansi beforefieldinit DynamicallyAccessedMembersGenericAttribute`1 + extends [System.Runtime]System.Attribute + { + .param type T + .custom instance void [System.Runtime]System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute::.ctor(valuetype [System.Runtime]System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes) = ( + 01 00 08 00 00 00 00 00 + ) + + .method public hidebysig specialname rtspecialname + instance default void '.ctor' () cil managed + { + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void class [System.Runtime]System.Attribute::'.ctor'() + IL_0006: ret + } + } + + .class public auto ansi beforefieldinit DynamicallyAccessedMembersGenericClass`1 + extends [System.Runtime]System.Object + { + .custom instance void class Mono.Linker.Tests.Cases.Attributes.Dependencies.DynamicallyAccessedMembersGenericAttribute`1::'.ctor'() = (01 00 00 00) + + .method public hidebysig specialname rtspecialname + instance default void '.ctor' () cil managed + { + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void class [System.Runtime]System.Object::'.ctor'() + IL_0006: ret + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs index e8340a5b7d2cc2..0e2673d2063d0f 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs @@ -1,8 +1,12 @@ using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.Attributes { + [SetupCompileBefore("GenericAttributesDataFlow.dll", new[] { "Dependencies/GenericAttributesDataFlow.il" })] class GenericAttributes { static void Main() @@ -10,6 +14,9 @@ static void Main() new WithGenericAttribute_OfString(); new WithGenericAttribute_OfInt(); new WithConstrainedGenericAttribute(); + typeof(WithNewConstrainedGenericAttribute).GetCustomAttributes(false); + ReflectOnGenericAttributeWithUnannotatedTypeParameter.Test(); + ReflectOnGenericAttributeWithAnnotatedTypeParameter.Test(); } [Kept] @@ -60,6 +67,13 @@ class ConstraintType { } + [Kept] + class TypeWithPublicMethods + { + [Kept] + public void Method() { } + } + [KeptBaseType(typeof(ConstraintType))] class DerivedFromConstraintType : ConstraintType { @@ -72,5 +86,60 @@ class ConstrainedGenericAttribute : Attribute [Kept] public ConstrainedGenericAttribute() { } } + + [Kept] + class Handler + { + [Kept] + public Handler() { } + } + + [Kept] + [KeptAttributeAttribute(typeof(NewConstrainedGenericAttribute))] + [KeptMember(".ctor()")] + [NewConstrainedGenericAttribute] + class WithNewConstrainedGenericAttribute + { + } + + [Kept] + [KeptBaseType(typeof(Attribute))] + class NewConstrainedGenericAttribute<[KeptGenericParamAttributes(GenericParameterAttributes.DefaultConstructorConstraint)] T> : Attribute + where T : new() + { + [Kept] + public NewConstrainedGenericAttribute() { } + } + + [Kept] + class ReflectOnGenericAttributeWithUnannotatedTypeParameter + { + [Kept] + [ExpectedWarning("IL2091", Tool.Trimmer | Tool.NativeAot, "")] + public static void Test() + { + GetDynamicallyAccessedMembersGenericClass(typeof(T)).GetCustomAttributes(false); + } + } + + [Kept] + class ReflectOnGenericAttributeWithAnnotatedTypeParameter< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + [KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] + T>() + { + [Kept] + public static void Test() + { + GetDynamicallyAccessedMembersGenericClass(typeof(T)).GetCustomAttributes(false); + } + } + + [Kept] + static Type GetDynamicallyAccessedMembersGenericClass(Type typeArgument) + { + return Type.GetType("Mono.Linker.Tests.Cases.Attributes.Dependencies.DynamicallyAccessedMembersGenericClass`1, GenericAttributesDataFlow")! + .MakeGenericType(typeArgument); + } } } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs deleted file mode 100644 index 3dff3e33d8235e..00000000000000 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/NewConstrainedGenericAttributeArgumentConstructorIsKept.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Reflection; -using Mono.Linker.Tests.Cases.Expectations.Assertions; - -namespace Mono.Linker.Tests.Cases.Attributes -{ - public class NewConstrainedGenericAttributeArgumentConstructorIsKept - { - public static void Main() - { - // Accessing the attributes via reflection forces the attribute (and its - // generic instantiation) to be kept. The new() constraint on the attribute's - // type parameter requires the public parameterless constructor of the - // generic argument to be preserved, otherwise the runtime throws a - // TypeLoadException when materializing the attribute. - typeof(CheckBox).GetCustomAttributes(false); - } - - [Kept] - public class Handler - { - [Kept] - public Handler() { } - } - - [Kept] - [KeptBaseType(typeof(Attribute))] - class MyAttribute<[KeptGenericParamAttributes(GenericParameterAttributes.DefaultConstructorConstraint)] T> : Attribute where T : new() - { - [Kept] - public MyAttribute() { } - } - - [Kept] - [KeptAttributeAttribute(typeof(MyAttribute))] - [My] - class CheckBox - { - } - } -} From 3df8a70fbcf1920c60a3305c78bb355c6389066e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 1 Jun 2026 20:31:56 +0000 Subject: [PATCH 9/9] Get GenericAttributes illink test to green; cover achievable DAM generic-attribute case Co-authored-by: jtschuster <36744439+jtschuster@users.noreply.github.com> --- .../Dependencies/GenericAttributesDataFlow.il | 8 +++- .../Attributes/GenericAttributes.cs | 47 ++++++------------- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il index 47a3209e01bf2d..4dcf19a31f8d12 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/Dependencies/GenericAttributesDataFlow.il @@ -11,6 +11,7 @@ .namespace Mono.Linker.Tests.Cases.Attributes.Dependencies { + // A generic attribute whose type parameter requires DynamicallyAccessedMemberTypes.PublicMethods. .class public auto ansi beforefieldinit DynamicallyAccessedMembersGenericAttribute`1 extends [System.Runtime]System.Attribute { @@ -29,7 +30,12 @@ } } - .class public auto ansi beforefieldinit DynamicallyAccessedMembersGenericClass`1 + // The attribute is applied with the class's own generic parameter as the generic argument. This + // construct cannot be expressed in C# (CS8968: an attribute type argument cannot use type + // parameters), so it is provided in IL. The trimmer must analyze the generic-argument data flow of + // the attribute without crashing and warn (IL2091) because the unannotated generic argument does + // not satisfy the attribute's PublicMethods requirement. + .class public auto ansi beforefieldinit ClassWithUnannotatedTypeParameter`1 extends [System.Runtime]System.Object { .custom instance void class Mono.Linker.Tests.Cases.Attributes.Dependencies.DynamicallyAccessedMembersGenericAttribute`1::'.ctor'() = (01 00 00 00) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs index 0e2673d2063d0f..8574fc90e94d33 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Attributes/GenericAttributes.cs @@ -1,12 +1,17 @@ using System; -using System.Diagnostics.CodeAnalysis; using System.Reflection; using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.Attributes { + // A generic attribute whose type parameter is annotated with DynamicallyAccessedMembers can only be + // applied with a generic-parameter argument in IL (C# forbids type parameters as generic attribute + // arguments - CS8968), so the data-flow scenario is provided by a compiled-before IL assembly. + // The warning originates in that dependency assembly, so it is asserted with LogContains + // (ExpectedWarning only matches origins in the test assembly itself). [SetupCompileBefore("GenericAttributesDataFlow.dll", new[] { "Dependencies/GenericAttributesDataFlow.il" })] + [LogContains("IL2091.*ClassWithUnannotatedTypeParameter", regexMatch: true)] class GenericAttributes { static void Main() @@ -15,8 +20,7 @@ static void Main() new WithGenericAttribute_OfInt(); new WithConstrainedGenericAttribute(); typeof(WithNewConstrainedGenericAttribute).GetCustomAttributes(false); - ReflectOnGenericAttributeWithUnannotatedTypeParameter.Test(); - ReflectOnGenericAttributeWithAnnotatedTypeParameter.Test(); + ReflectOnGenericAttributeWithUnannotatedTypeParameter(); } [Kept] @@ -70,7 +74,6 @@ class ConstraintType [Kept] class TypeWithPublicMethods { - [Kept] public void Method() { } } @@ -96,7 +99,6 @@ public Handler() { } [Kept] [KeptAttributeAttribute(typeof(NewConstrainedGenericAttribute))] - [KeptMember(".ctor()")] [NewConstrainedGenericAttribute] class WithNewConstrainedGenericAttribute { @@ -111,35 +113,16 @@ class NewConstrainedGenericAttribute<[KeptGenericParamAttributes(GenericParamete public NewConstrainedGenericAttribute() { } } + // Reflecting over the generic instantiation keeps the dependency type and forces the trimmer + // to analyze the generic attribute applied to it. [Kept] - class ReflectOnGenericAttributeWithUnannotatedTypeParameter - { - [Kept] - [ExpectedWarning("IL2091", Tool.Trimmer | Tool.NativeAot, "")] - public static void Test() - { - GetDynamicallyAccessedMembersGenericClass(typeof(T)).GetCustomAttributes(false); - } - } - - [Kept] - class ReflectOnGenericAttributeWithAnnotatedTypeParameter< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] - [KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] - T>() - { - [Kept] - public static void Test() - { - GetDynamicallyAccessedMembersGenericClass(typeof(T)).GetCustomAttributes(false); - } - } - - [Kept] - static Type GetDynamicallyAccessedMembersGenericClass(Type typeArgument) + static void ReflectOnGenericAttributeWithUnannotatedTypeParameter() { - return Type.GetType("Mono.Linker.Tests.Cases.Attributes.Dependencies.DynamicallyAccessedMembersGenericClass`1, GenericAttributesDataFlow")! - .MakeGenericType(typeArgument); + // ClassWithUnannotatedTypeParameter applies a DynamicallyAccessedMembers-annotated generic + // attribute using its own (unverifiable) generic parameter as the argument, so the trimmer + // must analyze the generic-argument data flow without crashing and warn (IL2091). + Type.GetType("Mono.Linker.Tests.Cases.Attributes.Dependencies.ClassWithUnannotatedTypeParameter`1, GenericAttributesDataFlow")! + .MakeGenericType(typeof(TypeWithPublicMethods)).GetCustomAttributes(false); } } }