Skip to content

Commit

Permalink
Merge pull request #1118 from hcoles/feature/check_incompatible_plugins
Browse files Browse the repository at this point in the history
Reduce use of legacy ClassInfo and detect missing plugins
  • Loading branch information
hcoles committed Nov 28, 2022
2 parents 70982db + 8b387e5 commit 190f2e8
Show file tree
Hide file tree
Showing 74 changed files with 1,184 additions and 887 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ Read all about it at http://pitest.org

* #1067 Improved Quarkus and Roboelectric support
* #1116 Option to exclude lines 0 and 1 of all classes
* #1118 Detect missing plugins

As a result of #1067 it is important that mutations are only created for a single class for each JVM. The `MutationGrouper` extension point has therefore been removed as this allowed this constraint to be violated. Any third party plugins using this extension are no longer supported.

Releases also includes multiple changes to internal data structures. Any third party plugins relying on these structures may need to be updated.

1116 provides a course way to exclude autogenerated code not picked up by pitest's other filters. It is disabled by default as it may exclude code that is valid for mutation in some situations.

### 1.9.11
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.RecordComponentNode;
import org.objectweb.asm.util.Textifier;
Expand All @@ -35,7 +39,6 @@ public static ClassTree fromBytes(byte[] bytes) {
return new ClassTree(classNode);
}


