diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs index 737d99d93be908..cddf5abf3deb8a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection.Metadata; @@ -523,7 +524,7 @@ protected override TypeAnnotations CreateValueFromKey(TypeDesc key) // Look for the compiler generated backing field. If it doesn't work out simply move on. In such case we would still // propagate the annotation to the setter/getter and later on when analyzing the setter/getter we will warn // that the field (which ever it is) must be annotated as well. - ScanMethodBodyForFieldAccess(methodBody, write: true, out backingFieldFromSetter); + backingFieldFromSetter = GetAutoPropertyCompilerGeneratedField(methodBody, isWriteAccessor: true); } MethodAnnotations? setterAnnotation = null; @@ -560,16 +561,14 @@ protected override TypeAnnotations CreateValueFromKey(TypeDesc key) MethodDesc getMethod = property.GetMethod; if (getMethod != null) { - - // Abstract property backing field propagation doesn't make sense, and any derived property will be validated - // to have the exact same annotations on getter/setter, and thus if it has a detectable backing field that will be validated as well. + // Look only at compiler-generated accessors MethodIL methodBody = _ilProvider.GetMethodIL(getMethod); if (methodBody != null) { // Look for the compiler generated backing field. If it doesn't work out simply move on. In such case we would still // propagate the annotation to the setter/getter and later on when analyzing the setter/getter we will warn // that the field (which ever it is) must be annotated as well. - ScanMethodBodyForFieldAccess(methodBody, write: false, out backingFieldFromGetter); + backingFieldFromGetter = GetAutoPropertyCompilerGeneratedField(methodBody, isWriteAccessor: false); } MethodAnnotations? getterAnnotation = null; @@ -593,27 +592,41 @@ protected override TypeAnnotations CreateValueFromKey(TypeDesc key) } } - FieldDesc? backingField; - if (backingFieldFromGetter != null && backingFieldFromSetter != null && - backingFieldFromGetter != backingFieldFromSetter) - { - _logger.LogWarning(property, DiagnosticId.DynamicallyAccessedMembersCouldNotFindBackingField, property.GetDisplayName()); - backingField = null; - } - else - { - backingField = backingFieldFromGetter ?? backingFieldFromSetter; - } - - if (backingField != null) + if (IsAutoProperty(property)) { - if (annotatedFields.Any(a => a.Field == backingField)) + FieldDesc? backingField = null; + if ((property.SetMethod is not null + && property.SetMethod.HasCustomAttribute("System.Runtime.CompilerServices", "CompilerGeneratedAttribute") + && backingFieldFromSetter is null) + || (property.GetMethod is not null + && property.GetMethod.HasCustomAttribute("System.Runtime.CompilerServices", "CompilerGeneratedAttribute") + && backingFieldFromGetter is null)) { - _logger.LogWarning(backingField, DiagnosticId.DynamicallyAccessedMembersOnPropertyConflictsWithBackingField, property.GetDisplayName(), backingField.GetDisplayName()); + // We failed to find the backing field of an auto-property accessor + _logger.LogWarning(property, DiagnosticId.DynamicallyAccessedMembersCouldNotFindBackingField, property.GetDisplayName()); + } + else if (backingFieldFromGetter is not null && backingFieldFromSetter is not null + && backingFieldFromSetter != backingFieldFromGetter) + { + // We found two different backing fields for the getter and the setter + _logger.LogWarning(property, DiagnosticId.DynamicallyAccessedMembersCouldNotFindBackingField, property.GetDisplayName()); } else { - annotatedFields.Add(new FieldAnnotation(backingField, annotation)); + // We either have a single auto-property accessor or both accessors point to the same backing field + backingField = backingFieldFromSetter ?? backingFieldFromGetter; + } + + if (backingField != null) + { + if (annotatedFields.Any(a => a.Field == backingField)) + { + _logger.LogWarning(backingField, DiagnosticId.DynamicallyAccessedMembersOnPropertyConflictsWithBackingField, property.GetDisplayName(), backingField.GetDisplayName()); + } + else + { + annotatedFields.Add(new FieldAnnotation(backingField, annotation)); + } } } } @@ -651,13 +664,33 @@ protected override TypeAnnotations CreateValueFromKey(TypeDesc key) return attrs; } - private static bool ScanMethodBodyForFieldAccess(MethodIL body, bool write, out FieldDesc? found) + /// + /// Returns true if the property has a single accessor which is compiler generated, + /// indicating that it is an auto-property. + /// + /// + /// Ideally this would be tightened to only return true if both accessors are auto-property accessors, + /// but it allows for either for back compatibility with existing behavior. + /// + private static bool IsAutoProperty(PropertyPseudoDesc property) + { + return property.SetMethod?.HasCustomAttribute("System.Runtime.CompilerServices", "CompilerGeneratedAttribute") == true + || property.GetMethod?.HasCustomAttribute("System.Runtime.CompilerServices", "CompilerGeneratedAttribute") == true; + } + + private static FieldDesc? GetAutoPropertyCompilerGeneratedField(MethodIL body, bool isWriteAccessor) { // Tries to find the backing field for a property getter/setter. // Returns true if this is a method body that we can unambiguously analyze. // The found field could still be null if there's no backing store. - found = null; + // Only analyze compiler-generated accessors + if (!body.OwningMethod.HasCustomAttribute("System.Runtime.CompilerServices", "CompilerGeneratedAttribute")) + { + return null; + } + + FieldDesc? found = null; ILReader ilReader = new ILReader(body.GetILBytes()); while (ilReader.HasNext) @@ -665,45 +698,35 @@ private static bool ScanMethodBodyForFieldAccess(MethodIL body, bool write, out ILOpcode opcode = ilReader.ReadILOpcode(); switch (opcode) { - case ILOpcode.ldsfld when !write: - case ILOpcode.ldfld when !write: - case ILOpcode.stsfld when write: - case ILOpcode.stfld when write: + case ILOpcode.ldsfld when !isWriteAccessor: + case ILOpcode.ldfld when !isWriteAccessor: + case ILOpcode.stsfld when isWriteAccessor: + case ILOpcode.stfld when isWriteAccessor: + { + // Multiple field accesses - ambiguous, fail + if (found != null) { - // This writes/reads multiple fields - can't guess which one is the backing store. - // Return failure. - if (found != null) - { - found = null; - return false; - } - found = (FieldDesc)body.GetObject(ilReader.ReadILToken()); + return null; } + found = (FieldDesc)body.GetObject(ilReader.ReadILToken()); break; + } default: ilReader.Skip(opcode); break; } } - if (found == null) - { - // Doesn't access any fields. Could be e.g. "Type Foo => typeof(Bar);" - // Return success. - return true; - } - - if (found.OwningType != body.OwningMethod.OwningType || + if (found is null || + found.OwningType != body.OwningMethod.OwningType || found.IsStatic != body.OwningMethod.Signature.IsStatic || !found.HasCustomAttribute("System.Runtime.CompilerServices", "CompilerGeneratedAttribute")) { - // A couple heuristics to make sure we got the right field. - // Return failure. - found = null; - return false; + // Heuristics failed - not a backing field + return null; } - return true; + return found; } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/IPropertySymbolExtensions.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/IPropertySymbolExtensions.cs index d4697ee60ed040..8174b089bc2922 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/IPropertySymbolExtensions.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/IPropertySymbolExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace ILLink.RoslynAnalyzer { @@ -30,5 +31,37 @@ public static class IPropertySymbolExtensions } return setMethod; } + + public static bool IsAutoProperty(this IPropertySymbol property) + { + if (property.IsAbstract) + return false; + + return (property.GetMethod?.IsAutoAccessor() ?? false) || (property.SetMethod?.IsAutoAccessor() ?? false); + } + + private static bool IsAutoAccessor(this IMethodSymbol method) + { + if (method == null || method.IsAbstract) + return false; + + foreach (var decl in method.DeclaringSyntaxReferences) + { + var syntax = decl.GetSyntax(); + // Auto property accessors have no body in their syntax + switch (syntax) + { + case AccessorDeclarationSyntax a: + if (a.Body is not null) + return false; + if (a.ExpressionBody is not null) + return false; + return true; + default: + break; + } + } + return false; + } } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs index eedac3a8487662..dd210c6eea3297 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs @@ -117,6 +117,13 @@ internal static DynamicallyAccessedMemberTypes GetFieldAnnotation(IFieldSymbol f if (!field.OriginalDefinition.Type.IsTypeInterestingForDataflow(isByRef: field.RefKind is not RefKind.None)) return DynamicallyAccessedMemberTypes.None; + if (field.AssociatedSymbol is IPropertySymbol property) + { + // If this is an auto property, we get the property annotation + if (property.IsAutoProperty()) + return property.GetDynamicallyAccessedMemberTypes(); + } + return field.GetDynamicallyAccessedMemberTypes(); } diff --git a/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs b/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs index b03346959a2328..200710a9b1a679 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/FlowAnnotations.cs @@ -354,7 +354,7 @@ TypeAnnotations BuildTypeAnnotations(TypeDefinition type) FieldDefinition? backingFieldFromSetter = null; // Propagate the annotation to the setter method - MethodDefinition setMethod = property.SetMethod; + MethodDefinition? setMethod = property.SetMethod; if (setMethod != null) { @@ -365,7 +365,7 @@ TypeAnnotations BuildTypeAnnotations(TypeDefinition type) // Look for the compiler generated backing field. If it doesn't work out simply move on. In such case we would still // propagate the annotation to the setter/getter and later on when analyzing the setter/getter we will warn // that the field (which ever it is) must be annotated as well. - ScanMethodBodyForFieldAccess(setMethod.Body, write: true, out backingFieldFromSetter); + backingFieldFromSetter = GetAutoPropertyCompilerGeneratedField(setMethod.Body, isWriteAccessor: true); } MethodAnnotations? setterAnnotation = null; @@ -399,7 +399,7 @@ TypeAnnotations BuildTypeAnnotations(TypeDefinition type) FieldDefinition? backingFieldFromGetter = null; // Propagate the annotation to the getter method - MethodDefinition getMethod = property.GetMethod; + MethodDefinition? getMethod = property.GetMethod; if (getMethod != null) { @@ -410,7 +410,7 @@ TypeAnnotations BuildTypeAnnotations(TypeDefinition type) // Look for the compiler generated backing field. If it doesn't work out simply move on. In such case we would still // propagate the annotation to the setter/getter and later on when analyzing the setter/getter we will warn // that the field (which ever it is) must be annotated as well. - ScanMethodBodyForFieldAccess(getMethod.Body, write: false, out backingFieldFromGetter); + backingFieldFromGetter = GetAutoPropertyCompilerGeneratedField(getMethod.Body, isWriteAccessor: false); } MethodAnnotations? getterAnnotation = null; foreach (var annotatedMethod in annotatedMethods) @@ -433,27 +433,41 @@ TypeAnnotations BuildTypeAnnotations(TypeDefinition type) } } - FieldDefinition? backingField; - if (backingFieldFromGetter != null && backingFieldFromSetter != null && - backingFieldFromGetter != backingFieldFromSetter) + if (IsAutoProperty(property)) { - _context.LogWarning(property, DiagnosticId.DynamicallyAccessedMembersCouldNotFindBackingField, property.GetDisplayName()); - backingField = null; - } - else - { - backingField = backingFieldFromGetter ?? backingFieldFromSetter; - } - - if (backingField != null) - { - if (annotatedFields.Any(a => a.Field == backingField)) + FieldDefinition? backingField = null; + // If it's an annotated auto-prop, we should be able to find the compiler generated field + if ((property.SetMethod is not null + && property.SetMethod.IsCompilerGenerated() + && backingFieldFromSetter is null) + || (property.GetMethod is not null + && property.GetMethod.IsCompilerGenerated() + && backingFieldFromGetter is null)) + { + // We failed to find the backing field of an auto-property accessor + _context.LogWarning(property, DiagnosticId.DynamicallyAccessedMembersCouldNotFindBackingField, property.GetDisplayName()); + } + else if (backingFieldFromGetter is not null && backingFieldFromSetter is not null + && backingFieldFromSetter != backingFieldFromGetter) { - _context.LogWarning(backingField, DiagnosticId.DynamicallyAccessedMembersOnPropertyConflictsWithBackingField, property.GetDisplayName(), backingField.GetDisplayName()); + // We found two different backing fields for the getter and the setter + _context.LogWarning(property, DiagnosticId.DynamicallyAccessedMembersCouldNotFindBackingField, property.GetDisplayName()); } else { - annotatedFields.Add(new FieldAnnotation(backingField, annotation)); + // We either have a single auto-property accessor or both accessors point to the same backing field + backingField = backingFieldFromSetter ?? backingFieldFromGetter; + } + if (backingField != null) + { + if (annotatedFields.Any(a => a.Field == backingField)) + { + _context.LogWarning(backingField, DiagnosticId.DynamicallyAccessedMembersOnPropertyConflictsWithBackingField, property.GetDisplayName(), backingField.GetDisplayName()); + } + else + { + annotatedFields.Add(new FieldAnnotation(backingField, annotation)); + } } } } @@ -478,6 +492,20 @@ TypeAnnotations BuildTypeAnnotations(TypeDefinition type) return new TypeAnnotations(type, typeAnnotation, annotatedMethods.ToArray(), annotatedFields.ToArray(), typeGenericParameterAnnotations); } + /// + /// Returns true if the property has a single accessor which is compiler generated, + /// indicating that it is an auto-property. + /// + /// + /// Ideally this would be tightened to only return true if both accessors are auto-property accessors, + /// but it allows for either for back compatibility with existing behavior. + /// + private static bool IsAutoProperty(PropertyDefinition property) + { + return property.SetMethod?.IsCompilerGenerated() == true + || property.GetMethod?.IsCompilerGenerated() == true; + } + private IList? GetGeneratedTypeAttributes(TypeDefinition typeDef) { if (!CompilerGeneratedNames.IsStateMachineOrDisplayClass(typeDef.Name)) @@ -489,29 +517,32 @@ TypeAnnotations BuildTypeAnnotations(TypeDefinition type) return attrs; } - bool ScanMethodBodyForFieldAccess(MethodBody body, bool write, out FieldDefinition? found) + FieldDefinition? GetAutoPropertyCompilerGeneratedField(MethodBody body, bool isWriteAccessor) { // Tries to find the backing field for a property getter/setter. // Returns true if this is a method body that we can unambiguously analyze. // The found field could still be null if there's no backing store. + // Auto properties have CompilerGeneratedAttribute + if (!body.Method.IsCompilerGenerated()) + return null; + FieldReference? foundReference = null; foreach (Instruction instruction in _context.GetMethodIL(body).Instructions) { switch (instruction.OpCode.Code) { - case Code.Ldsfld when !write: - case Code.Ldfld when !write: - case Code.Stsfld when write: - case Code.Stfld when write: + case Code.Ldsfld when !isWriteAccessor: + case Code.Ldfld when !isWriteAccessor: + case Code.Stsfld when isWriteAccessor: + case Code.Stfld when isWriteAccessor: if (foundReference != null) { // This writes/reads multiple fields - can't guess which one is the backing store. // Return failure. - found = null; - return false; + return null; } foundReference = (FieldReference)instruction.Operand; @@ -523,17 +554,16 @@ bool ScanMethodBodyForFieldAccess(MethodBody body, bool write, out FieldDefiniti { // Doesn't access any fields. Could be e.g. "Type Foo => typeof(Bar);" // Return success. - found = null; - return true; + return null; } - found = _context.Resolve(foundReference); + var found = _context.Resolve(foundReference); if (found == null) { // If the field doesn't resolve, it can't be a field on the current type // anyway. Return failure. - return false; + return null; } if (found.DeclaringType != body.Method.DeclaringType || @@ -542,11 +572,10 @@ bool ScanMethodBodyForFieldAccess(MethodBody body, bool write, out FieldDefiniti { // A couple heuristics to make sure we got the right field. // Return failure. - found = null; - return false; + return null; } - return true; + return found; } internal void ValidateMethodAnnotationsAreSame(OverrideInformation ov) diff --git a/src/tools/illink/src/linker/Linker/MethodDefinitionExtensions.cs b/src/tools/illink/src/linker/Linker/MethodDefinitionExtensions.cs index 8d0884f3115542..352f5443213822 100644 --- a/src/tools/illink/src/linker/Linker/MethodDefinitionExtensions.cs +++ b/src/tools/illink/src/linker/Linker/MethodDefinitionExtensions.cs @@ -146,5 +146,20 @@ internal static ParameterProxyEnumerable GetMetadataParameters(this MethodDefini int implicitThisOffset = method.HasImplicitThis() ? 1 : 0; return new ParameterProxyEnumerable(implicitThisOffset, method.Parameters.Count + implicitThisOffset, method); } + + public static bool IsCompilerGenerated(this MethodDefinition field) + { + if (!field.HasCustomAttributes) + return false; + + foreach (var ca in field.CustomAttributes) + { + var caType = ca.AttributeType; + if (caType.Name == "CompilerGeneratedAttribute" && caType.Namespace == "System.Runtime.CompilerServices") + return true; + } + + return false; + } } } diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs index 2d29cc0d9617ba..3c3287cabab981 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs @@ -203,6 +203,12 @@ public Task FieldDataFlow() return RunTest(nameof(FieldDataFlow)); } + [Fact] + public Task FieldKeyword() + { + return RunTest(); + } + [Fact] public Task FileScopedClasses() { diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceCompatibilityTestUtils.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceCompatibilityTestUtils.cs index a5169775cc45bc..ac901c6ce109a8 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceCompatibilityTestUtils.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceCompatibilityTestUtils.cs @@ -42,7 +42,7 @@ static MetadataReference CreateReferencedMetadata(string referencedSource) var refs = SourceGenerators.Tests.LiveReferencePack.GetMetadataReferences(); var referencedCompilation = CSharpCompilation.Create( "ReferencedAssembly", - new[] { SyntaxFactory.ParseSyntaxTree(referencedSource) }, + new[] { SyntaxFactory.ParseSyntaxTree(referencedSource, new CSharpParseOptions(languageVersion: LanguageVersion.Preview)) }, refs, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); var referencedImage = new MemoryStream(); diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs index d5b21354db6f30..16e4c6d992114c 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseCompilation.cs @@ -30,7 +30,7 @@ public static (CompilationWithAnalyzers Compilation, SemanticModel SemanticModel IEnumerable? additionalReferences = null, IEnumerable? additionalSources = null, IEnumerable? additionalFiles = null) - => CreateCompilation(CSharpSyntaxTree.ParseText(src), consoleApplication, globalAnalyzerOptions, additionalReferences, additionalSources, additionalFiles); + => CreateCompilation(CSharpSyntaxTree.ParseText(src, new CSharpParseOptions(LanguageVersion.Preview)), consoleApplication, globalAnalyzerOptions, additionalReferences, additionalSources, additionalFiles); public static (CompilationWithAnalyzers Compilation, SemanticModel SemanticModel, List ExceptionDiagnostics) CreateCompilation( SyntaxTree src, @@ -58,7 +58,7 @@ public static (CompilationWithAnalyzers Compilation, SemanticModel SemanticModel Path.Combine(sharedDir, "RequiresDynamicCodeAttribute.cs"), }; - sources.AddRange(commonSourcePaths.Select(p => CSharpSyntaxTree.ParseText(File.ReadAllText(p), path: p))); + sources.AddRange(commonSourcePaths.Select(p => CSharpSyntaxTree.ParseText(File.ReadAllText(p), new CSharpParseOptions(languageVersion: LanguageVersion.Preview), path: p))); var comp = CSharpCompilation.Create( assemblyName: "test", syntaxTrees: sources, diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs index baa3a5b96831b0..c548ecf20887cd 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestCaseUtils.cs @@ -61,11 +61,14 @@ public static async Task RunTestFile(string suiteName, string testName, bool all Assert.True(File.Exists(testPath), $"{testPath} should exist"); var tree = SyntaxFactory.ParseSyntaxTree( SourceText.From(File.OpenRead(testPath), Encoding.UTF8), + new CSharpParseOptions(languageVersion: LanguageVersion.Preview), path: testPath); var testDependenciesSource = GetTestDependencies(testCaseDir, tree) .Where(f => Path.GetExtension(f) == ".cs") - .Select(f => SyntaxFactory.ParseSyntaxTree(SourceText.From(File.OpenRead(f)))); + .Select(f => SyntaxFactory.ParseSyntaxTree(SourceText.From( + File.OpenRead(f)), + new CSharpParseOptions(languageVersion: LanguageVersion.Preview))); var additionalFiles = GetAdditionalFiles(rootSourceDir, tree); var (comp, model, exceptionDiagnostics) = TestCaseCompilation.CreateCompilation( diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs index f6a3915c805be5..29f4d290532563 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/TestChecker.cs @@ -234,7 +234,8 @@ static bool IsExpectedDiagnostic(AttributeSyntax attribute) case nameof(UnexpectedWarningAttribute): case nameof(LogContainsAttribute): var args = LinkerTestBase.GetAttributeArguments(attribute); - if (args.TryGetValue("ProducedBy", out var producedBy)) + if (args.TryGetValue("ProducedBy", out var producedBy) + || args.TryGetValue("producedBy", out producedBy)) { // Skip if this warning is not expected to be produced by any of the analyzers that we are currently testing. return GetProducedBy(producedBy).HasFlag(Tool.Analyzer); diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/DataFlowTests.g.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/DataFlowTests.g.cs index dc1ff8cb67c8ea..077a0c4af576ba 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/DataFlowTests.g.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/generated/ILLink.RoslynAnalyzer.Tests.Generator/ILLink.RoslynAnalyzer.Tests.TestCaseGenerator/DataFlowTests.g.cs @@ -7,6 +7,12 @@ namespace ILLink.RoslynAnalyzer.Tests public sealed partial class DataFlowTests : LinkerTestBase { + [Fact] + public Task DataflowFailsToConverge() + { + return RunTest(allowMissingWarnings: true); + } + [Fact] public Task ExponentialDataFlow() { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AnnotatedMembersAccessedViaReflection.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AnnotatedMembersAccessedViaReflection.cs index 3db70ba943f1ff..f719ae746f7434 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AnnotatedMembersAccessedViaReflection.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/AnnotatedMembersAccessedViaReflection.cs @@ -234,7 +234,7 @@ static void AnnotatedAttributeConstructor() { } - // DynamicDependency is not supported yet in the analyzer + // DynamicDependency is not supported yet in the analyzer [ExpectedWarning("IL2111", nameof(MethodWithSingleAnnotatedParameter), Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/83080")] [DynamicDependency(DynamicallyAccessedMemberTypes.PublicMethods, typeof(AnnotatedMethodParameters))] static void DynamicDependency() @@ -247,7 +247,7 @@ static void DynamicDependencySuppressedByRUC() { } - // DynamicDependency is not supported yet in the analyzer + // DynamicDependency is not supported yet in the analyzer [ExpectedWarning("IL2111", nameof(MethodWithSingleAnnotatedParameter), Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/83080")] [DynamicDependency(nameof(MethodWithSingleAnnotatedParameter), typeof(AnnotatedMethodParameters))] static void DynamicDependencyByName() @@ -681,14 +681,14 @@ static void DynamicallyAccessedMembersAll2() typeof(AnnotatedProperty).RequiresAll(); } - // Analyzer doesn't produce this warning - [ExpectedWarning("IL2110", nameof(Property1WithAnnotation), Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/linker/issues/2628")] + // Analyzer doesn't produce this warning + [ExpectedWarning("IL2110", nameof(Property1WithAnnotation))] static void DynamicallyAccessedFields() { typeof(AnnotatedProperty).RequiresNonPublicFields(); } - // Analyzer doesn't recognize Linq.Expressions + // Analyzer doesn't recognize Linq.Expressions [ExpectedWarning("IL2111", nameof(Property1WithAnnotation) + ".set", Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/runtime/issues/101148")] static void LdToken() { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FieldKeyword.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FieldKeyword.cs new file mode 100644 index 00000000000000..388410874db6cb --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FieldKeyword.cs @@ -0,0 +1,147 @@ +// 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; + +namespace Mono.Linker.Tests.Cases.DataFlow +{ + [ExpectedNoWarnings] + [Kept] + public class FieldKeyword + { + [Kept] + public static void Main() + { + WithMethods = typeof(FieldKeyword); + WithFields = typeof(FieldKeyword); + _ = WithNone; + WithNone = null; + _ = WithFields; + _ = WithMethods; + _ = MismatchAssignedFromField; + _ = MismatchAssignedFromField_FieldAnnotated; + _ = MismatchAssignedToField; + _ = MismatchAssignedToField_FieldAnnotated; + MismatchAssignedFromValue = null; + AssignNoneToMethods(); + } + + [Kept] + [KeptBackingField] + static Type WithNone { [Kept] get => field; [Kept] set => field = value; } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods), + KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] + [Kept] + [KeptBackingField] + static Type WithMethods + { + [Kept] + [ExpectedWarning("IL2078", "return value", nameof(WithMethods), "BackingField")] + get => field; + [Kept] + set => field = value; + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields), + KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] + [Kept] + [KeptBackingField] + static Type WithFields + { + [Kept] + [ExpectedWarning("IL2078", "return value", nameof(WithFields), "BackingField")] + get => field; + [Kept] + set => field = value; + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields), + KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] + [Kept] + [KeptBackingField] + static Type MismatchAssignedToField + { + [ExpectedWarning("IL2078", "return value", nameof(MismatchAssignedToField))] + [Kept] + get + { + field = WithNone; + return field; + } + } + + [field: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields), + KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] + [Kept] + [KeptBackingField] + static Type MismatchAssignedToField_FieldAnnotated + { + [ExpectedWarning("IL2074", nameof(WithNone))] + [Kept] + [return: KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + get + { + field = WithNone; + return field; + } + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields), + KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] + [Kept] + [KeptBackingField] + static Type MismatchAssignedFromField + { + [Kept] + [ExpectedWarning("IL2077", nameof(MismatchAssignedFromField), nameof(WithMethods))] + [ExpectedWarning("IL2078", "return value")] + get + { + WithMethods = field; + return field; + } + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields), + KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] + [field: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + [Kept] + [KeptBackingField] + static Type MismatchAssignedFromField_FieldAnnotated + { + [Kept] + [ExpectedWarning("IL2077", nameof(MismatchAssignedFromField), nameof(WithMethods))] + get + { + WithMethods = field; + return field; + } + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields), + KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] + [Kept] + [KeptBackingField] + static Type MismatchAssignedFromValue + { + [ExpectedWarning("IL2067", nameof(WithMethods))] + [Kept] + set + { + WithMethods = value; + field = value; + } + } + + [ExpectedWarning("IL2072")] + [Kept] + static void AssignNoneToMethods() + { + WithMethods = WithNone; + } + } +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs index 6b469c873b7272..1fc8ed65bc8379 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs @@ -50,6 +50,10 @@ public static void Main() ImplicitIndexerAccess.Test(); AnnotationOnUnsupportedType.Test(); + AutoPropertyUnrecognizedField.Test(); + + OneAutoPropAccessor.Test(); + AutoPropertySyntaxVariations.Test(); } [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] @@ -242,6 +246,7 @@ static Type PropertyWhichLooksLikeCompilerGenerated // See above comment about fake compiler generated backing fields - this warning is expected from the analyzer [ExpectedWarning("IL2078", nameof(TestAutomaticPropagationType) + "." + nameof(PropertyWhichLooksLikeCompilerGenerated) + ".get", nameof(TestAutomaticPropagationType) + "." + nameof(PropertyWhichLooksLikeCompilerGenerated_Field), Tool.Analyzer, "")] + [CompilerGenerated] get { return PropertyWhichLooksLikeCompilerGenerated_Field; @@ -284,7 +289,7 @@ public void TestPropertyWithDifferentBackingFields() // Analyzer doesn't try to detect backing fields of properties: https://github.com/dotnet/linker/issues/2273 [ExpectedWarning("IL2042", - "Mono.Linker.Tests.Cases.DataFlow.PropertyDataFlow.TestAutomaticPropagationType.PropertyWithDifferentBackingFields", Tool.Trimmer | Tool.NativeAot, "")] + "Mono.Linker.Tests.Cases.DataFlow.PropertyDataFlow.TestAutomaticPropagationType.PropertyWithDifferentBackingFields", Tool.Trimmer | Tool.NativeAot, "Requires IL")] [ExpectedWarning("IL2078", nameof(TestAutomaticPropagationType) + "." + nameof(PropertyWithDifferentBackingFields) + ".get", "Type", Tool.Analyzer, "")] @@ -293,11 +298,13 @@ Type PropertyWithDifferentBackingFields { [ExpectedWarning("IL2078", nameof(TestAutomaticPropagationType) + "." + nameof(PropertyWithDifferentBackingFields) + ".get", Tool.Trimmer | Tool.NativeAot, "")] + [CompilerGenerated] get { return PropertyWithDifferentBackingFields_GetterField; } + [CompilerGenerated] set { PropertyWithDifferentBackingFields_SetterField = value; @@ -324,11 +331,13 @@ Type PropertyWithExistingAttributes // On property/accessor mismatch, ILLink warns on accessor and analyzer warns on property https://github.com/dotnet/linker/issues/2654 [ExpectedWarning("IL2043", "PropertyWithExistingAttributes", "PropertyWithExistingAttributes.get", Tool.Trimmer | Tool.NativeAot, "")] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + [CompilerGenerated] get { return PropertyWithExistingAttributes_Field; } // On property/accessor mismatch, ILLink warns on accessor and analyzer warns on property https://github.com/dotnet/linker/issues/2654 [ExpectedWarning("IL2043", "PropertyWithExistingAttributes", "PropertyWithExistingAttributes.set", Tool.Trimmer | Tool.NativeAot, "")] [param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + [CompilerGenerated] set { PropertyWithExistingAttributes_Field = value; } } @@ -360,11 +369,13 @@ Type PropertyWithConflictingAttributes // On property/accessor mismatch, ILLink warns on accessor and analyzer warns on property https://github.com/dotnet/linker/issues/2654 [ExpectedWarning("IL2043", "PropertyWithConflictingAttributes", "PropertyWithConflictingAttributes.get", Tool.Trimmer | Tool.NativeAot, "")] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicConstructors)] + [CompilerGenerated] get { return PropertyWithConflictingAttributes_Field; } // On property/accessor mismatch, ILLink warns on accessor and analyzer warns on property https://github.com/dotnet/linker/issues/2654 [ExpectedWarning("IL2043", "PropertyWithConflictingAttributes", "PropertyWithConflictingAttributes.set", Tool.Trimmer | Tool.NativeAot, "")] [param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicConstructors)] + [CompilerGenerated] set { PropertyWithConflictingAttributes_Field = value; } } @@ -393,9 +404,11 @@ Type PropertyWithConflictingNoneAttributes [ExpectedWarning("IL2078", nameof(TestAutomaticPropagationType) + "." + nameof(PropertyWithConflictingNoneAttributes) + ".get", nameof(TestAutomaticPropagationType) + "." + nameof(PropertyWithConflictingNoneAttributes_Field), Tool.Analyzer, "")] [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.None)] + [CompilerGenerated] get { return PropertyWithConflictingNoneAttributes_Field; } [param: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.None)] + [CompilerGenerated] set { PropertyWithConflictingNoneAttributes_Field = value; } } @@ -939,6 +952,273 @@ public static void Test() } } + class AutoPropertyUnrecognizedField + { + // Simulate an auto-property with unrecognizeable accessor behavior + [CompilerGenerated] + private Type Property_BackingField; + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + [ExpectedWarning("IL2042", Tool.NativeAot | Tool.Trimmer, "Requires IL")] // Can't find backing field + public Type Property + { + [CompilerGenerated] + [ExpectedWarning("IL2078", "return value", nameof(Property_BackingField))] + get + { + // tools cannot find backing field when there are two loads in a getter + _ = Property_BackingField; + return Property_BackingField; + } + [CompilerGenerated] + set + { + // tools cannot find backing field when there are two stores in a setter + Property_BackingField = null; + Property_BackingField = value; + } + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + [ExpectedWarning("IL2042", Tool.NativeAot | Tool.Trimmer, "Requires IL")] // Can't find backing field + public Type PropertyAutoSet + { + [CompilerGenerated] + [ExpectedWarning("IL2078", "return value", nameof(Property_BackingField))] + get + { + // tools cannot find backing field when there are two loads in a getter + _ = Property_BackingField; + return Property_BackingField; + } + set; + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + [ExpectedWarning("IL2042", Tool.NativeAot | Tool.Trimmer, "Requires IL")] // Can't find backing field + public Type PropertyAutoGet + { + [ExpectedWarning("IL2078", ["return value", nameof(PropertyAutoGet), "BackingField"], producedBy: Tool.NativeAot | Tool.Trimmer, "Requires IL")] + get; + [CompilerGenerated] + set + { + // tools cannot find backing field when there are two stores in a setter + Property_BackingField = null; + Property_BackingField = value; + } + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + [ExpectedWarning("IL2042", Tool.NativeAot | Tool.Trimmer, "Requires IL")] // Can't find backing field + public Type PropertyManualSet + { + [CompilerGenerated] + [ExpectedWarning("IL2078", "return value", nameof(Property_BackingField))] + get + { + // tools cannot find backing field when there are two loads in a getter + _ = Property_BackingField; + return Property_BackingField; + } + set => Property_BackingField = value; + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + [ExpectedWarning("IL2042", Tool.NativeAot | Tool.Trimmer, "Requires IL")] // Can't find backing field + public Type PropertyManualGet + { + [ExpectedWarning("IL2078", "return value", nameof(Property_BackingField))] + get => Property_BackingField; + [CompilerGenerated] + set + { + // tools cannot find backing field when there are two stores in a setter + Property_BackingField = null; + Property_BackingField = value; + } + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + [ExpectedWarning("IL2042", Tool.NativeAot | Tool.Trimmer, "Requires IL")] // Can't find backing field + public Type PropertyOnlyGet + { + [CompilerGenerated] + [ExpectedWarning("IL2078", "return value", nameof(Property_BackingField))] + get + { + // tools cannot find backing field when there are two loads in a getter + _ = Property_BackingField; + return Property_BackingField; + } + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + [ExpectedWarning("IL2042", nameof(PropertyOnlySet), Tool.NativeAot | Tool.Trimmer, "Requires IL")] // Can't find backing field + public Type PropertyOnlySet + { + [CompilerGenerated] + set + { + // tools cannot find backing field when there are two stores in a setter + Property_BackingField = null; + Property_BackingField = value; + } + } + + public static void Test() + { + var instance = new AutoPropertyUnrecognizedField(); + // No warning since annotation is not propagated + instance.Property_BackingField = GetUnknownType(); + + TestProperty(); + TestPropertyAutoSet(); + TestPropertyAutoGet(); + TestPropertyOnlySet(); + TestPropertyOnlyGet(); + TestPropertyManualGet(); + TestPropertyManualSet(); + } + + [ExpectedWarning("IL2072", nameof(Property), nameof(GetUnknownType))] + [ExpectedWarning("IL2072", "RequiresAll", nameof(Property))] + public static void TestProperty() + { + var instance = new AutoPropertyUnrecognizedField(); + instance.Property = GetUnknownType(); + instance.Property = GetTypeWithPublicConstructors(); + instance.Property.RequiresPublicConstructors(); + instance.Property.RequiresAll(); + } + + [ExpectedWarning("IL2072", nameof(PropertyAutoSet), nameof(GetUnknownType))] + [ExpectedWarning("IL2072", "RequiresAll", nameof(PropertyAutoSet))] + public static void TestPropertyAutoSet() + { + var instance = new AutoPropertyUnrecognizedField(); + instance.PropertyAutoSet = GetUnknownType(); + instance.PropertyAutoSet = GetTypeWithPublicConstructors(); + instance.PropertyAutoSet.RequiresPublicConstructors(); + instance.PropertyAutoSet.RequiresAll(); + } + + [ExpectedWarning("IL2072", nameof(PropertyAutoGet), nameof(GetUnknownType))] + [ExpectedWarning("IL2072", "RequiresAll", nameof(PropertyAutoGet))] + public static void TestPropertyAutoGet() + { + var instance = new AutoPropertyUnrecognizedField(); + instance.PropertyAutoGet = GetUnknownType(); + instance.PropertyAutoGet = GetTypeWithPublicConstructors(); + instance.PropertyAutoGet.RequiresPublicConstructors(); + instance.PropertyAutoGet.RequiresAll(); + } + + [ExpectedWarning("IL2072", nameof(PropertyOnlySet), nameof(GetUnknownType))] + public static void TestPropertyOnlySet() + { + var instance = new AutoPropertyUnrecognizedField(); + instance.PropertyOnlySet = GetUnknownType(); + instance.PropertyOnlySet = GetTypeWithPublicConstructors(); + } + + [ExpectedWarning("IL2072", "RequiresAll", nameof(PropertyOnlyGet))] + public static void TestPropertyOnlyGet() + { + var instance = new AutoPropertyUnrecognizedField(); + instance.PropertyOnlyGet.RequiresPublicConstructors(); + instance.PropertyOnlyGet.RequiresAll(); + } + + [ExpectedWarning("IL2072", nameof(PropertyManualGet), nameof(GetUnknownType))] + [ExpectedWarning("IL2072", "RequiresAll", nameof(PropertyManualGet))] + public static void TestPropertyManualGet() + { + var instance = new AutoPropertyUnrecognizedField(); + instance.PropertyManualGet.RequiresPublicConstructors(); + instance.PropertyManualGet.RequiresAll(); + instance.PropertyManualGet = GetTypeWithPublicConstructors(); + instance.PropertyManualGet = GetUnknownType(); + } + + [ExpectedWarning("IL2072", nameof(PropertyManualSet), nameof(GetUnknownType))] + [ExpectedWarning("IL2072", "RequiresAll", nameof(PropertyManualSet))] + public static void TestPropertyManualSet() + { + var instance = new AutoPropertyUnrecognizedField(); + instance.PropertyManualSet.RequiresPublicConstructors(); + instance.PropertyManualSet.RequiresAll(); + instance.PropertyManualSet = GetTypeWithPublicConstructors(); + instance.PropertyManualSet = GetUnknownType(); + } + } + + // Validate that auto-property accessors are recognized and annotation is propagated to backing field + // Even when there is only one auto-property accessor and the other is manually implemented. + class OneAutoPropAccessor + { + private Type Property_BackingField; + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + public Type AutoGet + { + get; + set { field = value; } + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + public Type AutoSet + { + // Annotation does propagate to the backing field, so no warning + get { return field; } + set; + } + + [ExpectedWarning("IL2072", nameof(AutoSet), nameof(GetUnknownType))] + [ExpectedWarning("IL2072", nameof(AutoGet), nameof(GetUnknownType))] + public static void Test() + { + var instance = new OneAutoPropAccessor(); + instance.AutoGet = GetUnknownType(); + _ = instance.AutoGet; + instance.AutoSet = GetUnknownType(); + _ = instance.AutoSet; + } + } + + // Validates a number of different auto-property syntax variations to ensure that they are all recognized and the annotation is propagated + class AutoPropertySyntaxVariations + { + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + public Type BodySetter + { + get; + [ExpectedWarning("IL2074", nameof(BodySetter), nameof(GetUnknownType))] + set { field = GetUnknownType(); } + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + public Type ExpressionSetter + { + get; + [ExpectedWarning("IL2074", nameof(ExpressionSetter), nameof(GetUnknownType))] + set => field = GetUnknownType(); + } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + public Type BodyGetter { get { return field; } set; } + + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] + public Type ExpressionGetter { get => field; set; } + + public static void Test() + { + var instance = new AutoPropertySyntaxVariations(); + instance.BodySetter = instance.BodyGetter; + instance.ExpressionSetter = instance.ExpressionGetter; + } + } + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] private static Type GetTypeWithPublicParameterlessConstructor() { diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeHierarchyReflectionWarnings.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeHierarchyReflectionWarnings.cs index eb28b22136631a..c3431fda231d69 100644 --- a/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeHierarchyReflectionWarnings.cs +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Reflection/TypeHierarchyReflectionWarnings.cs @@ -941,8 +941,7 @@ public class BaseWithField [KeptAttributeAttribute(typeof(DynamicallyAccessedMembersAttribute))] [KeptBaseType(typeof(BaseWithField))] [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicFields)] - [ExpectedWarning("IL2115", nameof(BaseWithField), nameof(BaseWithField.CompilerGeneratedProperty), - Tool.Trimmer | Tool.NativeAot, "https://github.com/dotnet/linker/issues/2628")] + [ExpectedWarning("IL2115", nameof(BaseWithField), nameof(BaseWithField.CompilerGeneratedProperty))] public class DerivedWithAnnotation : BaseWithField { }