diff --git a/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs b/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs index 4f16f1b0c88d..b7768912d8c8 100644 --- a/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs +++ b/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs @@ -132,7 +132,12 @@ public override TValue VisitSimpleAssignment (ISimpleAssignmentOperation operati // The remaining cases don't have a dataflow value that represents LValues, so we need // to handle the LHS specially. case IPropertyReferenceOperation propertyRef: { - var setMethod = propertyRef.Property.SetMethod; + IPropertySymbol? property = propertyRef.Property; + IMethodSymbol? setMethod; + while ((setMethod = property.SetMethod) == null) { + if ((property = property.OverriddenProperty) == null) + break; + } if (setMethod == null) { // This can happen in a constructor - there it is possible to assign to a property // without a setter. This turns into an assignment to the compiler-generated backing field. @@ -249,8 +254,14 @@ public override TValue VisitPropertyReference (IPropertyReferenceOperation opera // Accessing property for reading is really a call to the getter // The setter case is handled in assignment operation since here we don't have access to the value to pass to the setter TValue instanceValue = Visit (operation.Instance, state); + IPropertySymbol? property = operation.Property; + IMethodSymbol? getMethod; + while ((getMethod = property.GetMethod) == null) { + if ((property = property.OverriddenProperty) == null) + break; + } return HandleMethodCall ( - operation.Property.GetMethod!, + getMethod!, instanceValue, ImmutableArray.Empty, operation); diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs index 188bc8672d7b..2156cadd5695 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/PropertyDataFlow.cs @@ -41,6 +41,8 @@ public static void Main () PropertyWithAttributeMarkingItself.Test (); WriteToGetOnlyProperty.Test (); + + BasePropertyAccess.Test (); } [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors)] @@ -547,6 +549,45 @@ public static void Test () } } + class BasePropertyAccess + { + class Base + { + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] + public virtual Type DerivedGetOnly { get; set; } + + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + public virtual Type DerivedSetOnly { get; set; } + } + + class Derived : Base + { + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)] + public override Type DerivedGetOnly { get => null; } + + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)] + public override Type DerivedSetOnly { set => throw null; } + } + + [ExpectedWarning ("IL2072", nameof (Derived.DerivedGetOnly) + ".set", nameof (GetTypeWithNonPublicConstructors))] + static void TestWriteToDerivedGetOnly () + { + new Derived ().DerivedGetOnly = GetTypeWithNonPublicConstructors (); + } + + [ExpectedWarning ("IL2072", nameof (Derived.DerivedSetOnly) + ".get", nameof (DataFlowTypeExtensions.RequiresAll))] + static void TestReadFromDerivedSetOnly () + { + new Derived ().DerivedSetOnly.RequiresAll (); + } + + public static void Test () + { + TestWriteToDerivedGetOnly (); + TestReadFromDerivedSetOnly (); + } + } + [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] private static Type GetTypeWithPublicParameterlessConstructor () {