/** * Copyright (C) 2010-2017 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 <http://www.gnu.org/licenses/>. */ package org.evosuite.continuous.project; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.evosuite.continuous.project.ProjectStaticData.ClassInfo; import org.evosuite.seeding.CastClassAnalyzer; import org.evosuite.setup.DependencyAnalysis; import org.evosuite.setup.InheritanceTree; import org.evosuite.setup.InheritanceTreeGenerator; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.MethodNode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <p> * Class representing the class graph. For each class (CUT and non-testable), * not only we want to know its hierarchy (parents, interfaces, subclasses, etc) * but also where in the project it is used by other classes as input. * </p> * * <p> * For definition of CUT, see {@link ProjectStaticData} * </p> */ public class ProjectGraph { /* * Need to guarantee to build the graph once with all needed info, and then the public * methods just query the map data structures directly (instead of recalculating * on the fly) */ private static final Logger logger = LoggerFactory.getLogger(ProjectGraph.class); private final InheritanceTree inheritanceTree; /** * FIXME * Map from TODO (key) to TODO (value) */ private final Map<String, Set<String>> castInformation; private final ProjectStaticData data; /** * Main constructor * * @param data */ public ProjectGraph(ProjectStaticData data) { this.data = data; inheritanceTree = InheritanceTreeGenerator.createFromClassList(data.getClassNames()); castInformation = new HashMap<String, Set<String>>(); if(logger.isDebugEnabled()){ logger.debug("Classes in inheritance tree: " + inheritanceTree.getAllClasses()); } } /** * <p> * Return the full qualifying names of all classes that are CUTs and that * are used as input in any of the public methods of <code>cut</code> (but * not of any of its parent hierarchy). * </p> * * <p> * If a method takes as input a reference of a non-SUT class (e.g., * <code>java.lang.Object</code>), but then there is a cast to a CUT (e.g. a * class X), then X will be added in the returned set. * </p> * * <p> * If a method takes as input a reference of a SUT class X that is not a CUT * (e.g., an interface with no code), then X will <b>not</b> be added in the * returned set, although based on <code>includeSubclasses</code> we might * add its subclasses. * </p> * * @param cut * the class under test (CUT) * @param includeSubclasses * If a class X is in the returned set, then normally no subclass * Y of X would be added in the returned set, unless Y is * directly used in the CUT as input. * @return a set of full qualifying names of CUTs * @throws IllegalArgumentException * if the input <code>cut</code> is not a CUT */ public Set<String> getCUTsDirectlyUsedAsInput(String cut, boolean includeSubclasses) throws IllegalArgumentException { checkCUT(cut); Set<String> parameterClasses = recursionToSearchDirectInputs(cut,includeSubclasses); parameterClasses.remove(cut); removeNonCUT(parameterClasses); logger.debug("Parameter classes of " + cut + ": " + parameterClasses); return parameterClasses; } protected Set<String> recursionToSearchDirectInputs(String aClass, boolean includeSubclasses){ if (includeSubclasses) { Set<String> directlyUsed = recursionToSearchDirectInputs(aClass, false); //recursion Set<String> all = new LinkedHashSet<String>(directlyUsed); for (String name : directlyUsed) { all.addAll(getAllCUTsSubclasses(name)); } return all; } Set<String> parameterClasses = getParameterClasses(aClass); parameterClasses.addAll(getCastClasses(aClass)); return parameterClasses; } /** * Calculate all the CUTs that use the given <code>cut</code> as input in * any of their public methods * * @param cut * the class under test (CUT) * @param includeSuperClasses * not only using as input the * <code>cut</body>, but also any of its CUT ancestors/interfaces * @return a set of full qualifying names of CUTs * @throws IllegalArgumentException * if the input <code>cut</code> is not a CUT */ public Set<String> getCUTsThatUseThisCUTasInput(String cut, boolean includeSuperClasses) throws IllegalArgumentException { checkCUT(cut); Set<String> classNames = recursionToSearchWhatUsesItAsInput(cut,includeSuperClasses); classNames.remove(cut); removeNonCUT(classNames); return classNames; } protected Set<String> recursionToSearchWhatUsesItAsInput(String aClass, boolean includeSuperClasses){ if (includeSuperClasses) { Set<String> directlyUsed = recursionToSearchWhatUsesItAsInput(aClass, false); //recursion Set<String> all = new LinkedHashSet<String>(directlyUsed); for (String name : getAllCUTsParents(aClass)) { all.addAll(recursionToSearchWhatUsesItAsInput(name, false)); //recursion } return all; } Set<String> classNames = new LinkedHashSet<String>(); for (String className : inheritanceTree.getAllClasses()) { if (className.equals(aClass)){ continue; } Set<String> inputClasses = getCUTsDirectlyUsedAsInput(className, true); if (inputClasses.contains(aClass)){ classNames.add(className); } } return classNames; } /** * Is the given class name representing an interface in the SUT? * @param className * @return * @throws IllegalArgumentException */ public boolean isInterface(String className) throws IllegalArgumentException { checkClass(className); ClassNode node = getClassNode(className); if ((node.access & Opcodes.ACC_INTERFACE) == Opcodes.ACC_INTERFACE) return true; return false; } /** * Is the given class name representing a concrete class in the SUT? * @param className * @return * @throws IllegalArgumentException */ public boolean isConcrete(String className) throws IllegalArgumentException { checkClass(className); return !isInterface(className) && !isAbstract(className); } /** * Is the given class name representing an abstract class in the SUT? * @param className * @return * @throws IllegalArgumentException */ public boolean isAbstract(String className) throws IllegalArgumentException { checkClass(className); ClassNode node = getClassNode(className); return (node.access & Opcodes.ACC_ABSTRACT) == Opcodes.ACC_ABSTRACT; } /** * Check if the given class does belong to the SUT. * This is independent on whether it is a CUT (ie testable) or not. * * @param className * @throws IllegalArgumentException */ private void checkClass(String className) throws IllegalArgumentException{ if(!data.containsClass(className)){ throw new IllegalArgumentException("Class "+className+" is not part of the SUT"); } } /** * Check if the given class does belong to the SUT and if it is testable (ie a CUT). * * @param cut */ private void checkCUT(String cut) throws IllegalArgumentException{ ClassInfo info = data.getClassInfo(cut); if(info==null){ throw new IllegalArgumentException("Class "+cut+" is not part of the SUT"); } if(!info.isTestable()){ throw new IllegalArgumentException("Class "+cut+" belongs to the SUT, but it is not a CUT (ie testable)"); } } /** * Return all the child hierarchy of the <code>aClass</code>. Include only classes * that are CUTs. * This <code>aClass</code> will not be part of the returned set, even if it is a CUT * * @param aClass * a class belonging to the SUT, but not necessarily a CUT * @return a set of full qualifying names of CUTs * @throws IllegalArgumentException * if the input <code>aClass</code> does not belong to the SUT */ public Set<String> getAllCUTsSubclasses(String aClass) throws IllegalArgumentException { checkClass(aClass); Set<String> set = null; try{ //FIXME set = inheritanceTree.getSubclasses(aClass); } catch(Exception e){ logger.error("Bug in inheritanceTree: "+e); return new HashSet<String>(); } set.remove(aClass); removeNonCUT(set); return set; } /** * Return all the CUT classes that this <code>aClass</code> extends/implements * (ie, parent hierarchy). * This <code>aClass</code> will not be part of the returned set, even if it is a CUT * * @param aClass * a class belonging to the SUT, but not necessarily a CUT * @return a set of full qualifying names of CUTs * @throws IllegalArgumentException * if the input <code>aClass</code> does not belong to the SUT */ public Set<String> getAllCUTsParents(String aClass) throws IllegalArgumentException { checkClass(aClass); Set<String> set = null; try{ //FIXME set = inheritanceTree.getSuperclasses(aClass); } catch(Exception e){ logger.error("Bug in inheritanceTree: "+e); return new HashSet<String>(); } set.remove(aClass); //it seems inheritanceTree returns 'cut' in the set removeNonCUT(set); return set; } private void removeNonCUT(Set<String> set) throws IllegalArgumentException{ Iterator<String> iter = set.iterator(); while(iter.hasNext()){ String name = iter.next(); ClassInfo info = data.getClassInfo(name); if(info==null){ throw new IllegalArgumentException("Class "+name+" does not belong to the SUT"); } if(!info.isTestable()){ iter.remove(); } } } /** * For now use the cache provided by dependency analysis * * @param className * @return */ private ClassNode getClassNode(String className) { return DependencyAnalysis.getClassNode(className); } /** * Determine the set of classes that are used in casts in a CUT * * @param className * @return */ private Set<String> getCastClasses(String className) { if (!castInformation.containsKey(className)) { CastClassAnalyzer analyzer = new CastClassAnalyzer(); // The analyzer gets the classnode from the DependencyAnalysis classnode cache Map<Type, Integer> castMap = analyzer.analyze(className); Set<String> castClasses = new LinkedHashSet<String>(); for (Type type : castMap.keySet()) { String name = type.getClassName(); if (inheritanceTree.hasClass(name)) { castClasses.add(name); } } castInformation.put(className, castClasses); } return castInformation.get(className); } @SuppressWarnings("unchecked") private Set<String> getParameterClasses(String cut) { Set<String> parameters = new LinkedHashSet<String>(); ClassNode node = getClassNode(cut); List<MethodNode> methods = node.methods; for (MethodNode methodNode : methods) { addParameterClasses(methodNode, parameters); } List<FieldNode> fields = node.fields; for (FieldNode fieldNode : fields) { addParameterClasses(fieldNode, parameters); } return parameters; } private void addParameterClasses(MethodNode methodNode, Set<String> classNames) { // TODO: Only including public methods for now. Should this be refined to match // TestClusterGenerator.canUse? if ((methodNode.access & Opcodes.ACC_PUBLIC) != Opcodes.ACC_PUBLIC) return; for (Type parameterType : Type.getArgumentTypes(methodNode.desc)) { String name = parameterType.getClassName(); if (inheritanceTree.hasClass(name)) { classNames.add(name); } } } private void addParameterClasses(FieldNode fieldNode, Set<String> classNames) { // TODO: Only including public fields for now. Should this be refined to match // TestClusterGenerator.canUse? if ((fieldNode.access & Opcodes.ACC_PUBLIC) != Opcodes.ACC_PUBLIC) return; String name = Type.getType(fieldNode.desc).getClassName(); if (inheritanceTree.hasClass(name)) { classNames.add(name); } } }