22// The .NET Foundation licenses this file to you under the MIT license.
33
44using System ;
5+ using System . Diagnostics ;
56using System . Collections . Generic ;
67using System . Diagnostics . CodeAnalysis ;
78using System . Reflection . Metadata ;
@@ -523,7 +524,7 @@ protected override TypeAnnotations CreateValueFromKey(TypeDesc key)
523524 // Look for the compiler generated backing field. If it doesn't work out simply move on. In such case we would still
524525 // propagate the annotation to the setter/getter and later on when analyzing the setter/getter we will warn
525526 // that the field (which ever it is) must be annotated as well.
526- ScanMethodBodyForFieldAccess ( methodBody , write : true , out backingFieldFromSetter ) ;
527+ backingFieldFromSetter = GetAutoPropertyCompilerGeneratedField ( methodBody , isWriteAccessor : true ) ;
527528 }
528529
529530 MethodAnnotations ? setterAnnotation = null ;
@@ -560,16 +561,14 @@ protected override TypeAnnotations CreateValueFromKey(TypeDesc key)
560561 MethodDesc getMethod = property . GetMethod ;
561562 if ( getMethod != null )
562563 {
563-
564- // Abstract property backing field propagation doesn't make sense, and any derived property will be validated
565- // to have the exact same annotations on getter/setter, and thus if it has a detectable backing field that will be validated as well.
564+ // Look only at compiler-generated accessors
566565 MethodIL methodBody = _ilProvider . GetMethodIL ( getMethod ) ;
567566 if ( methodBody != null )
568567 {
569568 // Look for the compiler generated backing field. If it doesn't work out simply move on. In such case we would still
570569 // propagate the annotation to the setter/getter and later on when analyzing the setter/getter we will warn
571570 // that the field (which ever it is) must be annotated as well.
572- ScanMethodBodyForFieldAccess ( methodBody , write : false , out backingFieldFromGetter ) ;
571+ backingFieldFromGetter = GetAutoPropertyCompilerGeneratedField ( methodBody , isWriteAccessor : false ) ;
573572 }
574573
575574 MethodAnnotations ? getterAnnotation = null ;
@@ -593,27 +592,41 @@ protected override TypeAnnotations CreateValueFromKey(TypeDesc key)
593592 }
594593 }
595594
596- FieldDesc ? backingField ;
597- if ( backingFieldFromGetter != null && backingFieldFromSetter != null &&
598- backingFieldFromGetter != backingFieldFromSetter )
599- {
600- _logger . LogWarning ( property , DiagnosticId . DynamicallyAccessedMembersCouldNotFindBackingField , property . GetDisplayName ( ) ) ;
601- backingField = null ;
602- }
603- else
604- {
605- backingField = backingFieldFromGetter ?? backingFieldFromSetter ;
606- }
607-
608- if ( backingField != null )
595+ if ( IsAutoProperty ( property ) )
609596 {
610- if ( annotatedFields . Any ( a => a . Field == backingField ) )
597+ FieldDesc ? backingField = null ;
598+ if ( ( property . SetMethod is not null
599+ && property . SetMethod . HasCustomAttribute ( "System.Runtime.CompilerServices" , "CompilerGeneratedAttribute" )
600+ && backingFieldFromSetter is null )
601+ || ( property . GetMethod is not null
602+ && property . GetMethod . HasCustomAttribute ( "System.Runtime.CompilerServices" , "CompilerGeneratedAttribute" )
603+ && backingFieldFromGetter is null ) )
611604 {
612- _logger . LogWarning ( backingField , DiagnosticId . DynamicallyAccessedMembersOnPropertyConflictsWithBackingField , property . GetDisplayName ( ) , backingField . GetDisplayName ( ) ) ;
605+ // We failed to find the backing field of an auto-property accessor
606+ _logger . LogWarning ( property , DiagnosticId . DynamicallyAccessedMembersCouldNotFindBackingField , property . GetDisplayName ( ) ) ;
607+ }
608+ else if ( backingFieldFromGetter is not null && backingFieldFromSetter is not null
609+ && backingFieldFromSetter != backingFieldFromGetter )
610+ {
611+ // We found two different backing fields for the getter and the setter
612+ _logger . LogWarning ( property , DiagnosticId . DynamicallyAccessedMembersCouldNotFindBackingField , property . GetDisplayName ( ) ) ;
613613 }
614614 else
615615 {
616- annotatedFields . Add ( new FieldAnnotation ( backingField , annotation ) ) ;
616+ // We either have a single auto-property accessor or both accessors point to the same backing field
617+ backingField = backingFieldFromSetter ?? backingFieldFromGetter ;
618+ }
619+
620+ if ( backingField != null )
621+ {
622+ if ( annotatedFields . Any ( a => a . Field == backingField ) )
623+ {
624+ _logger . LogWarning ( backingField , DiagnosticId . DynamicallyAccessedMembersOnPropertyConflictsWithBackingField , property . GetDisplayName ( ) , backingField . GetDisplayName ( ) ) ;
625+ }
626+ else
627+ {
628+ annotatedFields . Add ( new FieldAnnotation ( backingField , annotation ) ) ;
629+ }
617630 }
618631 }
619632 }
@@ -651,59 +664,69 @@ protected override TypeAnnotations CreateValueFromKey(TypeDesc key)
651664 return attrs ;
652665 }
653666
654- private static bool ScanMethodBodyForFieldAccess ( MethodIL body , bool write , out FieldDesc ? found )
667+ /// <summary>
668+ /// Returns true if the property has a single accessor which is compiler generated,
669+ /// indicating that it is an auto-property.
670+ /// </summary>
671+ /// <remarks>
672+ /// Ideally this would be tightened to only return true if both accessors are auto-property accessors,
673+ /// but it allows for either for back compatibility with existing behavior.
674+ /// </remarks>
675+ private static bool IsAutoProperty ( PropertyPseudoDesc property )
676+ {
677+ return property . SetMethod ? . HasCustomAttribute ( "System.Runtime.CompilerServices" , "CompilerGeneratedAttribute" ) == true
678+ || property . GetMethod ? . HasCustomAttribute ( "System.Runtime.CompilerServices" , "CompilerGeneratedAttribute" ) == true ;
679+ }
680+
681+ private static FieldDesc ? GetAutoPropertyCompilerGeneratedField ( MethodIL body , bool isWriteAccessor )
655682 {
656683 // Tries to find the backing field for a property getter/setter.
657684 // Returns true if this is a method body that we can unambiguously analyze.
658685 // The found field could still be null if there's no backing store.
659- found = null ;
660686
687+ // Only analyze compiler-generated accessors
688+ if ( ! body . OwningMethod . HasCustomAttribute ( "System.Runtime.CompilerServices" , "CompilerGeneratedAttribute" ) )
689+ {
690+ return null ;
691+ }
692+
693+ FieldDesc ? found = null ;
661694 ILReader ilReader = new ILReader ( body . GetILBytes ( ) ) ;
662695
663696 while ( ilReader . HasNext )
664697 {
665698 ILOpcode opcode = ilReader . ReadILOpcode ( ) ;
666699 switch ( opcode )
667700 {
668- case ILOpcode . ldsfld when ! write :
669- case ILOpcode . ldfld when ! write :
670- case ILOpcode . stsfld when write :
671- case ILOpcode . stfld when write :
701+ case ILOpcode . ldsfld when ! isWriteAccessor :
702+ case ILOpcode . ldfld when ! isWriteAccessor :
703+ case ILOpcode . stsfld when isWriteAccessor :
704+ case ILOpcode . stfld when isWriteAccessor :
705+ {
706+ // Multiple field accesses - ambiguous, fail
707+ if ( found != null )
672708 {
673- // This writes/reads multiple fields - can't guess which one is the backing store.
674- // Return failure.
675- if ( found != null )
676- {
677- found = null ;
678- return false ;
679- }
680- found = ( FieldDesc ) body . GetObject ( ilReader . ReadILToken ( ) ) ;
709+ return null ;
681710 }
711+ found = ( FieldDesc ) body . GetObject ( ilReader . ReadILToken ( ) ) ;
682712 break ;
713+ }
683714 default :
684715 ilReader . Skip ( opcode ) ;
685716 break ;
686717 }
687718 }
688719
689- if ( found == null )
690- {
691- // Doesn't access any fields. Could be e.g. "Type Foo => typeof(Bar);"
692- // Return success.
693- return true ;
694- }
695-
696- if ( found . OwningType != body . OwningMethod . OwningType ||
720+ if ( found is null ||
721+ found . OwningType != body . OwningMethod . OwningType ||
697722 found . IsStatic != body . OwningMethod . Signature . IsStatic ||
698723 ! found . HasCustomAttribute ( "System.Runtime.CompilerServices" , "CompilerGeneratedAttribute" ) )
699724 {
700- // A couple heuristics to make sure we got the right field.
701- // Return failure.
702- found = null ;
703- return false ;
725+ // Heuristics failed - not a backing field
726+ return null ;
704727 }
705728
706- return true ;
729+ return found ;
707730 }
708731 }
709732
0 commit comments