public List<MethodTree> methods() {
if (this.lazyMethods != null) {
return this.lazyMethods;
Expand All @@ -49,14 +52,14 @@ public Optional<MethodTree> method(Location loc) {
}

public List<AnnotationNode> annotations() {
final List<AnnotationNode> annotaions = new ArrayList<>();
final List<AnnotationNode> annotations = new ArrayList<>();
if (this.rawNode.invisibleAnnotations != null) {
annotaions.addAll(this.rawNode.invisibleAnnotations);
annotations.addAll(this.rawNode.invisibleAnnotations);
}
if (this.rawNode.visibleAnnotations != null) {
annotaions.addAll(this.rawNode.visibleAnnotations);
annotations.addAll(this.rawNode.visibleAnnotations);
}
return annotaions;
return annotations;
}

public List<RecordComponentNode> recordComponents() {
Expand All @@ -79,6 +82,37 @@ public ClassNode rawNode() {
return this.rawNode;
}

public Set<Integer> codeLineNumbers() {
return methods().stream()
.flatMap(m -> m.instructions().stream()
.filter(n -> n instanceof LineNumberNode)
.map(n -> ((LineNumberNode) n).line))
.collect(Collectors.toSet());
}

public int numberOfCodeLines() {
return (int) methods().stream()
.flatMap(m -> m.instructions().stream()
.filter(n -> n instanceof LineNumberNode))
.count();
}

public boolean isAbstract() {
return (this.rawNode.access & Opcodes.ACC_ABSTRACT) != 0;
}

public boolean isInterface() {
return (this.rawNode.access & Opcodes.ACC_INTERFACE) != 0;
}

public boolean isSynthetic() {
return (this.rawNode.access & Opcodes.ACC_SYNTHETIC) != 0;
}

public ClassTree rename(ClassName name) {
this.rawNode.name = name.asInternalName();
return this;
}

@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,51 +14,25 @@
*/
package org.pitest.classinfo;

import java.lang.annotation.Annotation;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;

import org.objectweb.asm.Opcodes;
import org.pitest.functional.FCollection;
import java.util.Optional;

public class ClassInfo {
/**
* Captures information about a class, its position within a hierarchy. Changes to
* a class between runs are tracked by calculating the hash of its bytecode and the
* bytecode of classes it has a strong relationship to.
*/
public final class ClassInfo {

private final ClassIdentifier id;

private final int access;
private final Set<Integer> codeLines;
private final ClassPointer outerClass;
private final ClassPointer superClass;
private final Collection<ClassName> annotations;
private final String sourceFile;
private final Map<ClassName, Object> classAnnotationValues;

public ClassInfo(final ClassPointer superClass,
final ClassPointer outerClass, final ClassInfoBuilder builder) {
public ClassInfo(ClassPointer superClass, ClassPointer outerClass, ClassInfoBuilder builder) {
this.superClass = superClass;
this.outerClass = outerClass;
this.id = builder.id;
this.access = builder.access;
this.codeLines = builder.codeLines;
this.annotations = FCollection.map(builder.annotations,
ClassName.stringToClassName());
this.sourceFile = builder.sourceFile;
this.classAnnotationValues = builder.classAnnotationValues;
}

public int getNumberOfCodeLines() {
return this.codeLines.size();
}

public boolean isCodeLine(final int line) {
return this.codeLines.contains(line);
}

public ClassIdentifier getId() {
return this.id;
}
Expand All @@ -67,22 +41,6 @@ public ClassName getName() {
return this.id.getName();
}

public boolean isInterface() {
return (this.access & Opcodes.ACC_INTERFACE) != 0;
}

public boolean isAbstract() {
return (this.access & Opcodes.ACC_ABSTRACT) != 0;
}

public boolean isSynthetic() {
return (this.access & Opcodes.ACC_SYNTHETIC) != 0;
}

public boolean isTopLevelClass() {
return !getOuterClass().isPresent();
}

public Optional<ClassInfo> getOuterClass() {
return this.outerClass.fetch();
}
Expand All @@ -91,22 +49,6 @@ public Optional<ClassInfo> getSuperClass() {
return getParent();
}

public String getSourceFileName() {
return this.sourceFile;
}

public boolean hasAnnotation(final Class<? extends Annotation> annotation) {
return hasAnnotation(ClassName.fromClass(annotation));
}

public boolean hasAnnotation(final ClassName annotation) {
return this.annotations.contains(annotation);
}

public Object getClassAnnotationValue(final ClassName annotation) {
return this.classAnnotationValues.get(annotation);
}

public boolean descendsFrom(final Class<?> clazz) {
return descendsFrom(ClassName.fromClass(clazz));
}
Expand Down Expand Up @@ -152,20 +94,9 @@ private boolean descendsFrom(final ClassName clazz) {
return getSuperClass().get().descendsFrom(clazz);
}

public static Predicate<ClassInfo> matchIfAbstract() {
return ClassInfo::isAbstract;
}

@Override
public String toString() {
return this.id.getName().asJavaName();
}

public static Function<ClassInfo, ClassName> toClassName() {
return ClassInfo::getName;
}

public static Function<ClassInfo, HierarchicalClassId> toFullClassId() {
return ClassInfo::getHierarchicalId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2010 Henry Coles
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package org.pitest.classinfo;

class ClassInfoBuilder {

ClassIdentifier id;
String outerClass;
String superClass;
}
105 changes: 105 additions & 0 deletions pitest-entry/src/main/java/org/pitest/classinfo/ClassInfoVisitor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright 2010 Henry Coles
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package org.pitest.classinfo;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.pitest.bytecode.ASMVersion;
import org.pitest.functional.F5;

public final class ClassInfoVisitor extends ClassVisitor {

private final F5<Integer, String, String, String, String[], Boolean> filter = BridgeMethodFilter.INSTANCE;
private final ClassInfoBuilder classInfo;

private ClassInfoVisitor(final ClassInfoBuilder classInfo,
final ClassVisitor writer) {
super(ASMVersion.ASM_VERSION, writer);
this.classInfo = classInfo;
}

public static ClassInfoBuilder getClassInfo(final ClassName name,
final byte[] bytes, final long hash) {
final ClassReader reader = new ClassReader(bytes);
final ClassInfoBuilder info = new ClassInfoBuilder();
info.id = new ClassIdentifier(hash, name);
reader.accept(new ClassInfoVisitor(info, null), 0);
return info;
}

@Override
public void visitSource(final String source, final String debug) {
super.visitSource(source, debug);
}

@Override
public void visit(final int version, final int access, final String name,
final String signature, final String superName, final String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this.classInfo.superClass = superName;
}

@Override
public void visitOuterClass(final String owner, final String name,
final String desc) {
super.visitOuterClass(owner, name, desc);
this.classInfo.outerClass = owner;
}

@Override
public void visitInnerClass(final String name, final String outerName,
final String innerName, final int access) {
super.visitInnerClass(name, outerName, innerName, access);
if ((outerName != null)
&& this.classInfo.id.getName().equals(ClassName.fromString(name))) {
this.classInfo.outerClass = outerName;
}
}

@Override
public MethodVisitor visitMethod(final int access, final String name,
final String desc, final String signature, final String[] exceptions) {
if (shouldInstrument(access, name, desc, signature, exceptions)) {
return visitMethodIfRequired(access, name, desc, signature, exceptions);
} else {
return super.visitMethod(access, name, desc, signature, exceptions);
}
}

public MethodVisitor visitMethodIfRequired(final int access,
final String name, final String desc, final String signature,
final String[] exceptions) {

return new InfoMethodVisitor(this.classInfo);

}

private boolean shouldInstrument(final int access, final String name,
final String desc, final String signature, final String[] exceptions) {
return this.filter.apply(access, name, desc, signature, exceptions);
}

}

class InfoMethodVisitor extends MethodVisitor {
private final ClassInfoBuilder classInfo;

InfoMethodVisitor(final ClassInfoBuilder classInfo) {
super(ASMVersion.ASM_VERSION, null);
this.classInfo = classInfo;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,15 @@ private Optional<ClassInfo> nameToClassInfo(final ClassName name) {
if (bytes.isPresent()) {
final ClassInfoBuilder classData = ClassInfoVisitor.getClassInfo(name,
bytes.get(), this.hashFunction.hash(bytes.get()));
return contructClassInfo(classData);
return constructClassInfo(classData);
} else {
return Optional.empty();
}
}

public Optional<byte[]> querySource(final ClassName name) {
// cost of scanning the entire classpath is high, so avoid repeatedly
// looking for the same unresolvable classes
if (this.unknownClasses.contains(name)) {
return Optional.empty();
}
Expand All @@ -88,7 +90,7 @@ public Optional<byte[]> querySource(final ClassName name) {
return option;
}

private Optional<ClassInfo> contructClassInfo(final ClassInfoBuilder classData) {
private Optional<ClassInfo> constructClassInfo(final ClassInfoBuilder classData) {
return Optional.ofNullable(new ClassInfo(resolveClass(classData.superClass),
resolveClass(classData.outerClass), classData));
}
Expand Down

0 comments on commit 190f2e8

Please sign in to comment.