From 2d55bf21ba3146ea959097612ebc3edaff12bc55 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Wed, 20 Aug 2025 14:44:41 -0700 Subject: [PATCH 01/14] Add ExtensionsDataFlow test --- .../DataFlowTests.cs | 6 ++ .../DataFlow/ExtensionsDataFlow.cs | 66 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionsDataFlow.cs diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs index eb88652e62a252..86114a73aea4cf 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs @@ -167,6 +167,12 @@ public Task EventDataFlow() return RunTest(); } + [Fact] + public Task ExtensionsDataFlow() + { + return RunTest(); + } + [Fact] public Task FeatureCheckDataFlow() { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionsDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionsDataFlow.cs new file mode 100644 index 00000000000000..19fd6fe3f9940d --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionsDataFlow.cs @@ -0,0 +1,66 @@ +// 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 Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; + +namespace Mono.Linker.Tests.Cases.DataFlow +{ + [SkipKeptItemsValidation] + [ExpectedNoWarnings] + public class ExtensionsDataFlow + { + public static void Main() + { + TestExtensionMethod(); + TestExtensionMethodMismatch(); + TestExtensionMethodRequires(); + } + + [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMethod")] + static void TestExtensionMethod() + { + GetWithFields().ExtensionMethod(); + GetWithMethods().ExtensionMethod(); + } + + static void TestExtensionMethodMismatch() + { + GetWithFields().ExtensionMethodMismatch(); + } + + [ExpectedWarning("IL2026", "ExtensionMethodRequires")] + static void TestExtensionMethodRequires() + { + GetWithFields().ExtensionMethodRequires(); + } + + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + static Type GetWithFields() => null; + + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + static Type GetWithMethods() => null; + } + + [ExpectedNoWarnings] + public static class Extensions + { + public static void ExtensionMethod([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] this Type type) + { + type.RequiresPublicFields(); + } + + [ExpectedWarning("IL2067", "RequiresPublicMethods")] + public static void ExtensionMethodMismatch([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] this Type type) + { + type.RequiresPublicMethods(); + } + + [RequiresUnreferencedCode(nameof(ExtensionMethodRequires))] + public static void ExtensionMethodRequires([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] this Type type) + { + } + } +} \ No newline at end of file From 11d14a6e4bfe704cdc42537bbc70e0728d83f6e5 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Thu, 21 Aug 2025 15:46:44 -0700 Subject: [PATCH 02/14] Add support for extension members --- .../DataFlow/LocalDataFlowVisitor.cs | 10 +++++ .../IMethodSymbolExtensions.cs | 12 +++++- .../TrimAnalysis/FlowAnnotations.cs | 3 +- .../TrimAnalysis/MethodParameterValue.cs | 5 --- .../TrimAnalysis/ParameterProxy.cs | 41 ++++++++++++++----- .../TrimAnalysis/TrimAnalysisVisitor.cs | 18 +++----- .../DataFlow/ExtensionsDataFlow.cs | 6 +-- .../TestCasesRunner/ResultChecker.cs | 14 +++++++ 8 files changed, 74 insertions(+), 35 deletions(-) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs index db1ae85bf19385..e27d91f8b6efb7 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs @@ -910,6 +910,16 @@ private TValue ProcessMethodCall( TValue instanceValue = Visit(instance, state); var argumentsBuilder = ImmutableArray.CreateBuilder(); + + // For calls to C# 14 extensions, treat the instance argument as a regular argument. + // The extension method doesn't have an implicit this, yet (unlike for existing extension methods) + // the IOperation represents it as having an Instance. + if (method.HasExtensionParameterOnType()) + { + argumentsBuilder.Add(instanceValue); + instanceValue = TopValue; + } + foreach (var argument in arguments) { // For __arglist argument there might not be any parameter diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/IMethodSymbolExtensions.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/IMethodSymbolExtensions.cs index 9d32539e8711e4..9f1bd58abeec8c 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/IMethodSymbolExtensions.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/IMethodSymbolExtensions.cs @@ -58,7 +58,7 @@ public static ParameterProxy GetParameter(this IMethodSymbol method, ParameterIn /// public static int GetMetadataParametersCount(this IMethodSymbol method) { - return method.Parameters.Length; + return method.Parameters.Length + (method.HasExtensionParameterOnType() ? 1 : 0); } /// @@ -66,7 +66,15 @@ public static int GetMetadataParametersCount(this IMethodSymbol method) /// public static int GetParametersCount(this IMethodSymbol method) { - return method.Parameters.Length + (method.HasImplicitThis() ? 1 : 0); + return method.Parameters.Length + (method.HasImplicitThis() ? 1 : 0) + (method.HasExtensionParameterOnType() ? 1 : 0); + } + + /// + /// Returns true if the method's containing type has an extension parameter + /// + public static bool HasExtensionParameterOnType(this IMethodSymbol method) + { + return method.ContainingType.ExtensionParameter != null; } } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs index 78368423563f27..c78d8c6641bd81 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs @@ -165,8 +165,7 @@ internal static DynamicallyAccessedMemberTypes GetMethodParameterAnnotation(Para var damt = parameter.GetDynamicallyAccessedMemberTypes(); - var parameterMethod = (IMethodSymbol)parameter.ContainingSymbol; - Debug.Assert(parameterMethod != null); + IMethodSymbol parameterMethod = param.Method.Method; // If there are conflicts between the setter and the property annotation, // the setter annotation wins. (But DAMT.None is ignored) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodParameterValue.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodParameterValue.cs index ed9d87b854e212..85a58bae1973bd 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodParameterValue.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/MethodParameterValue.cs @@ -9,11 +9,6 @@ namespace ILLink.Shared.TrimAnalysis { internal partial record MethodParameterValue { - public MethodParameterValue(IParameterSymbol parameterSymbol) - : this(new ParameterProxy(parameterSymbol)) { } - public MethodParameterValue(IMethodSymbol methodSymbol, ParameterIndex parameterIndex, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes) - : this(new(new(methodSymbol), parameterIndex), dynamicallyAccessedMemberTypes) { } - public MethodParameterValue(ParameterProxy parameter) : this(parameter, FlowAnnotations.GetMethodParameterAnnotation(parameter)) { } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ParameterProxy.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ParameterProxy.cs index 2085c3855610f2..1cf794dfb828f9 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ParameterProxy.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ParameterProxy.cs @@ -9,10 +9,12 @@ namespace ILLink.Shared.TypeSystemProxy { internal partial struct ParameterProxy { - public ParameterProxy(IParameterSymbol parameter) + public ParameterProxy(IParameterSymbol parameter, IMethodSymbol method) { - Method = new((IMethodSymbol)parameter.ContainingSymbol); - Index = (ParameterIndex)parameter.Ordinal + (Method.HasImplicitThis() ? 1 : 0); + Method = new(method); + Index = (ParameterIndex)parameter.Ordinal + + (Method.HasImplicitThis() ? 1 : 0) + + (method.HasExtensionParameterOnType() && parameter.ContainingSymbol is IMethodSymbol ? 1 : 0); } public partial ReferenceKind GetReferenceKind() @@ -22,14 +24,14 @@ public partial ReferenceKind GetReferenceKind() ? ReferenceKind.Ref : ReferenceKind.None; - switch (Method.Method.Parameters[MetadataIndex].RefKind) + switch (ParameterSymbol!.RefKind) { case RefKind.Ref: return ReferenceKind.Ref; case RefKind.In: return ReferenceKind.In; case RefKind.Out: return ReferenceKind.Out; case RefKind.None: return ReferenceKind.None; default: - Debug.Fail($"Unexpected RefKind {Method.Method.Parameters[MetadataIndex].RefKind} found on parameter {GetDisplayName()}"); + Debug.Fail($"Unexpected RefKind {ParameterSymbol!.RefKind} found on parameter {GetDisplayName()}"); return ReferenceKind.None; } } @@ -37,17 +39,36 @@ public partial ReferenceKind GetReferenceKind() /// /// Returns the IParameterSymbol representing the parameter. Returns null for the implicit this paramter. /// - public IParameterSymbol? ParameterSymbol => IsImplicitThis ? null : Method.Method.Parameters[MetadataIndex]; + public IParameterSymbol? ParameterSymbol + { + get + { + if (IsImplicitThis) + { + return null; + } + + var method = Method.Method; + int hasExtensionParameter = 0; + if (method.HasExtensionParameterOnType()) + { + if (MetadataIndex == 0) + { + return method.ContainingType.ExtensionParameter; + } + hasExtensionParameter = 1; + } + + return method.Parameters[MetadataIndex - hasExtensionParameter]; + } + } /// /// Returns the IParameterSymbol.Location[0] for the parameter. Returns null for the implicit this paramter. /// public Location? Location => ParameterSymbol?.Locations[0]; - public TypeProxy ParameterType - => IsImplicitThis - ? new TypeProxy(Method.Method.ContainingType) - : new TypeProxy(Method.Method.Parameters[MetadataIndex].Type); + public TypeProxy ParameterType => new TypeProxy(ParameterSymbol?.Type ?? Method.Method.ContainingType); public partial string GetDisplayName() { diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs index baa9daffcbccac..89a140b787b611 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs @@ -137,18 +137,8 @@ public override MultiValue VisitConversion(IConversionOperation operation, State public override MultiValue VisitParameterReference(IParameterReferenceOperation paramRef, StateValue state) { - IParameterSymbol parameter = paramRef.Parameter; - - if (parameter.ContainingSymbol is not IMethodSymbol) - { - // TODO: Extension members allows parameters to be on types, rather than methods. - // For example: `extension(ref T value) { }` will enumerate `value` where - // the containing symbol is a `NonErrorNamedTypeSymbol` - return TopValue; - } - // Reading from a parameter always returns the same annotated value. We don't track modifications. - return GetParameterTargetValue(parameter); + return GetParameterTargetValue(paramRef.Parameter); } public override MultiValue VisitInstanceReference(IInstanceReferenceOperation instanceRef, StateValue state) @@ -162,7 +152,7 @@ public override MultiValue VisitInstanceReference(IInstanceReferenceOperation in // It can also happen that we see this for a static method - for example a delegate creation // over a local function does this, even thought the "this" makes no sense inside a static scope. if (OwningSymbol is IMethodSymbol method && !method.IsStatic) - return new MethodParameterValue(method, (ParameterIndex)0, FlowAnnotations.GetMethodParameterAnnotation(new ParameterProxy(new(method), (ParameterIndex)0))); + return new MethodParameterValue(new ParameterProxy(new(method), (ParameterIndex)0)); return TopValue; } @@ -265,7 +255,9 @@ public override MultiValue GetBackingFieldTargetValue(IPropertyReferenceOperatio public override MultiValue GetParameterTargetValue(IParameterSymbol parameter) - => new MethodParameterValue(parameter); + { + return new MethodParameterValue(new ParameterProxy(parameter, parameter.ContainingSymbol as IMethodSymbol ?? (IMethodSymbol)OwningSymbol)); + } public override void HandleAssignment(MultiValue source, MultiValue target, IOperation operation, in FeatureContext featureContext) { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionsDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionsDataFlow.cs index 19fd6fe3f9940d..a3d6e6735f8c87 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionsDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionsDataFlow.cs @@ -19,7 +19,7 @@ public static void Main() TestExtensionMethodRequires(); } - [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMethod")] + [ExpectedWarning("IL2072", "GetWithMethods", nameof(Extensions.ExtensionMethod))] static void TestExtensionMethod() { GetWithFields().ExtensionMethod(); @@ -31,7 +31,7 @@ static void TestExtensionMethodMismatch() GetWithFields().ExtensionMethodMismatch(); } - [ExpectedWarning("IL2026", "ExtensionMethodRequires")] + [ExpectedWarning("IL2026", nameof(Extensions.ExtensionMethodRequires))] static void TestExtensionMethodRequires() { GetWithFields().ExtensionMethodRequires(); @@ -63,4 +63,4 @@ public static void ExtensionMethodRequires([DynamicallyAccessedMembers(Dynamical { } } -} \ No newline at end of file +} diff --git a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs index 6fc186db7d2564..3f57b237025fb6 100644 --- a/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/tools/illink/test/Mono.Linker.Tests/TestCasesRunner/ResultChecker.cs @@ -909,6 +909,20 @@ void VerifyLoggedMessages(AssemblyDefinition original, TrimmingTestLogger logger List unexpectedMessageWarnings = []; foreach (var attrProvider in GetAttributeProviders(original)) { + if (attrProvider is IMemberDefinition attrMember && + attrMember is not TypeDefinition && + attrMember.DeclaringType is TypeDefinition declaringType && + declaringType.Name.StartsWith("")) + { + // Workaround: C# 14 extension members result in a compiler-generated type + // that has a member for each extension member (this is in addition to the type + // which contains the actual extension member implementation). + // The generated members inherit attributes from the extension members, but + // have empty implementations. We don't want to check inherited ExpectedWarningAttributes + // for these members. + continue; + } + foreach (var attr in attrProvider.CustomAttributes) { if (!IsProducedByLinker(attr)) From 8333fea57cd47d32a56761875a0e4f0148190248 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 22 Aug 2025 11:02:08 -0700 Subject: [PATCH 03/14] Add property tests and arguments fix --- .../DataFlow/LocalDataFlowVisitor.cs | 12 +- .../DataFlowTests.cs | 6 + .../DataFlow/ExtensionMembersDataFlow.cs | 135 ++++++++++++++++++ 3 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs index e27d91f8b6efb7..58df9d6349c182 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs @@ -705,14 +705,20 @@ public override TValue VisitPropertyReference(IPropertyReferenceOperation operat // Accessing property for reading is really a call to the getter // The setter case is handled in assignment operation since here we don't have access to the value to pass to the setter TValue instanceValue = Visit(operation.Instance, state); - IMethodSymbol? getMethod = operation.Property.GetGetMethod(); + IMethodSymbol getMethod = operation.Property.GetGetMethod()!; + ImmutableArray.Builder arguments = ImmutableArray.CreateBuilder(); + // Handle C# 14 extension property access (see comment in ProcessMethodCall) + if (getMethod.HasExtensionParameterOnType()) + { + arguments.Add(instanceValue); + instanceValue = TopValue; + } // Property may be an indexer, in which case there will be one or more index arguments - ImmutableArray.Builder arguments = ImmutableArray.CreateBuilder(); foreach (var val in operation.Arguments) arguments.Add(Visit(val, state)); - return HandleMethodCallHelper(getMethod!, instanceValue, arguments.ToImmutableArray(), operation, state); + return HandleMethodCallHelper(getMethod, instanceValue, arguments.ToImmutableArray(), operation, state); } public override TValue VisitEventReference(IEventReferenceOperation operation, LocalDataFlowState state) diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs index 86114a73aea4cf..7725ced3ecabd5 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs @@ -173,6 +173,12 @@ public Task ExtensionsDataFlow() return RunTest(); } + [Fact] + public Task ExtensionMembersDataFlow() + { + return RunTest(); + } + [Fact] public Task FeatureCheckDataFlow() { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs new file mode 100644 index 00000000000000..bec838bd31182a --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs @@ -0,0 +1,135 @@ +// 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.Runtime.CompilerServices; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Helpers; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.DataFlow +{ + [SkipKeptItemsValidation] + [ExpectedNoWarnings] + public class ExtensionMembersDataFlow + { + public static void Main() + { + TestExtensionMethod(); + TestExtensionMethodMismatch(); + TestExtensionMethodRequires(); + TestExtensionMethodWithParams(); + TestExtensionStaticMethodRequires(); + TestExtensionProperty(); + TestExtensionPropertyAnnotatedReturn(); + TestExtensionPropertyMismatch(); + TestExtensionPropertyRequires(); + } + + [ExpectedWarning("IL2072", "GetWithMethods", nameof(ExtensionMembers.ExtensionMembersMethod))] + static void TestExtensionMethod() + { + GetWithFields().ExtensionMembersMethod(); + GetWithMethods().ExtensionMembersMethod(); + } + + static void TestExtensionMethodMismatch() + { + GetWithFields().ExtensionMembersMethodMismatch(); + } + + [ExpectedWarning("IL2026", nameof(ExtensionMembers.ExtensionMembersMethodRequires))] + static void TestExtensionMethodRequires() + { + GetWithFields().ExtensionMembersMethodRequires(); + } + + [ExpectedWarning("IL2072", "GetWithMethods", nameof(ExtensionMembers.ExtensionMembersMethodWithParams))] + [ExpectedWarning("IL2072", "GetWithMethods", nameof(ExtensionMembers.ExtensionMembersMethodWithParams))] + static void TestExtensionMethodWithParams() + { + GetWithFields().ExtensionMembersMethodWithParams(GetWithFields()); + GetWithMethods().ExtensionMembersMethodWithParams(GetWithMethods()); + } + + [ExpectedWarning("IL2026", nameof(ExtensionMembers.ExtensionMembersStaticMethodRequires))] + static void TestExtensionStaticMethodRequires() + { + ExtensionMembers.ExtensionMembersStaticMethodRequires(); + } + + [UnexpectedWarning("IL2072", "ExtensionMembersProperty", "RequiresPublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/80017")] + static void TestExtensionProperty() + { + GetWithFields().ExtensionMembersProperty.RequiresPublicMethods(); + } + + [UnexpectedWarning("IL2072", "ExtensionMembersPropertyAnnotatedReturn", "RequiresPublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/80017")] + static void TestExtensionPropertyAnnotatedReturn() + { + GetWithFields().ExtensionMembersPropertyAnnotatedReturn.RequiresPublicMethods(); + } + + + [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMembersProperty")] + [ExpectedWarning("IL2072", "ExtensionMembersProperty", "RequiresPublicFields")] + static void TestExtensionPropertyMismatch() + { + GetWithMethods().ExtensionMembersProperty.RequiresPublicFields(); + } + + [ExpectedWarning("IL2026", "ExtensionMembersPropertyRequires")] + static void TestExtensionPropertyRequires() + { + _ = GetWithFields().ExtensionMembersPropertyRequires; + } + + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + static Type GetWithFields() => null; + + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + static Type GetWithMethods() => null; + } + + [ExpectedNoWarnings] + public static class ExtensionMembers + { + extension([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type type) + { + public void ExtensionMembersMethod() => type.RequiresPublicFields(); + + [ExpectedWarning("IL2067", "RequiresPublicMethods")] // The attribute gets copied to the internal class too, but that one is unused. Then it complains. + public void ExtensionMembersMethodMismatch() => type.RequiresPublicMethods(); + + [RequiresUnreferencedCode(nameof(ExtensionMembersMethodRequires))] + public void ExtensionMembersMethodRequires() { } + + [ExpectedWarning("IL2067", "type", "RequiresPublicConstructors")] + [ExpectedWarning("IL2067", "typeParam", "RequiresPublicMethods")] + public void ExtensionMembersMethodWithParams([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type typeParam) + { + type.RequiresPublicConstructors(); + typeParam.RequiresPublicMethods(); + } + + [RequiresUnreferencedCode(nameof(ExtensionMembersStaticMethodRequires))] + public static void ExtensionMembersStaticMethodRequires() { } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + public Type ExtensionMembersProperty => null; + + public Type ExtensionMembersPropertyAnnotatedReturn + { + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + get => null; + } + + public Type ExtensionMembersPropertyRequires + { + [RequiresUnreferencedCode("ExtensionMembersPropertyRequires")] + get => null; + } + } + } +} From 11a20ad6fd4089bebd2661fcbe63e431977ee9b2 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 22 Aug 2025 11:07:32 -0700 Subject: [PATCH 04/14] Fix ILCompiler.Trimming.Tests infra --- .../TestCasesRunner/ResultChecker.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs index 673df2e9a11500..ff012a7945fee0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Trimming.Tests/TestCasesRunner/ResultChecker.cs @@ -202,6 +202,20 @@ private void VerifyLoggedMessages(AssemblyDefinition original, TrimmingTestLogge List<(ICustomAttributeProvider, CustomAttribute)> expectedNoWarningsAttributes = new(); foreach (var attrProvider in GetAttributeProviders(original)) { + if (attrProvider is IMemberDefinition attrMember && + attrMember is not TypeDefinition && + attrMember.DeclaringType is TypeDefinition declaringType && + declaringType.Name.StartsWith("")) + { + // Workaround: C# 14 extension members result in a compiler-generated type + // that has a member for each extension member (this is in addition to the type + // which contains the actual extension member implementation). + // The generated members inherit attributes from the extension members, but + // have empty implementations. We don't want to check inherited ExpectedWarningAttributes + // for these members. + continue; + } + foreach (var attr in attrProvider.CustomAttributes) { if (!IsProducedByNativeAOT(attr)) From 5de1b6f17bd94569d7f3ad34c7a1923c76d5ef74 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 22 Aug 2025 11:14:11 -0700 Subject: [PATCH 05/14] Update MicrosoftCodeAnalysisVersion_LatestVS --- eng/Versions.props | 2 +- .../ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs | 2 +- .../src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index d61001668130e5..87c7a1db2d1179 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -56,7 +56,7 @@ Source-build builds the product with the most recent previously source-built release. Thankfully, these two requirements line up nicely such that any version that satisfies the VS version requirement will also satisfy the .NET SDK version requirement because of how we ship. --> - 4.8.0 + 4.14.0 3.3.5-beta1.23270.2 diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs index 58df9d6349c182..70e48076491846 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs @@ -896,7 +896,7 @@ private TValue HandleMethodCallHelper( TConditionValue conditionValue = GetConditionValue(argumentOperation, state); var current = state.Current; ApplyCondition( - doesNotReturnIfConditionValue == false + !doesNotReturnIfConditionValue ? conditionValue : conditionValue.Negate(), ref current); diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj b/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj index 49b09a3c07510b..8d4c2dfe2a5647 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj @@ -8,9 +8,6 @@ false Latest $(NoWarn);CS8524 - - $(NoWarn);CS0436 cs true + + $(NoWarn);CS0436 true From c76e789792097a48abf9308325c0a99b2263657b Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 22 Aug 2025 16:32:10 -0700 Subject: [PATCH 06/14] Update MicrosoftCodeAnalysisVersion_LatestVS --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index d61001668130e5..87c7a1db2d1179 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -56,7 +56,7 @@ Source-build builds the product with the most recent previously source-built release. Thankfully, these two requirements line up nicely such that any version that satisfies the VS version requirement will also satisfy the .NET SDK version requirement because of how we ship. --> - 4.8.0 + 4.14.0 3.3.5-beta1.23270.2 From 5fd7bdef01ed8bc724bdf307415b5afdffa37ab0 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 25 Aug 2025 14:57:55 -0700 Subject: [PATCH 07/14] Reorganize tests, fix setter arguments And add tests for setters --- .../DataFlow/LocalDataFlowVisitor.cs | 10 +- .../DataFlow/ExtensionMembersDataFlow.cs | 110 +++++++++++++----- 2 files changed, 93 insertions(+), 27 deletions(-) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs index 70e48076491846..f392a2113139f4 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs @@ -305,8 +305,16 @@ bool merge return value; } - // Property may be an indexer, in which case there will be one or more index arguments followed by a value argument ImmutableArray.Builder arguments = ImmutableArray.CreateBuilder(); + + // Handle C# 14 extension property access (see comment in ProcessMethodCall) + if (setMethod.HasExtensionParameterOnType()) + { + arguments.Add(instanceValue); + instanceValue = TopValue; + } + + // Property may be an indexer, in which case there will be one or more index arguments followed by a value argument foreach (var val in propertyRef.Arguments) arguments.Add(Visit(val, state)); arguments.Add(value); diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs index bec838bd31182a..120cb6a832293a 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs @@ -20,23 +20,24 @@ public static void Main() TestExtensionMethodMismatch(); TestExtensionMethodRequires(); TestExtensionMethodWithParams(); + TestExtensionMethodWithParamsMismatch(); TestExtensionStaticMethodRequires(); TestExtensionProperty(); - TestExtensionPropertyAnnotatedReturn(); TestExtensionPropertyMismatch(); + TestExtensionPropertyAnnotatedAccessor(); + TestExtensionPropertyAnnotatedAccessorMismatch(); TestExtensionPropertyRequires(); } - [ExpectedWarning("IL2072", "GetWithMethods", nameof(ExtensionMembers.ExtensionMembersMethod))] static void TestExtensionMethod() { GetWithFields().ExtensionMembersMethod(); - GetWithMethods().ExtensionMembersMethod(); } + [ExpectedWarning("IL2072", "GetWithMethods", nameof(ExtensionMembers.ExtensionMembersMethod))] static void TestExtensionMethodMismatch() { - GetWithFields().ExtensionMembersMethodMismatch(); + GetWithMethods().ExtensionMembersMethodMismatch(); } [ExpectedWarning("IL2026", nameof(ExtensionMembers.ExtensionMembersMethodRequires))] @@ -45,12 +46,16 @@ static void TestExtensionMethodRequires() GetWithFields().ExtensionMembersMethodRequires(); } - [ExpectedWarning("IL2072", "GetWithMethods", nameof(ExtensionMembers.ExtensionMembersMethodWithParams))] - [ExpectedWarning("IL2072", "GetWithMethods", nameof(ExtensionMembers.ExtensionMembersMethodWithParams))] static void TestExtensionMethodWithParams() { - GetWithFields().ExtensionMembersMethodWithParams(GetWithFields()); - GetWithMethods().ExtensionMembersMethodWithParams(GetWithMethods()); + GetWithFields().ExtensionMembersMethodWithParams(GetWithMethods()); + } + + [ExpectedWarning("IL2072", "GetWithMethods", nameof(ExtensionMembers.ExtensionMembersMethodWithParams))] + [ExpectedWarning("IL2072", "GetWithFields", nameof(ExtensionMembers.ExtensionMembersMethodWithParams))] + static void TestExtensionMethodWithParamsMismatch() + { + GetWithMethods().ExtensionMembersMethodWithParamsMismatch(GetWithFields()); } [ExpectedWarning("IL2026", nameof(ExtensionMembers.ExtensionMembersStaticMethodRequires))] @@ -59,24 +64,42 @@ static void TestExtensionStaticMethodRequires() ExtensionMembers.ExtensionMembersStaticMethodRequires(); } - [UnexpectedWarning("IL2072", "ExtensionMembersProperty", "RequiresPublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/80017")] + [UnexpectedWarning("IL2072", "ExtensionMembersProperty", "RequiresPublicMethods", Tool.Trimmer | Tool.NativeAot, "")] static void TestExtensionProperty() { - GetWithFields().ExtensionMembersProperty.RequiresPublicMethods(); + var instance = GetWithFields(); + instance.ExtensionMembersProperty.RequiresPublicMethods(); + instance.ExtensionMembersProperty = GetWithMethods(); } - [UnexpectedWarning("IL2072", "ExtensionMembersPropertyAnnotatedReturn", "RequiresPublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/80017")] - static void TestExtensionPropertyAnnotatedReturn() + [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMembersPropertyMismatch")] + [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMembersPropertyMismatch")] + [ExpectedWarning("IL2072", "ExtensionMembersPropertyMismatch", "RequiresPublicFields")] + [ExpectedWarning("IL2072", "ExtensionMembersPropertyMismatch", "GetWithFields", Tool.Analyzer, "")] + static void TestExtensionPropertyMismatch() { - GetWithFields().ExtensionMembersPropertyAnnotatedReturn.RequiresPublicMethods(); + var instance = GetWithMethods(); + instance.ExtensionMembersPropertyMismatch.RequiresPublicFields(); + instance.ExtensionMembersPropertyMismatch = GetWithFields(); } + [UnexpectedWarning("IL2072", "ExtensionMembersPropertyAnnotatedAccessor", "RequiresPublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/80017")] + static void TestExtensionPropertyAnnotatedAccessor() + { + var instance = GetWithFields(); + instance.ExtensionMembersPropertyAnnotatedAccessor.RequiresPublicMethods(); + instance.ExtensionMembersPropertyAnnotatedAccessor = GetWithMethods(); + } - [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMembersProperty")] - [ExpectedWarning("IL2072", "ExtensionMembersProperty", "RequiresPublicFields")] - static void TestExtensionPropertyMismatch() + [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMembersPropertyAnnotatedAccessorMismatch")] + [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMembersPropertyAnnotatedAccessorMismatch")] + [ExpectedWarning("IL2072", "ExtensionMembersPropertyAnnotatedAccessorMismatch", "RequiresPublicFields")] + [ExpectedWarning("IL2072", "ExtensionMembersPropertyAnnotatedAccessorMismatch", "GetWithFields")] + static void TestExtensionPropertyAnnotatedAccessorMismatch() { - GetWithMethods().ExtensionMembersProperty.RequiresPublicFields(); + var instance = GetWithMethods(); + instance.ExtensionMembersPropertyAnnotatedAccessorMismatch.RequiresPublicFields(); + instance.ExtensionMembersPropertyAnnotatedAccessorMismatch = GetWithFields(); } [ExpectedWarning("IL2026", "ExtensionMembersPropertyRequires")] @@ -86,10 +109,10 @@ static void TestExtensionPropertyRequires() } [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] - static Type GetWithFields() => null; + public static Type GetWithFields() => null; [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] - static Type GetWithMethods() => null; + public static Type GetWithMethods() => null; } [ExpectedNoWarnings] @@ -105,24 +128,59 @@ public static class ExtensionMembers [RequiresUnreferencedCode(nameof(ExtensionMembersMethodRequires))] public void ExtensionMembersMethodRequires() { } - [ExpectedWarning("IL2067", "type", "RequiresPublicConstructors")] - [ExpectedWarning("IL2067", "typeParam", "RequiresPublicMethods")] - public void ExtensionMembersMethodWithParams([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type typeParam) + public void ExtensionMembersMethodWithParams([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type typeParam) { - type.RequiresPublicConstructors(); + type.RequiresPublicFields(); typeParam.RequiresPublicMethods(); } + [ExpectedWarning("IL2067", "type", "RequiresPublicMethods")] + [ExpectedWarning("IL2067", "typeParam", "RequiresPublicFields")] + public void ExtensionMembersMethodWithParamsMismatch([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type typeParam) + { + type.RequiresPublicMethods(); + typeParam.RequiresPublicFields(); + } + [RequiresUnreferencedCode(nameof(ExtensionMembersStaticMethodRequires))] public static void ExtensionMembersStaticMethodRequires() { } [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] - public Type ExtensionMembersProperty => null; + public Type ExtensionMembersProperty + { + get => ExtensionMembersDataFlow.GetWithMethods(); + + [UnexpectedWarning("IL2067", "value", "RequiresPublicMethods", Tool.Trimmer | Tool.NativeAot, "")] + set => value.RequiresPublicMethods(); + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + public Type ExtensionMembersPropertyMismatch + { + [ExpectedWarning("IL2073", "GetWithFields", Tool.Analyzer, "")] + get => ExtensionMembersDataFlow.GetWithFields(); + + [ExpectedWarning("IL2067", "value", "RequiresPublicFields")] + set => value.RequiresPublicFields(); + } - public Type ExtensionMembersPropertyAnnotatedReturn + public Type ExtensionMembersPropertyAnnotatedAccessor { [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] - get => null; + get => ExtensionMembersDataFlow.GetWithMethods(); + [param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + set => value.RequiresPublicMethods(); + } + + public Type ExtensionMembersPropertyAnnotatedAccessorMismatch + { + [ExpectedWarning("IL2073", "GetWithFields", Tool.Analyzer, "https://github.com/dotnet/roslyn/issues/80017")] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + get => ExtensionMembersDataFlow.GetWithFields(); + + [ExpectedWarning("IL2067", "value", "RequiresPublicFields")] + [param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + set => value.RequiresPublicFields(); } public Type ExtensionMembersPropertyRequires From 67e73b56c7d545ff58e892d165004cdbbe06d39e Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 25 Aug 2025 15:40:05 -0700 Subject: [PATCH 08/14] Don't propagate extension property annotations --- .../TrimAnalysis/FlowAnnotations.cs | 9 +++++++-- .../DataFlow/ExtensionMembersDataFlow.cs | 6 ++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs index c78d8c6641bd81..eedac3a8487662 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs @@ -173,7 +173,9 @@ internal static DynamicallyAccessedMemberTypes GetMethodParameterAnnotation(Para // Is this a property setter `value` parameter? if (parameterMethod!.MethodKind == MethodKind.PropertySet && damt == DynamicallyAccessedMemberTypes.None - && parameter.Ordinal == parameterMethod.Parameters.Length - 1) + && parameter.Ordinal == parameterMethod.Parameters.Length - 1 + // Do not propagate property-level DAM for C# 14 extension properties + && !parameterMethod.HasExtensionParameterOnType()) { var property = (IPropertySymbol)parameterMethod.AssociatedSymbol!; Debug.Assert(property != null); @@ -193,7 +195,10 @@ public static DynamicallyAccessedMemberTypes GetMethodReturnValueAnnotation(IMet // Is this a property getter? // If there are conflicts between the getter and the property annotation, // the getter annotation wins. (But DAMT.None is ignored) - if (method.MethodKind is MethodKind.PropertyGet && returnDamt == DynamicallyAccessedMemberTypes.None) + if (method.MethodKind is MethodKind.PropertyGet + && returnDamt == DynamicallyAccessedMemberTypes.None + // Do not propagate property-level DAM for C# 14 extension properties] + && !method.HasExtensionParameterOnType()) { var property = (IPropertySymbol)method.AssociatedSymbol!; Debug.Assert(property != null); diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs index 120cb6a832293a..de6e589b39791d 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs @@ -64,7 +64,7 @@ static void TestExtensionStaticMethodRequires() ExtensionMembers.ExtensionMembersStaticMethodRequires(); } - [UnexpectedWarning("IL2072", "ExtensionMembersProperty", "RequiresPublicMethods", Tool.Trimmer | Tool.NativeAot, "")] + [ExpectedWarning("IL2072", "ExtensionMembersProperty", "RequiresPublicMethods")] static void TestExtensionProperty() { var instance = GetWithFields(); @@ -75,7 +75,6 @@ static void TestExtensionProperty() [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMembersPropertyMismatch")] [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMembersPropertyMismatch")] [ExpectedWarning("IL2072", "ExtensionMembersPropertyMismatch", "RequiresPublicFields")] - [ExpectedWarning("IL2072", "ExtensionMembersPropertyMismatch", "GetWithFields", Tool.Analyzer, "")] static void TestExtensionPropertyMismatch() { var instance = GetWithMethods(); @@ -150,14 +149,13 @@ public Type ExtensionMembersProperty { get => ExtensionMembersDataFlow.GetWithMethods(); - [UnexpectedWarning("IL2067", "value", "RequiresPublicMethods", Tool.Trimmer | Tool.NativeAot, "")] + [ExpectedWarning("IL2067", "value", "RequiresPublicMethods")] set => value.RequiresPublicMethods(); } [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public Type ExtensionMembersPropertyMismatch { - [ExpectedWarning("IL2073", "GetWithFields", Tool.Analyzer, "")] get => ExtensionMembersDataFlow.GetWithFields(); [ExpectedWarning("IL2067", "value", "RequiresPublicFields")] From 2295d29916d42b7c7d1a75b60c777ff8aca05d52 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Mon, 25 Aug 2025 15:55:40 -0700 Subject: [PATCH 09/14] Avoid conflict warning for DAM on property --- .../DynamicallyAccessedMembersAnalyzer.cs | 5 +++++ .../DataFlow/ExtensionMembersDataFlow.cs | 21 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs index 31284d48b17d10..6f327374ee2cef 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs @@ -322,6 +322,11 @@ private static void VerifyDamOnPropertyAndAccessorMatch(SymbolAnalysisContext co || propertySymbol.GetDynamicallyAccessedMemberTypes() == DynamicallyAccessedMemberTypes.None) return; + // For C# 14 extension properties, property-level DAM does not propagate to accessors + // and we do not consider property vs accessor conflicts meaningful. Skip conflict checks. + if (methodSymbol.HasExtensionParameterOnType()) + return; + // None on the return type of 'get' matches unannotated if (methodSymbol.MethodKind == MethodKind.PropertyGet && methodSymbol.GetDynamicallyAccessedMemberTypesOnReturnType() != DynamicallyAccessedMemberTypes.None diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs index de6e589b39791d..4a2022c0152cd9 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs @@ -27,6 +27,7 @@ public static void Main() TestExtensionPropertyAnnotatedAccessor(); TestExtensionPropertyAnnotatedAccessorMismatch(); TestExtensionPropertyRequires(); + TestExtensionPropertyConflict(); } static void TestExtensionMethod() @@ -107,6 +108,14 @@ static void TestExtensionPropertyRequires() _ = GetWithFields().ExtensionMembersPropertyRequires; } + [UnexpectedWarning("IL2072", "ExtensionMembersPropertyConflict", "RequiresPublicFields", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/80017")] + static void TestExtensionPropertyConflict() + { + var instance = GetWithFields(); + instance.ExtensionMembersPropertyConflict.RequiresPublicFields(); + instance.ExtensionMembersPropertyConflict = GetWithFields(); + } + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] public static Type GetWithFields() => null; @@ -186,6 +195,18 @@ public Type ExtensionMembersPropertyRequires [RequiresUnreferencedCode("ExtensionMembersPropertyRequires")] get => null; } + + // Conflict scenario for extension property: both property-level and accessor-level DAM + // Analyzer behavior: skip IL2043 conflicts for extension properties (property metadata doesn't apply to accessors). + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + public Type ExtensionMembersPropertyConflict + { + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + get => ExtensionMembersDataFlow.GetWithFields(); + + [param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + set => value.RequiresPublicFields(); + } } } } From 13bdc7fc36de502096371e00506dcfec407ec9e6 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 26 Aug 2025 12:23:55 -0700 Subject: [PATCH 10/14] Add coverage for operators Including extension operators, and plain operators. --- .../TestChecker.cs | 6 ++ .../DataFlow/ExtensionMembersDataFlow.cs | 87 ++++++++++++++----- .../DataFlow/MethodParametersDataFlow.cs | 76 ++++++++++++++-- .../DataFlow/MethodReturnParameterDataFlow.cs | 53 +++++++++++ 4 files changed, 196 insertions(+), 26 deletions(-) diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs index 655fcf13b9aeb2..f6a3915c805be5 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs @@ -110,6 +110,12 @@ public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) CheckMember(node); } + public override void VisitOperatorDeclaration(OperatorDeclarationSyntax node) + { + base.VisitOperatorDeclaration(node); + CheckMember(node); + } + public override void VisitEventDeclaration(EventDeclarationSyntax node) { base.VisitEventDeclaration(node); diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs index 4a2022c0152cd9..3abcd49e785fe7 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs @@ -28,6 +28,8 @@ public static void Main() TestExtensionPropertyAnnotatedAccessorMismatch(); TestExtensionPropertyRequires(); TestExtensionPropertyConflict(); + TestExtensionOperators(); + TestExtensionOperatorsMismatch(); } static void TestExtensionMethod() @@ -35,7 +37,7 @@ static void TestExtensionMethod() GetWithFields().ExtensionMembersMethod(); } - [ExpectedWarning("IL2072", "GetWithMethods", nameof(ExtensionMembers.ExtensionMembersMethod))] + [ExpectedWarning("IL2072", nameof(GetWithMethods), nameof(ExtensionMembers.ExtensionMembersMethod))] static void TestExtensionMethodMismatch() { GetWithMethods().ExtensionMembersMethodMismatch(); @@ -52,8 +54,8 @@ static void TestExtensionMethodWithParams() GetWithFields().ExtensionMembersMethodWithParams(GetWithMethods()); } - [ExpectedWarning("IL2072", "GetWithMethods", nameof(ExtensionMembers.ExtensionMembersMethodWithParams))] - [ExpectedWarning("IL2072", "GetWithFields", nameof(ExtensionMembers.ExtensionMembersMethodWithParams))] + [ExpectedWarning("IL2072", nameof(GetWithMethods), nameof(ExtensionMembers.ExtensionMembersMethodWithParams))] + [ExpectedWarning("IL2072", nameof(GetWithFields), nameof(ExtensionMembers.ExtensionMembersMethodWithParams))] static void TestExtensionMethodWithParamsMismatch() { GetWithMethods().ExtensionMembersMethodWithParamsMismatch(GetWithFields()); @@ -65,7 +67,7 @@ static void TestExtensionStaticMethodRequires() ExtensionMembers.ExtensionMembersStaticMethodRequires(); } - [ExpectedWarning("IL2072", "ExtensionMembersProperty", "RequiresPublicMethods")] + [ExpectedWarning("IL2072", "ExtensionMembersProperty", nameof(DataFlowTypeExtensions.RequiresPublicMethods))] static void TestExtensionProperty() { var instance = GetWithFields(); @@ -73,9 +75,9 @@ static void TestExtensionProperty() instance.ExtensionMembersProperty = GetWithMethods(); } - [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMembersPropertyMismatch")] - [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMembersPropertyMismatch")] - [ExpectedWarning("IL2072", "ExtensionMembersPropertyMismatch", "RequiresPublicFields")] + [ExpectedWarning("IL2072", nameof(GetWithMethods), "ExtensionMembersPropertyMismatch")] + [ExpectedWarning("IL2072", nameof(GetWithMethods), "ExtensionMembersPropertyMismatch")] + [ExpectedWarning("IL2072", "ExtensionMembersPropertyMismatch", nameof(DataFlowTypeExtensions.RequiresPublicFields))] static void TestExtensionPropertyMismatch() { var instance = GetWithMethods(); @@ -83,7 +85,7 @@ static void TestExtensionPropertyMismatch() instance.ExtensionMembersPropertyMismatch = GetWithFields(); } - [UnexpectedWarning("IL2072", "ExtensionMembersPropertyAnnotatedAccessor", "RequiresPublicMethods", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/80017")] + [UnexpectedWarning("IL2072", "ExtensionMembersPropertyAnnotatedAccessor", nameof(DataFlowTypeExtensions.RequiresPublicMethods), Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/80017")] static void TestExtensionPropertyAnnotatedAccessor() { var instance = GetWithFields(); @@ -91,10 +93,10 @@ static void TestExtensionPropertyAnnotatedAccessor() instance.ExtensionMembersPropertyAnnotatedAccessor = GetWithMethods(); } - [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMembersPropertyAnnotatedAccessorMismatch")] - [ExpectedWarning("IL2072", "GetWithMethods", "ExtensionMembersPropertyAnnotatedAccessorMismatch")] - [ExpectedWarning("IL2072", "ExtensionMembersPropertyAnnotatedAccessorMismatch", "RequiresPublicFields")] - [ExpectedWarning("IL2072", "ExtensionMembersPropertyAnnotatedAccessorMismatch", "GetWithFields")] + [ExpectedWarning("IL2072", nameof(GetWithMethods), "ExtensionMembersPropertyAnnotatedAccessorMismatch")] + [ExpectedWarning("IL2072", nameof(GetWithMethods), "ExtensionMembersPropertyAnnotatedAccessorMismatch")] + [ExpectedWarning("IL2072", "ExtensionMembersPropertyAnnotatedAccessorMismatch", nameof(DataFlowTypeExtensions.RequiresPublicFields))] + [ExpectedWarning("IL2072", "ExtensionMembersPropertyAnnotatedAccessorMismatch", nameof(GetWithFields))] static void TestExtensionPropertyAnnotatedAccessorMismatch() { var instance = GetWithMethods(); @@ -108,7 +110,7 @@ static void TestExtensionPropertyRequires() _ = GetWithFields().ExtensionMembersPropertyRequires; } - [UnexpectedWarning("IL2072", "ExtensionMembersPropertyConflict", "RequiresPublicFields", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/80017")] + [UnexpectedWarning("IL2072", "ExtensionMembersPropertyConflict", nameof(DataFlowTypeExtensions.RequiresPublicFields), Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/80017")] static void TestExtensionPropertyConflict() { var instance = GetWithFields(); @@ -116,6 +118,28 @@ static void TestExtensionPropertyConflict() instance.ExtensionMembersPropertyConflict = GetWithFields(); } + [UnexpectedWarning("IL2072", nameof(ExtensionMembers.op_Addition), nameof(DataFlowTypeExtensions.RequiresPublicFields), Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/roslyn/issues/80017")] + [UnexpectedWarning("IL2062", nameof(DataFlowTypeExtensions.RequiresPublicFields), Tool.Analyzer, "https://github.com/dotnet/runtime/issues/119110")] + static void TestExtensionOperators() + { + var a = GetWithFields(); + var b = GetWithFields(); + var c = a + b; + c.RequiresPublicFields(); + } + + [ExpectedWarning("IL2072", nameof(GetWithMethods), nameof(ExtensionMembers.op_Subtraction), Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/119110")] + [ExpectedWarning("IL2072", nameof(GetWithMethods), nameof(ExtensionMembers.op_Subtraction), Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/119110")] + [ExpectedWarning("IL2072", nameof(ExtensionMembers.op_Subtraction), nameof(DataFlowTypeExtensions.RequiresPublicFields), Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/119110")] + [UnexpectedWarning("IL2062", nameof(DataFlowTypeExtensions.RequiresPublicFields), Tool.Analyzer, "https://github.com/dotnet/runtime/issues/119110")] + static void TestExtensionOperatorsMismatch() + { + var a = GetWithMethods(); + var b = GetWithMethods(); + var c = a - b; + c.RequiresPublicFields(); + } + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] public static Type GetWithFields() => null; @@ -130,7 +154,7 @@ public static class ExtensionMembers { public void ExtensionMembersMethod() => type.RequiresPublicFields(); - [ExpectedWarning("IL2067", "RequiresPublicMethods")] // The attribute gets copied to the internal class too, but that one is unused. Then it complains. + [ExpectedWarning("IL2067", nameof(DataFlowTypeExtensions.RequiresPublicMethods))] public void ExtensionMembersMethodMismatch() => type.RequiresPublicMethods(); [RequiresUnreferencedCode(nameof(ExtensionMembersMethodRequires))] @@ -142,8 +166,8 @@ public void ExtensionMembersMethodWithParams([DynamicallyAccessedMembers(Dynamic typeParam.RequiresPublicMethods(); } - [ExpectedWarning("IL2067", "type", "RequiresPublicMethods")] - [ExpectedWarning("IL2067", "typeParam", "RequiresPublicFields")] + [ExpectedWarning("IL2067", "type", nameof(DataFlowTypeExtensions.RequiresPublicMethods))] + [ExpectedWarning("IL2067", "typeParam", nameof(DataFlowTypeExtensions.RequiresPublicFields))] public void ExtensionMembersMethodWithParamsMismatch([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type typeParam) { type.RequiresPublicMethods(); @@ -158,7 +182,7 @@ public Type ExtensionMembersProperty { get => ExtensionMembersDataFlow.GetWithMethods(); - [ExpectedWarning("IL2067", "value", "RequiresPublicMethods")] + [ExpectedWarning("IL2067", "value", nameof(DataFlowTypeExtensions.RequiresPublicMethods))] set => value.RequiresPublicMethods(); } @@ -167,7 +191,7 @@ public Type ExtensionMembersPropertyMismatch { get => ExtensionMembersDataFlow.GetWithFields(); - [ExpectedWarning("IL2067", "value", "RequiresPublicFields")] + [ExpectedWarning("IL2067", "value", nameof(DataFlowTypeExtensions.RequiresPublicFields))] set => value.RequiresPublicFields(); } @@ -181,11 +205,11 @@ public Type ExtensionMembersPropertyAnnotatedAccessor public Type ExtensionMembersPropertyAnnotatedAccessorMismatch { - [ExpectedWarning("IL2073", "GetWithFields", Tool.Analyzer, "https://github.com/dotnet/roslyn/issues/80017")] + [ExpectedWarning("IL2073", nameof(ExtensionMembersDataFlow.GetWithFields), Tool.Analyzer, "https://github.com/dotnet/roslyn/issues/80017")] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] get => ExtensionMembersDataFlow.GetWithFields(); - [ExpectedWarning("IL2067", "value", "RequiresPublicFields")] + [ExpectedWarning("IL2067", "value", nameof(DataFlowTypeExtensions.RequiresPublicFields))] [param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] set => value.RequiresPublicFields(); } @@ -207,6 +231,29 @@ public Type ExtensionMembersPropertyConflict [param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] set => value.RequiresPublicFields(); } + + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + public static Type operator +( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type left, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type right) + { + left.RequiresPublicFields(); + right.RequiresPublicFields(); + return ExtensionMembersDataFlow.GetWithFields(); + } + + [ExpectedWarning("IL2067", "left", nameof(DataFlowTypeExtensions.RequiresPublicMethods))] + [ExpectedWarning("IL2067", "right", nameof(DataFlowTypeExtensions.RequiresPublicMethods))] + [ExpectedWarning("IL2073", nameof(ExtensionMembersDataFlow.GetWithMethods), Tool.Analyzer, "https://github.com/dotnet/roslyn/issues/80017")] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + public static Type operator -( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type left, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type right) + { + left.RequiresPublicMethods(); + right.RequiresPublicMethods(); + return ExtensionMembersDataFlow.GetWithMethods(); + } } } } diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs index 9e9d50d193ccb8..b718e678f55e43 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs @@ -15,6 +15,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow [ExpectedNoWarnings] [SetupCompileArgument("/unsafe")] [SetupLinkerArgument("--keep-metadata", "parametername")] + [SandboxDependency("Dependencies/TestSystemTypeBase.cs")] public class MethodParametersDataFlow { public static void Main() @@ -47,11 +48,12 @@ public static void Main() AnnotationOnUnsupportedParameter.Test(); AnnotationOnByRefParameter.Test(); WriteCapturedParameter.Test(); + OperatorParameters.Test(); } // Validate the error message when annotated parameter is passed to another annotated parameter - [ExpectedWarning("IL2067", "'sourceType'", "PublicParameterlessConstructorParameter(Type)", "'type'", "RequiresPublicConstructors(Type)")] - [ExpectedWarning("IL2067", nameof(DataFlowTypeExtensions) + "." + nameof(DataFlowTypeExtensions.RequiresNonPublicConstructors))] + [ExpectedWarning("IL2067", "'sourceType'", "PublicParameterlessConstructorParameter(Type)", "'type'", nameof(DataFlowTypeExtensions.RequiresPublicConstructors) + "(Type)")] + [ExpectedWarning("IL2067", nameof(DataFlowTypeExtensions) + "." + nameof(DataFlowTypeExtensions.RequiresNonPublicConstructors))] private static void PublicParameterlessConstructorParameter( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type sourceType) @@ -71,8 +73,8 @@ private static void PublicConstructorsParameter( type.RequiresNonPublicConstructors(); } - [ExpectedWarning("IL2067", nameof(DataFlowTypeExtensions) + "." + nameof(DataFlowTypeExtensions.RequiresPublicParameterlessConstructor))] - [ExpectedWarning("IL2067", nameof(DataFlowTypeExtensions) + "." + nameof(DataFlowTypeExtensions.RequiresPublicConstructors))] + [ExpectedWarning("IL2067", nameof(DataFlowTypeExtensions) + "." + nameof(DataFlowTypeExtensions.RequiresPublicParameterlessConstructor))] + [ExpectedWarning("IL2067", nameof(DataFlowTypeExtensions) + "." + nameof(DataFlowTypeExtensions.RequiresPublicConstructors))] private static void NonPublicConstructorsParameter( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type) @@ -82,7 +84,7 @@ private static void NonPublicConstructorsParameter( type.RequiresNonPublicConstructors(); } - [ExpectedWarning("IL2067", nameof(DataFlowTypeExtensions) + "." + nameof(DataFlowTypeExtensions.RequiresPublicConstructors))] + [ExpectedWarning("IL2067", nameof(DataFlowTypeExtensions) + "." + nameof(DataFlowTypeExtensions.RequiresPublicConstructors))] private void InstanceMethod( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type type) @@ -159,7 +161,7 @@ private void TwoAnnotatedParametersIntoOneValue( } // Validate the error message for the case of unannotated method return value passed to an annotated parameter. - [ExpectedWarning("IL2067", "'type'", "NoAnnotation(Type)", "'type'", "RequiresPublicParameterlessConstructor(Type)")] + [ExpectedWarning("IL2067", "'type'", "NoAnnotation(Type)", "'type'", nameof(DataFlowTypeExtensions.RequiresPublicParameterlessConstructor) + "(Type)")] private void NoAnnotation(Type type) { type.RequiresPublicParameterlessConstructor(); @@ -438,6 +440,68 @@ public TestType(int arg) { } private TestType(int arg1, int arg2) { } } + class OperatorParameters + { + public static void Test() + { + TestMatch(); + TestMismatch(); + } + + static void Use(Type t) + { + } + + static void TestMatch() + { + var left = GetWithFields(); + var right = GetWithFields(); + Use(left + right); + } + + [ExpectedWarning("IL2072", "left", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/119110")] + [ExpectedWarning("IL2072", "right", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/119110")] + static void TestMismatch() + { + var left = GetWithMethods(); + var right = GetWithMethods(); + Use(left - right); + } + + [ExpectedWarning("IL2063")] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + static OperatorType GetWithFields() => new OperatorType(); + + [ExpectedWarning("IL2063")] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + static OperatorType GetWithMethods() => new OperatorType(); + + sealed class OperatorType : TestSystemTypeBase + { + // Matching implementation + public static OperatorType operator +( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] OperatorType left, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] OperatorType right) + { + left.RequiresPublicFields(); + right.RequiresPublicFields(); + return new OperatorType(); + } + + // Mismatch in implementation + [ExpectedWarning("IL2067", "left", nameof(DataFlowTypeExtensions.RequiresPublicMethods))] + [ExpectedWarning("IL2067", "right", nameof(DataFlowTypeExtensions.RequiresPublicMethods))] + public static OperatorType operator -( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] OperatorType left, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] OperatorType right) + { + left.RequiresPublicMethods(); + right.RequiresPublicMethods(); + return new OperatorType(); + } + } + } + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] private static Type GetTypeWithPublicConstructors() { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/MethodReturnParameterDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/MethodReturnParameterDataFlow.cs index 03c88ec3341fff..7564dec3122c24 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/MethodReturnParameterDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/MethodReturnParameterDataFlow.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using Mono.Linker.Tests.Cases.Expectations.Assertions; using Mono.Linker.Tests.Cases.Expectations.Helpers; +using Mono.Linker.Tests.Cases.Expectations.Metadata; namespace Mono.Linker.Tests.Cases.DataFlow { @@ -12,6 +13,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow // - so the main validation is done by the ExpectedWarning attributes. [SkipKeptItemsValidation] [ExpectedNoWarnings] + [SandboxDependency("Dependencies/TestSystemTypeBase.cs")] public class MethodReturnParameterDataFlow { public static void Main() @@ -37,6 +39,7 @@ public static void Main() UnsupportedReturnTypeAndParameter(null); AnnotationOnUnsupportedReturnType.Test(); + OperatorReturn.Test(); } static Type NoRequirements() @@ -259,5 +262,55 @@ class TestType { public TestType() { } } + + class OperatorReturn + { + public static void Test() + { + TestMatch(); + TestMismatch(); + } + + [UnexpectedWarning("IL2062", nameof(DataFlowTypeExtensions.RequiresPublicFields), Tool.Analyzer, "https://github.com/dotnet/runtime/issues/119110")] + static void TestMatch() + { + var result = new OperatorType() + new OperatorType(); + result.RequiresPublicFields(); + } + + [ExpectedWarning("IL2072", nameof(DataFlowTypeExtensions.RequiresPublicMethods), Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/119110")] + [UnexpectedWarning("IL2062", nameof(DataFlowTypeExtensions.RequiresPublicMethods), Tool.Analyzer, "https://github.com/dotnet/runtime/issues/119110")] + static void TestMismatch() + { + var result = new OperatorType() - new OperatorType(); + result.RequiresPublicMethods(); + } + + [ExpectedWarning("IL2063")] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + static OperatorType GetWithFields() => new OperatorType(); + + [ExpectedWarning("IL2063")] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + static OperatorType GetWithMethods() => new OperatorType(); + + private sealed class OperatorType : TestSystemTypeBase + { + // Matching implementation + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + public static OperatorType operator +(OperatorType left, OperatorType right) + { + return GetWithFields(); + } + + // Mismatch in implementation + [ExpectedWarning("IL2073", nameof(GetWithMethods))] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + public static Type operator -(OperatorType left, OperatorType right) + { + return GetWithMethods(); + } + } + } } } From 59f254153e4f9db4878957020a733ccfe335a445 Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 26 Aug 2025 12:43:20 -0700 Subject: [PATCH 11/14] Add comment about property annotations --- .../DataFlow/ExtensionMembersDataFlow.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs index 3abcd49e785fe7..bdbfaec5b3201e 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ExtensionMembersDataFlow.cs @@ -177,6 +177,8 @@ public void ExtensionMembersMethodWithParamsMismatch([DynamicallyAccessedMembers [RequiresUnreferencedCode(nameof(ExtensionMembersStaticMethodRequires))] public static void ExtensionMembersStaticMethodRequires() { } + // Annotations on extension properties have no effect: + // https://github.com/dotnet/runtime/issues/119113 [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public Type ExtensionMembersProperty { @@ -186,6 +188,8 @@ public Type ExtensionMembersProperty set => value.RequiresPublicMethods(); } + // Annotations on extension properties have no effect: + // https://github.com/dotnet/runtime/issues/119113 [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public Type ExtensionMembersPropertyMismatch { @@ -220,8 +224,8 @@ public Type ExtensionMembersPropertyRequires get => null; } - // Conflict scenario for extension property: both property-level and accessor-level DAM - // Analyzer behavior: skip IL2043 conflicts for extension properties (property metadata doesn't apply to accessors). + // Annotations on extension properties have no effect: + // https://github.com/dotnet/runtime/issues/119113 [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public Type ExtensionMembersPropertyConflict { From 5417ececaf84ca77bfc3e5494f9a24f3326c318c Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Tue, 26 Aug 2025 14:20:19 -0700 Subject: [PATCH 12/14] Silence warnings --- Directory.Build.props | 4 ++++ .../src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 99eee260233d71..21c3a57182edbc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -269,6 +269,10 @@ $(WarningsNotAsErrors);NU1901;NU1902;NU1903;NU1904 $(NoWarn);CS8500;CS8969 + + $(NoWarn);IDE0100 + + $(NoWarn);IDE0060 $(NoWarn);CS1591 diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj b/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj index 49b09a3c07510b..e1b726443cb646 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/ILLink.RoslynAnalyzer.csproj @@ -10,7 +10,7 @@ $(NoWarn);CS8524 - $(NoWarn);CS0436 + $(NoWarn);CS0436 cs