org.evosuite.setup.InheritanceTreeGenerator Maven / Gradle / Ivy
/**
* Copyright (C) 2010-2018 Gordon Fraser, Andrea Arcuri and EvoSuite
* contributors
*
* This file is part of EvoSuite.
*
* EvoSuite is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* EvoSuite is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with EvoSuite. If not, see .
*/
/**
*
*/
package org.evosuite.setup;
import java.io.*;
import java.util.*;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.evosuite.PackageInfo;
import org.evosuite.Properties;
import org.evosuite.TestGenerationContext;
import org.evosuite.classpath.ResourceList;
import org.evosuite.rmi.ClientServices;
import org.evosuite.statistics.RuntimeVariable;
import org.evosuite.utils.LoggingUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InnerClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.thoughtworks.xstream.XStream;
/**
* @author Gordon Fraser
*
*/
public class InheritanceTreeGenerator {
private static Logger logger = LoggerFactory.getLogger(InheritanceTreeGenerator.class);
private static final String resourceFolder = "client/src/main/resources/";
private static final String jdkFile = "JDK_inheritance.xml";
private static final String shadedJdkFile = "JDK_inheritance_shaded.xml";
/**
* Iterate over items in classpath and analyze them
*
* @param classPath
* @return
*/
public static InheritanceTree createFromClassPath(List classPath) {
if (!Properties.INSTRUMENT_CONTEXT && !Properties.INHERITANCE_FILE.isEmpty()) {
try {
InheritanceTree tree = readInheritanceTree(Properties.INHERITANCE_FILE);
LoggingUtils.getEvoLogger().info("* Inheritance tree loaded from {}",
Properties.INHERITANCE_FILE);
return tree;
} catch (IOException e) {
LoggingUtils.getEvoLogger().warn("* Error loading inheritance tree: {}", e);
}
}
logger.debug("Reading JDK data");
InheritanceTree inheritanceTree = readJDKData();
if(inheritanceTree==null){
inheritanceTree = new InheritanceTree();
}
logger.debug("CP: {}", classPath);
for (String classPathEntry : classPath) {
logger.debug("Looking at CP entry: {}", classPathEntry);
if (classPathEntry.isEmpty())
continue;
if (classPathEntry.matches(".*evosuite-.*\\.jar"))
continue;
logger.debug("Analyzing classpath entry {}", classPathEntry);
LoggingUtils.getEvoLogger().info(" - " + classPathEntry);
for(String className : ResourceList.getInstance(
TestGenerationContext.getInstance().getClassLoaderForSUT()).getAllClasses(classPathEntry, "", true, false)) {
// handle individual class
analyzeClassStream(inheritanceTree, ResourceList.getInstance(
TestGenerationContext.getInstance().getClassLoaderForSUT()).getClassAsStream(className), false);
}
// analyze(inheritanceTree, classPathEntry);
}
return inheritanceTree;
}
/**
* Create inheritance tree only for the classes passed as parameter
*
*
* Private classes will be ignored
*
* @param classNames
* @return
*/
public static InheritanceTree createFromClassList(Collection classNames)
throws IllegalArgumentException {
if (classNames == null || classNames.isEmpty()) {
throw new IllegalArgumentException("No class name defined");
}
InheritanceTree inheritanceTree = new InheritanceTree();
for (String className : classNames) {
if (className == null) {
throw new IllegalArgumentException("Null class name");
}
analyzeClassName(inheritanceTree, className);
}
// Remove all classes not in the parameter list, otherwise we get the superclasses from outside the list
Set classes = new HashSet<>(inheritanceTree.getAllClasses());
for (String className : classes) {
if (!classNames.contains(className))
inheritanceTree.removeClass(className);
}
return inheritanceTree;
}
public static void gatherStatistics(InheritanceTree inheritanceTree) {
ClientServices.getInstance().getClientNode().trackOutputVariable(RuntimeVariable.Classpath_Classes,
inheritanceTree.getNumClasses());
}
/**
*
*/
private static void analyze(InheritanceTree inheritanceTree, File file) {
if (!file.canRead()) {
return;
}
if (file.getName().endsWith(".jar")) {
// handle jar file
analyzeJarFile(inheritanceTree, file);
} else if (file.getName().endsWith(".class")) {
// handle individual class
analyzeClassFile(inheritanceTree, file);
} else if (file.isDirectory()) {
// handle directory
analyzeDirectory(inheritanceTree, file);
} else {
// Invalid entry?
}
}
private static void analyzeJarFile(InheritanceTree inheritanceTree, File jarFile) {
ZipFile zf;
try {
zf = new ZipFile(jarFile);
} catch (Exception e) {
logger.warn("Failed to open/analyze jar file " + jarFile.getAbsolutePath()
+ " , " + e.getMessage());
return;
}
final Enumeration> e = zf.entries();
while (e.hasMoreElements()) {
final ZipEntry ze = (ZipEntry) e.nextElement();
final String fileName = ze.getName();
if (!fileName.endsWith(".class"))
continue;
try {
analyzeClassStream(inheritanceTree, zf.getInputStream(ze), false);
} catch (IOException e1) {
/*
* even if there is a problem with one of the entries, we can still
* go on and look at the others
*/
logger.error("Error while analyzing class " + fileName + " in the jar "
+ jarFile.getAbsolutePath(), e1);
}
}
try {
zf.close();
} catch (final IOException e1) {
logger.warn("Failed to close jar file " + jarFile.getAbsolutePath() + " , "
+ e1.getMessage());
}
}
private static void analyzeDirectory(InheritanceTree inheritanceTree, File directory) {
for (File file : directory.listFiles()) {
analyze(inheritanceTree, file);
}
}
private static void analyzeClassFile(InheritanceTree inheritanceTree, File classFile) {
try {
analyzeClassStream(inheritanceTree, new FileInputStream(classFile), false);
} catch (FileNotFoundException e) {
logger.error("", e);
}
}
private static void analyzeClassName(InheritanceTree inheritanceTree, String className) {
InputStream stream = ResourceList.getInstance(
TestGenerationContext.getInstance().getClassLoaderForSUT()).getClassAsStream(className);
if(stream==null){
throw new IllegalArgumentException("Failed to locate/load class: "+className);
}
logger.debug("Going to analyze: {}", className);
analyzeClassStream(inheritanceTree, stream, false);
}
private static void analyzeClassStream(InheritanceTree inheritanceTree,
InputStream inputStream, boolean onlyPublic) {
try {
ClassReader reader = new ClassReader(inputStream);
inputStream.close();
ClassNode cn = new ClassNode();
reader.accept(cn, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG
| ClassReader.SKIP_CODE);
analyzeClassNode(inheritanceTree, cn, onlyPublic);
} catch (IOException e) {
logger.error("", e);
} catch(java.lang.ArrayIndexOutOfBoundsException e) {
logger.error("ASM Error while reading class ("+e.getMessage()+")");
}
}
@SuppressWarnings("unchecked")
private static void analyzeClassNode(InheritanceTree inheritanceTree,
ClassNode cn, boolean onlyPublic) {
logger.info("Analyzing class {}", cn.name);
// Don't load classes already seen from a different CP entry
if(inheritanceTree.hasClass(cn.name))
return;
if ((Opcodes.ACC_INTERFACE & cn.access) != Opcodes.ACC_INTERFACE) {
for (Object m : cn.methods) {
MethodNode mn = (MethodNode) m;
inheritanceTree
.addAnalyzedMethod(cn.name, mn.name, mn.desc);
}
if ((Opcodes.ACC_ABSTRACT & cn.access) == Opcodes.ACC_ABSTRACT) {
inheritanceTree.registerAbstractClass(cn.name);
}
}else{
inheritanceTree.registerInterface(cn.name);
}
if (onlyPublic) {
if ((cn.access & Opcodes.ACC_PUBLIC) == 0) {
return;
}
// } else {
// if (!canUse(cn)) {
// logger.info("Cannot use "+cn.name);
// return;
// }
}
if (cn.superName != null)
inheritanceTree.addSuperclass(cn.name, cn.superName, cn.access);
List interfaces = cn.interfaces;
for (String interfaceName : interfaces) {
inheritanceTree.addInterface(cn.name, interfaceName);
}
}
private static final Pattern ANONYMOUS_MATCHER1 = Pattern.compile(".*\\$\\d+.*$");
private static final Pattern ANONYMOUS_MATCHER2 = Pattern.compile(".*\\.\\d+.*$");
public static boolean canUse(ClassNode cn) {
if ((cn.access & Opcodes.ACC_PRIVATE) == Opcodes.ACC_PRIVATE) {
logger.debug(cn.name + " is private, ignoring it");
return false;
}
if (ANONYMOUS_MATCHER1.matcher(cn.name).matches()) {
logger.debug(cn.name + " looks like an anonymous class, ignoring it");
return false;
}
if (ANONYMOUS_MATCHER2.matcher(cn.name).matches()) {
logger.debug(cn.name + " looks like an anonymous class, ignoring it");
return false;
}
// TODO: Handle Deprecated
if (cn.name.startsWith("junit"))
return false;
// If the SUT is not in the default package, then
// we cannot import classes that are in the default
// package
/*
if ((cn.access & Opcodes.ACC_PUBLIC) != Opcodes.ACC_PUBLIC) {
String nameWithDots = cn.name.replace('/', '.');
String packageName = ClassUtils.getPackageName(nameWithDots);
if (!packageName.equals(Properties.CLASS_PREFIX)) {
return false;
}
}
*/
// ASM has some problem with the access of inner classes
// so we check if the inner class name is the current class name
// and if so, check if the inner class is actually accessible
@SuppressWarnings("unchecked")
List in = cn.innerClasses;
for (InnerClassNode inc : in) {
if (cn.name.equals(inc.name)) {
// logger.debug("ASM weird behaviour: Inner class equals class: " + inc.name);
if ((inc.access & Opcodes.ACC_PRIVATE) == Opcodes.ACC_PRIVATE) {
return false;
}
/*
if ((inc.access & Opcodes.ACC_PUBLIC) != Opcodes.ACC_PUBLIC) {
String nameWithDots = inc.name.replace('/', '.');
String packageName = ClassUtils.getPackageName(nameWithDots);
if (!packageName.equals(Properties.CLASS_PREFIX)) {
return false;
}
}
*/
logger.debug("Can use inner class: " + inc.name);
return true;
}
}
logger.debug(cn.name + " is accessible");
return true;
}
private static List classExceptions = Arrays.asList(new String[] {
"java/lang/Class", "java/lang/Object", "java/lang/String",
"java/lang/Comparable", "java/io/Serializable", "com/apple", "apple/",
"sun/", "com/sun", "com/oracle", "sun/awt", "jdk/internal",
"java/util/prefs/MacOSXPreferences" });
/**
* During runtime, we do not want to consider standard classes to safe some
* time, so we perform this analysis only once.
*/
public static void generateJDKCluster(String... filters) {
int counter = 0;
Collection list = getAllResources();
InheritanceTree inheritanceTree = new InheritanceTree();
List others = new ArrayList<>();
/*
* Filtering against other inheritance trees is necessary to remove any
* version specific classes. For example, first generate an inheritance tree
* with JDK6 and then one with JDK7, filtering against JDK6, to keep only
* the intersection of classes.
*/
for (String filterFile : filters) {
logger.info("Trying to load {}", filterFile);
try {
InheritanceTree tree = readUncompressedInheritanceTree(filterFile);
others.add(tree);
} catch (IOException e) {
logger.info("Error: " + e);
}
}
EXCEPTION: for (String name : list) {
// We do not consider sun.* and apple.* and com.*
for (String exception : classExceptions) {
if (name.startsWith(exception.replace('/','.'))) {
logger.info("Skipping excluded class " + name);
continue EXCEPTION;
}
}
for (InheritanceTree other : others) {
if (!other.hasClass(ResourceList.getClassNameFromResourcePath(name))) {
logger.info("Skipping {} because it is not in other inheritance tree", name);
continue EXCEPTION;
} else {
logger.info("Not skipping {} because it is in other inheritance tree", name);
}
}
if (name.matches(".*\\$\\d+$")) {
logger.info("Skipping anonymous class");
continue;
}
InputStream stream = ResourceList.getInstance(
TestGenerationContext.getInstance().getClassLoaderForSUT()).getClassAsStream(name);
if(stream == null){
logger.warn("Cannot open/find "+name);
} else {
analyzeClassStream(inheritanceTree, stream, true);
counter++;
}
}
logger.info("Finished checking classes, writing data for "+counter+" classes");
// Write data to XML file
try {
FileOutputStream stream = new FileOutputStream(new File(resourceFolder+jdkFile));
XStream xstream = new XStream();
XStream.setupDefaultSecurity(xstream);
xstream.allowTypesByWildcard(new String[] {"org.evosuite.**", "org.jgrapht.**"});
xstream.toXML(inheritanceTree, stream);
} catch (FileNotFoundException e) {
logger.error("", e);
}
}
public static InheritanceTree readJDKData() {
XStream xstream = new XStream();
XStream.setupDefaultSecurity(xstream);
xstream.allowTypesByWildcard(new String[] {"org.evosuite.**", "org.jgrapht.**"});
String fileName;
if(! PackageInfo.isCurrentlyShaded()) {
fileName = "/" + jdkFile;
} else {
fileName = "/" + shadedJdkFile;
}
InputStream inheritance = InheritanceTreeGenerator.class.getResourceAsStream(fileName);
if (inheritance != null) {
return (InheritanceTree) xstream.fromXML(inheritance);
} else {
logger.warn("Found no JDK inheritance tree in the resource path: "+fileName);
return null;
}
}
public static InheritanceTree readInheritanceTree(String fileName) throws IOException {
XStream xstream = new XStream();
XStream.setupDefaultSecurity(xstream);
xstream.allowTypesByWildcard(new String[] {"org.evosuite.**", "org.jgrapht.**"});
GZIPInputStream inheritance = new GZIPInputStream(new FileInputStream(new File(fileName)));
return (InheritanceTree) xstream.fromXML(inheritance);
}
public static InheritanceTree readUncompressedInheritanceTree(String fileName)
throws IOException {
XStream xstream = new XStream();
XStream.setupDefaultSecurity(xstream);
xstream.allowTypesByWildcard(new String[] {"org.evosuite.**", "org.jgrapht.**"});
InputStream inheritance = new FileInputStream(new File(fileName));
return (InheritanceTree) xstream.fromXML(inheritance);
}
public static void writeInheritanceTree(InheritanceTree tree, File file) throws IOException {
XStream xstream = new XStream();
XStream.setupDefaultSecurity(xstream);
xstream.allowTypesByWildcard(new String[] {"org.evosuite.**", "org.jgrapht.**"});
GZIPOutputStream output = new GZIPOutputStream(new FileOutputStream(file));
xstream.toXML(tree, output);
output.close();
}
public static Collection getAllResources() {
Collection retval = getResources(System.getProperty("java.class.path", "."));
retval.addAll(getResources(System.getProperty("sun.boot.class.path")));
return retval;
}
private static Collection getResources(String classPath) {
final ArrayList retval = new ArrayList<>();
String[] classPathElements = classPath.split(File.pathSeparator);
for (final String element : classPathElements) {
if (element.contains("evosuite"))
continue;
try {
retval.addAll(ResourceList.getInstance(
TestGenerationContext.getInstance().getClassLoaderForSUT()).getAllClasses(element, "",true,true));
} catch (IllegalArgumentException e) {
System.err.println("Does not exist: " + element);
}
}
return retval;
}
private static void makeShadedCopy(){
String content = null;
try {
content = new Scanner(new File(resourceFolder + jdkFile)).useDelimiter("\\Z").next();
} catch (Exception e) {
logger.error("Error when reading JDK file",e);
return;
}
String shadedContent = content.replace(PackageInfo.getEvoSuitePackage(), PackageInfo.getShadedEvoSuitePackage());
File shadedFile = new File(resourceFolder+shadedJdkFile);
if(shadedFile.exists()){
shadedFile.delete();
}
try (PrintWriter out = new PrintWriter(shadedFile)) {
out.write(shadedContent);
} catch (Exception e){
logger.error("Error when making shaded copy");
}
}
/*
usage example from command line:
java -cp master/target/evosuite-master-0.1.1-SNAPSHOT-jar-minimal.jar org.evosuite.setup.InheritanceTreeGenerator
*/
public static void main(String[] args) {
generateJDKCluster(args);
makeShadedCopy();
}
}