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
{
}