@@ -47,9 +47,8 @@ internal class Instrumenter
4747 private MethodReference _customTrackerRecordHitMethod ;
4848 private List < string > _excludedSourceFiles ;
4949 private List < string > _branchesInCompiledGeneratedClass ;
50- private List < ( MethodDefinition , int ) > _excludedMethods ;
50+ private List < ( SequencePoint firstSequencePoint , SequencePoint lastSequencePoint ) > _excludedMethodSections ;
5151 private List < string > _excludedLambdaMethods ;
52- private List < string > _excludedCompilerGeneratedTypes ;
5352 private ReachabilityHelper _reachabilityHelper ;
5453
5554 public bool SkipModule { get ; set ; }
@@ -242,14 +241,7 @@ private void InstrumentModule()
242241 _instrumentationHelper . IsTypeIncluded ( _module , type . FullName , _parameters . IncludeFilters )
243242 )
244243 {
245- if ( IsSynthesizedMemberToBeExcluded ( type ) )
246- {
247- ( _excludedCompilerGeneratedTypes ??= new List < string > ( ) ) . Add ( type . FullName ) ;
248- }
249- else
250- {
251- InstrumentType ( type ) ;
252- }
244+ InstrumentType ( type ) ;
253245 }
254246 }
255247
@@ -467,9 +459,6 @@ private void InstrumentType(TypeDefinition type)
467459 {
468460 IEnumerable < MethodDefinition > methods = type . GetMethods ( ) ;
469461
470- // We keep ordinal index because it's the way used by compiler for generated types/methods to
471- // avoid ambiguity
472- int ordinal = - 1 ;
473462 foreach ( MethodDefinition method in methods )
474463 {
475464 MethodDefinition actualMethod = method ;
@@ -490,18 +479,11 @@ private void InstrumentType(TypeDefinition type)
490479 customAttributes = customAttributes . Union ( prop . CustomAttributes ) ;
491480 }
492481
493- ordinal ++ ;
494-
495482 if ( IsMethodOfCompilerGeneratedClassOfAsyncStateMachineToBeExcluded ( method ) )
496483 {
497484 continue ;
498485 }
499486
500- if ( IsSynthesizedMemberToBeExcluded ( method ) )
501- {
502- continue ;
503- }
504-
505487 if ( _excludedLambdaMethods != null && _excludedLambdaMethods . Contains ( method . FullName ) )
506488 {
507489 continue ;
@@ -514,7 +496,9 @@ private void InstrumentType(TypeDefinition type)
514496 else
515497 {
516498 ( _excludedLambdaMethods ??= new List < string > ( ) ) . AddRange ( CollectLambdaMethodsInsideLocalFunction ( method ) ) ;
517- ( _excludedMethods ??= new List < ( MethodDefinition , int ) > ( ) ) . Add ( ( method , ordinal ) ) ;
499+ _excludedMethodSections ??= new List < ( SequencePoint firstSequencePoint , SequencePoint lastSequencePoint ) > ( ) ;
500+ AnalyzeCompileGeneratedTypesForExcludedMethod ( method ) ;
501+ CacheExcludedMethodSection ( method ) ;
518502 }
519503 }
520504
@@ -604,7 +588,8 @@ private void InstrumentIL(MethodDefinition method)
604588
605589 if ( sequencePoint != null && ! sequencePoint . IsHidden )
606590 {
607- if ( _cecilSymbolHelper . SkipInlineAssignedAutoProperty ( _parameters . SkipAutoProps , method , currentInstruction ) )
591+ if ( _cecilSymbolHelper . SkipInlineAssignedAutoProperty ( _parameters . SkipAutoProps , method ,
592+ currentInstruction ) || IsInsideExcludedMethodSection ( sequencePoint ) )
608593 {
609594 index ++ ;
610595 continue ;
@@ -797,59 +782,38 @@ private static MethodBody GetMethodBody(MethodDefinition method)
797782 }
798783 }
799784
800- // Check if the member (type or method) is generated by the compiler from a method excluded from code coverage
801- private bool IsSynthesizedMemberToBeExcluded ( IMemberDefinition definition )
785+ private bool IsInsideExcludedMethodSection ( SequencePoint sequencePoint )
802786 {
803- if ( _excludedMethods is null )
804- {
805- return false ;
806- }
807-
808- TypeDefinition declaringType = definition . DeclaringType ;
787+ if ( _excludedMethodSections is null ) return false ;
809788
810- // We check all parent type of current one bottom-up
811- while ( declaringType != null )
789+ bool IsInsideExcludedSection ( SequencePoint firstSequencePoint , SequencePoint lastSequencePoint )
812790 {
791+ bool isInsideSameSourceFile = sequencePoint . Document . Url . Equals ( firstSequencePoint . Document . Url ) ;
792+ bool isInsideExcludedMethod = sequencePoint . StartLine >= Math . Min ( firstSequencePoint . StartLine , firstSequencePoint . EndLine ) &&
793+ sequencePoint . StartLine <= Math . Max ( lastSequencePoint . StartLine , lastSequencePoint . EndLine ) ;
794+ return isInsideExcludedMethod && isInsideSameSourceFile ;
795+ }
813796
814- // If parent type is excluded return
815- if ( _excludedCompilerGeneratedTypes != null &&
816- _excludedCompilerGeneratedTypes . Any ( t => t == declaringType . FullName ) )
817- {
818- return true ;
819- }
797+ return _excludedMethodSections
798+ . Where ( x => x is { firstSequencePoint : not null , lastSequencePoint : not null } )
799+ . Any ( x => IsInsideExcludedSection ( x . firstSequencePoint , x . lastSequencePoint ) ) ;
800+ }
820801
821- // Check methods members and compiler generated types
822- foreach ( ( MethodDefinition , int ) excludedMethods in _excludedMethods )
823- {
824- // Exclude this member if declaring type is the same of the excluded method and
825- // the name is synthesized from the name of the excluded method.
826- //
827- if ( declaringType . FullName == excludedMethods . Item1 . DeclaringType . FullName &&
828- IsSynthesizedNameOf ( definition . Name , excludedMethods . Item1 . Name , excludedMethods . Item2 ) )
829- {
830- return true ;
831- }
832- }
833- declaringType = declaringType . DeclaringType ;
834- }
802+ private void AnalyzeCompileGeneratedTypesForExcludedMethod ( MethodDefinition method )
803+ {
804+ IEnumerable < TypeDefinition > referencedTypes = method . CustomAttributes . Where ( x => x . HasConstructorArguments )
805+ . SelectMany ( x => x . ConstructorArguments . Select ( y => y . Value as TypeDefinition ) ) ;
835806
836- return false ;
807+ referencedTypes . ToList ( ) . ForEach ( x =>
808+ x ? . Methods . Where ( y => y . FullName . Contains ( "MoveNext" ) ) . ToList ( ) . ForEach ( CacheExcludedMethodSection )
809+ ) ;
837810 }
838811
839- // Check if the name is synthesized by the compiler
840- // Refer to https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs
841- // to see how the compiler generate names for lambda, local function, yield or async/await expressions
842- internal bool IsSynthesizedNameOf ( string name , string methodName , int methodOrdinal )
812+ private void CacheExcludedMethodSection ( MethodDefinition method )
843813 {
844- return
845- // Lambda method
846- name . IndexOf ( $ "<{ methodName } >b__{ methodOrdinal } ") != - 1 ||
847- // Lambda display class
848- name . IndexOf ( $ "<>c__DisplayClass{ methodOrdinal } _") != - 1 ||
849- // State machine
850- name . IndexOf ( $ "<{ methodName } >d__{ methodOrdinal } ") != - 1 ||
851- // Local function
852- ( name . IndexOf ( $ "<{ methodName } >g__") != - 1 && name . IndexOf ( $ "|{ methodOrdinal } _") != - 1 ) ;
814+ _excludedMethodSections . Add ( (
815+ method . DebugInformation . SequencePoints . FirstOrDefault ( x => ! x . IsHidden ) ,
816+ method . DebugInformation . SequencePoints . LastOrDefault ( x => ! x . IsHidden ) ) ) ;
853817 }
854818
855819 private static IEnumerable < string > CollectLambdaMethodsInsideLocalFunction ( MethodDefinition methodDefinition )
@@ -858,7 +822,8 @@ private static IEnumerable<string> CollectLambdaMethodsInsideLocalFunction(Metho
858822
859823 foreach ( Instruction instruction in methodDefinition . Body . Instructions . ToList ( ) )
860824 {
861- if ( instruction . OpCode == OpCodes . Ldftn && instruction . Operand is MethodReference mr && mr . Name . Contains ( ">b__" ) )
825+ if ( instruction . OpCode == OpCodes . Ldftn && instruction . Operand is MethodReference mr &&
826+ mr . Name . Contains ( ">b__" ) )
862827 {
863828 yield return mr . FullName ;
864829 }
0 commit comments