11package org .pitest .mutationtest .build .intercept .annotations ;
22
3+ import org .objectweb .asm .Handle ;
4+ import org .objectweb .asm .tree .AbstractInsnNode ;
35import org .objectweb .asm .tree .AnnotationNode ;
4- import org .pitest . bytecode . analysis . AnalysisFunctions ;
6+ import org .objectweb . asm . tree . InvokeDynamicInsnNode ;
57import org .pitest .bytecode .analysis .ClassTree ;
68import org .pitest .bytecode .analysis .MethodTree ;
79import org .pitest .functional .FCollection ;
1315
1416import java .util .Collection ;
1517import java .util .Collections ;
18+ import java .util .HashSet ;
19+ import java .util .LinkedList ;
1620import java .util .List ;
21+ import java .util .Queue ;
22+ import java .util .Set ;
1723import java .util .function .Predicate ;
1824import java .util .stream .Collectors ;
1925
@@ -24,7 +30,6 @@ public class ExcludedAnnotationInterceptor implements MutationInterceptor {
2430 private boolean skipClass ;
2531 private Predicate <MutationDetails > annotatedMethodMatcher ;
2632
27-
2833 ExcludedAnnotationInterceptor (List <String > configuredAnnotations ) {
2934 this .configuredAnnotations = configuredAnnotations ;
3035 }
@@ -39,17 +44,93 @@ public void begin(ClassTree clazz) {
3944 this .skipClass = clazz .annotations ().stream ()
4045 .anyMatch (avoidedAnnotation ());
4146 if (!this .skipClass ) {
42- final List <Predicate <MutationDetails >> methods = clazz .methods ().stream ()
47+ // 1. Collect methods with avoided annotations or that override such methods
48+ final List <MethodTree > avoidedMethods = clazz .methods ().stream ()
4349 .filter (hasAvoidedAnnotation ())
44- .map (AnalysisFunctions .matchMutationsInMethod ())
4550 .collect (Collectors .toList ());
46- this .annotatedMethodMatcher = Prelude .or (methods );
51+
52+ // Collect method names along with descriptors to handle overloaded methods
53+ final Set <MethodSignature > avoidedMethodSignatures = avoidedMethods .stream ()
54+ .map (method -> new MethodSignature (method .rawNode ().name , method .rawNode ().desc ))
55+ .collect (Collectors .toSet ());
56+
57+ // Keep track of processed methods to avoid infinite loops
58+ Set <MethodSignature > processedMethods = new HashSet <>(avoidedMethodSignatures );
59+
60+ // 2. For each avoided method, collect lambda methods recursively
61+ for (MethodTree avoidedMethod : avoidedMethods ) {
62+ collectLambdaMethods (avoidedMethod , clazz , avoidedMethodSignatures , processedMethods );
63+ }
64+
65+ // 3. Create a predicate to match mutations in methods to avoid
66+ this .annotatedMethodMatcher = mutation -> {
67+ MethodSignature mutationSignature = new MethodSignature (
68+ mutation .getMethod (), mutation .getId ().getLocation ().getMethodDesc ());
69+ return avoidedMethodSignatures .contains (mutationSignature );
70+ };
4771 }
4872 }
4973
74+ /**
75+ * Recursively collects lambda methods defined within the given method.
76+ *
77+ * @param method The method to inspect for lambdas.
78+ * @param clazz The class containing the methods.
79+ * @param avoidedMethodSignatures The set of method signatures to avoid.
80+ * @param processedMethods The set of already processed methods to prevent infinite loops.
81+ */
82+ private void collectLambdaMethods (MethodTree method , ClassTree clazz ,
83+ Set <MethodSignature > avoidedMethodSignatures ,
84+ Set <MethodSignature > processedMethods ) {
85+ Queue <MethodTree > methodsToProcess = new LinkedList <>();
86+ methodsToProcess .add (method );
87+
88+ while (!methodsToProcess .isEmpty ()) {
89+ MethodTree currentMethod = methodsToProcess .poll ();
90+
91+ for (AbstractInsnNode insn : currentMethod .rawNode ().instructions ) {
92+ if (insn instanceof InvokeDynamicInsnNode ) {
93+ InvokeDynamicInsnNode indy = (InvokeDynamicInsnNode ) insn ;
94+
95+ for (Object bsmArg : indy .bsmArgs ) {
96+ if (bsmArg instanceof Handle ) {
97+ Handle handle = (Handle ) bsmArg ;
98+ // Check if the method is in the same class and is a lambda method
99+ if (handle .getOwner ().equals (clazz .rawNode ().name ) && handle .getName ().startsWith ("lambda$" )) {
100+ MethodSignature lambdaMethodSignature = new MethodSignature (handle .getName (), handle .getDesc ());
101+ if (!avoidedMethodSignatures .contains (lambdaMethodSignature )
102+ && !processedMethods .contains (lambdaMethodSignature )) {
103+ avoidedMethodSignatures .add (lambdaMethodSignature );
104+ processedMethods .add (lambdaMethodSignature );
105+ // Find the MethodTree for this lambda method
106+ MethodTree lambdaMethod = findMethodTree (clazz , handle .getName (), handle .getDesc ());
107+ if (lambdaMethod != null ) {
108+ methodsToProcess .add (lambdaMethod );
109+ }
110+ }
111+ }
112+ }
113+ }
114+ }
115+ }
116+ }
117+ }
118+
119+ private MethodTree findMethodTree (ClassTree clazz , String methodName , String methodDesc ) {
120+ return clazz .methods ().stream ()
121+ .filter (m -> m .rawNode ().name .equals (methodName ) && m .rawNode ().desc .equals (methodDesc ))
122+ .findFirst ()
123+ .orElse (null );
124+ }
125+
126+ /**
127+ * Creates a predicate that checks if a method has an avoided annotation.
128+ *
129+ * @return A predicate that returns true if the method should be avoided.
130+ */
50131 private Predicate <MethodTree > hasAvoidedAnnotation () {
51- return a -> a . annotations (). stream ()
52- .anyMatch (avoidedAnnotation ());
132+ return methodTree ->
133+ methodTree . annotations (). stream () .anyMatch (avoidedAnnotation ());
53134 }
54135
55136 private Predicate <AnnotationNode > avoidedAnnotation () {
@@ -81,4 +162,34 @@ boolean shouldAvoid(String desc) {
81162 return false ;
82163 }
83164
165+ /**
166+ * Represents a method signature with its name and descriptor.
167+ * Used to uniquely identify methods, especially overloaded ones.
168+ */
169+ private static class MethodSignature {
170+ private final String name ;
171+ private final String desc ;
172+
173+ MethodSignature (String name , String desc ) {
174+ this .name = name ;
175+ this .desc = desc ;
176+ }
177+
178+ @ Override
179+ public boolean equals (Object obj ) {
180+ if (this == obj ) {
181+ return true ;
182+ }
183+ if (obj == null || getClass () != obj .getClass ()) {
184+ return false ;
185+ }
186+ MethodSignature that = (MethodSignature ) obj ;
187+ return name .equals (that .name ) && desc .equals (that .desc );
188+ }
189+
190+ @ Override
191+ public int hashCode () {
192+ return name .hashCode () * 31 + desc .hashCode ();
193+ }
194+ }
84195}
0 commit comments