diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/RegionInterceptor.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/RegionInterceptor.java index 4dec23373..4abd485d3 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/RegionInterceptor.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/RegionInterceptor.java @@ -11,6 +11,7 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -44,9 +45,13 @@ public Collection intercept( private Predicate buildPredicate() { return a -> { final int instruction = a.getInstructionIndex(); - final MethodTree method = this.currentClass.method(a.getId().getLocation()).get(); + final Optional method = this.currentClass.method(a.getId().getLocation()); - List regions = cache.computeIfAbsent(method, this::computeRegionIndex); + if (!method.isPresent()) { + return false; + } + + List regions = cache.computeIfAbsent(method.get(), this::computeRegionIndex); return regions.stream() .anyMatch(r -> r.start() <= instruction && r.end() >= instruction); }; diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java index 4ac822315..26bddc9b9 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/ForEachLoopFilter.java @@ -28,6 +28,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -189,7 +190,11 @@ public Collection intercept( private Predicate mutatesIteratorLoopPlumbing() { return a -> { final int instruction = a.getInstructionIndex(); - final MethodTree method = currentClass.method(a.getId().getLocation()).get(); + final Optional maybeMethod = currentClass.method(a.getId().getLocation()); + if (!maybeMethod.isPresent()) { + return false; + } + MethodTree method = maybeMethod.get(); final AbstractInsnNode mutatedInstruction = method.instruction(instruction); diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/InlinedFinallyBlockFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/InlinedFinallyBlockFilter.java index 5f76271d0..9851ba738 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/InlinedFinallyBlockFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/javafeatures/InlinedFinallyBlockFilter.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.logging.Logger; @@ -170,7 +171,11 @@ private void checkForInlinedCode(Collection mutantsToReturn, private boolean isInFinallyBlock(MutationDetails m) { - MethodTree method = currentClass.method(m.getId().getLocation()).get(); + Optional maybeMethod = currentClass.method(m.getId().getLocation()); + if (!maybeMethod.isPresent()) { + return false; + } + MethodTree method = maybeMethod.get(); List handlers = method.rawNode().tryCatchBlocks.stream() .filter(t -> t.type == null) .map(t -> t.handler) diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java index 7a1fcd1b2..c9c38c837 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/AvoidForLoopCounterFilter.java @@ -159,7 +159,12 @@ public Collection intercept( private Predicate mutatesAForLoopCounter() { return a -> { final int instruction = a.getInstructionIndex(); - final MethodTree method = AvoidForLoopCounterFilter.this.currentClass.method(a.getId().getLocation()).get(); + Optional maybeMethod = AvoidForLoopCounterFilter.this.currentClass.method(a.getId().getLocation()); + if (!maybeMethod.isPresent()) { + return false; + } + MethodTree method = maybeMethod.get(); + final AbstractInsnNode mutatedInstruction = method.instruction(instruction); // performance hack diff --git a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteLoopFilter.java b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteLoopFilter.java index 7c83f29e6..254531013 100644 --- a/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteLoopFilter.java +++ b/pitest-entry/src/main/java/org/pitest/mutationtest/build/intercept/timeout/InfiniteLoopFilter.java @@ -55,8 +55,11 @@ public Collection intercept( private Collection findTimeoutMutants(Location location, Collection mutations, Mutater m) { - final MethodTree method = this.currentClass.method(location) - .get(); + final Optional maybeMethod = this.currentClass.method(location); + if (!maybeMethod.isPresent()) { + return Collections.emptyList(); + } + MethodTree method = maybeMethod.get(); // give up if our matcher thinks loop is already infinite if (infiniteLoopMatcher().matches(method.instructions())) { @@ -64,7 +67,7 @@ private Collection findTimeoutMutants(Location location, } final List timeouts = new ArrayList<>(); - for ( final MutationDetails each : mutations ) { + for (final MutationDetails each : mutations) { // avoid cost of static analysis by first checking mutant is on // on instruction that could affect looping if (couldCauseInfiniteLoop(method, each) && isInfiniteLoop(each,m) ) { diff --git a/pitest/src/main/java/org/pitest/bytecode/ASMVersion.java b/pitest/src/main/java/org/pitest/bytecode/ASMVersion.java index 971e18dae..f512eb740 100644 --- a/pitest/src/main/java/org/pitest/bytecode/ASMVersion.java +++ b/pitest/src/main/java/org/pitest/bytecode/ASMVersion.java @@ -4,4 +4,13 @@ public class ASMVersion { public static final int ASM_VERSION = Opcodes.ASM9; + + /** + * Provide the asm version via a method call so third party plugins built against pitest + * will receive the current asm version instead of one inlined at build time + * @return asm version + */ + public static int asmVersion() { + return ASM_VERSION; + } } diff --git a/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/AnnotationInfo.java b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/AnnotationInfo.java new file mode 100644 index 000000000..2576dbe2d --- /dev/null +++ b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/AnnotationInfo.java @@ -0,0 +1,19 @@ +package org.pitest.mutationtest.engine.gregor; + +public class AnnotationInfo { + private final String descriptor; + private final boolean visible; + + public AnnotationInfo(String descriptor, boolean visible) { + this.descriptor = descriptor; + this.visible = visible; + } + + public String descriptor() { + return descriptor; + } + + public boolean isVisible() { + return visible; + } +} diff --git a/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/BasicContext.java b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/BasicContext.java new file mode 100644 index 000000000..8df8e9451 --- /dev/null +++ b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/BasicContext.java @@ -0,0 +1,12 @@ +package org.pitest.mutationtest.engine.gregor; + +import org.pitest.mutationtest.engine.MutationIdentifier; + +public interface BasicContext { + + ClassInfo getClassInfo(); + + void registerMutation(MutationIdentifier id, String description); + + boolean shouldMutate(MutationIdentifier newId); +} diff --git a/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MethodInfo.java b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MethodInfo.java index e9723f850..0de035f42 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MethodInfo.java +++ b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MethodInfo.java @@ -48,6 +48,10 @@ public String getMethodDescriptor() { return this.methodDescriptor; } + public ClassInfo getOwningClass() { + return this.owningClass; + } + public int getAccess() { return this.access; } diff --git a/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MethodMutatorFactory.java b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MethodMutatorFactory.java index e89d1eefc..d374b1753 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MethodMutatorFactory.java +++ b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MethodMutatorFactory.java @@ -14,6 +14,7 @@ */ package org.pitest.mutationtest.engine.gregor; +import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.pitest.mutationtest.engine.MutationIdentifier; @@ -25,7 +26,7 @@ * mutation points (locations in byte code where mutations can be applied) and * applying those mutations to the byte code. * - * Name of this class is misleading as it is capable of mutating both methods + * Name of this class is misleading as it is capable of mutating methods, annotations * and field, but it is retained for historic reasons. * *

@@ -44,7 +45,12 @@ default MethodVisitor create(MutationContext context, return null; } - default FieldVisitor createForField(ClassContext context, FieldInfo fieldInfo, FieldVisitor fieldVisitor) { + + default AnnotationVisitor createForAnnotation(NoMethodContext context, AnnotationInfo annotationInfo, AnnotationVisitor next) { + return null; + } + + default FieldVisitor createForField(NoMethodContext context, FieldInfo fieldInfo, FieldVisitor fieldVisitor) { return null; } diff --git a/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MutatingClassVisitor.java b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MutatingClassVisitor.java index 79ccd0503..2dc2a2661 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MutatingClassVisitor.java +++ b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MutatingClassVisitor.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.function.Predicate; +import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; @@ -29,7 +30,9 @@ class MutatingClassVisitor extends ClassVisitor { private final Predicate filter; private final ClassContext context; - private final List methodMutators; + private final List mutators; + + private final NoMethodContext nonMethodContext; MutatingClassVisitor(final ClassVisitor delegateClassVisitor, final ClassContext context, final Predicate filter, @@ -37,7 +40,8 @@ class MutatingClassVisitor extends ClassVisitor { super(ASMVersion.ASM_VERSION, delegateClassVisitor); this.context = context; this.filter = filter; - this.methodMutators = mutators; + this.mutators = mutators; + this.nonMethodContext = new NoMethodContext(context); } @Override @@ -54,12 +58,25 @@ public void visitSource(final String source, final String debug) { this.context.registerSourceFile(source); } + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + AnnotationVisitor next = super.visitAnnotation(descriptor, visible); + AnnotationInfo annotationInfo = new AnnotationInfo(descriptor, visible); + for (final MethodMutatorFactory each : this.mutators) { + AnnotationVisitor fv = each.createForAnnotation(this.nonMethodContext, annotationInfo, next); + if (fv != null) { + next = fv; + } + } + return next; + } + @Override public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { FieldVisitor next = super.visitField(access, name, descriptor, signature, value); FieldInfo fieldInfo = new FieldInfo(access, name, descriptor, signature, value); - for (final MethodMutatorFactory each : this.methodMutators) { - FieldVisitor fv = each.createForField(this.context, fieldInfo, next); + for (final MethodMutatorFactory each : this.mutators) { + FieldVisitor fv = each.createForField(this.nonMethodContext, fieldInfo, next); if (fv != null) { next = fv; } @@ -114,7 +131,7 @@ private MethodVisitor visitMethodForMutation( final MethodVisitor methodVisitor) { MethodVisitor next = methodVisitor; - for (final MethodMutatorFactory each : this.methodMutators) { + for (final MethodMutatorFactory each : this.mutators) { MethodVisitor mv = each.create(methodContext, methodInfo, next); if (mv != null) { next = mv; diff --git a/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MutationContext.java b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MutationContext.java index 84d31503d..f1ae315c0 100644 --- a/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MutationContext.java +++ b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/MutationContext.java @@ -3,17 +3,10 @@ import org.pitest.mutationtest.engine.MutationIdentifier; import org.pitest.mutationtest.engine.gregor.blocks.BlockCounter; -public interface MutationContext extends BlockCounter { +public interface MutationContext extends BasicContext, BlockCounter { void registerCurrentLine(int line); - ClassInfo getClassInfo(); - MutationIdentifier registerMutation(MethodMutatorFactory factory, String description); - - void registerMutation(MutationIdentifier id, String description); - - boolean shouldMutate(MutationIdentifier newId); - } \ No newline at end of file diff --git a/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/NoMethodContext.java b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/NoMethodContext.java new file mode 100644 index 000000000..1c3de114c --- /dev/null +++ b/pitest/src/main/java/org/pitest/mutationtest/engine/gregor/NoMethodContext.java @@ -0,0 +1,32 @@ +package org.pitest.mutationtest.engine.gregor; + +import org.pitest.mutationtest.engine.MutationDetails; +import org.pitest.mutationtest.engine.MutationIdentifier; + +public class NoMethodContext implements BasicContext { + + private final ClassContext context; + + public NoMethodContext(ClassContext context) { + this.context = context; + } + + @Override + public ClassInfo getClassInfo() { + return context.getClassInfo(); + } + + @Override + public void registerMutation(MutationIdentifier id, String description) { + registerMutation(new MutationDetails(id, context.getFileName(), description,0, 1)); + } + + @Override + public boolean shouldMutate(MutationIdentifier id) { + return this.context.shouldMutate(id); + } + + private void registerMutation(MutationDetails details) { + this.context.addMutation(details); + } +}