/** * 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/>. */ /** * <p>DescriptorMapping class.</p> * * @author Gordon Fraser */ package org.evosuite.instrumentation.testability; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Map; import java.util.Queue; import java.util.Set; import org.evosuite.PackageInfo; import org.evosuite.Properties; import org.evosuite.TestGenerationContext; import org.evosuite.classpath.ResourceList; import org.objectweb.asm.ClassReader; 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; public class DescriptorMapping { private static int id = 0; private final Map<String, String> descriptorMapping = new HashMap<String, String>(); private static Logger logger = LoggerFactory.getLogger(DescriptorMapping.class); private static DescriptorMapping instance = null; private DescriptorMapping() { } /** * <p>Getter for the field <code>instance</code>.</p> * * @return a {@link DescriptorMapping} object. */ public static DescriptorMapping getInstance() { if (instance == null) instance = new DescriptorMapping(); return instance; } final Map<String, String> originalDesc = new HashMap<String, String>(); final Map<String, String> originalName = new HashMap<String, String>(); private final Map<String, String> nameMapping = new HashMap<String, String>(); /** * <p>shouldTransform</p> * * @param classNameUnknown a {@link java.lang.String} object. * @return a boolean. */ public static boolean shouldTransform(String classNameUnknown) { //return false; String className = classNameUnknown.replace('/', '.'); switch (Properties.TT_SCOPE) { case ALL: return true; case TARGET: if (className.equals(Properties.TARGET_CLASS) || className.startsWith(Properties.TARGET_CLASS + "$")) return true; break; case PREFIX: if (className.startsWith(Properties.PROJECT_PREFIX)) return true; } return false; } /** * <p>isTransformedMethod</p> * * @param className a {@link java.lang.String} object. * @param methodName a {@link java.lang.String} object. * @param desc a {@link java.lang.String} object. * @return a boolean. */ public boolean isTransformedMethod(String className, String methodName, String desc) { logger.info("Initiating transformation of " + methodName); getMethodDesc(className, methodName, desc); // return originalDesc.containsKey(className.replace('.', '/') + "/" + methodName // + desc); return descriptorMapping.containsKey(className.replace('.', '/') + "/" + methodName + desc); } /** * <p>hasTransformedArguments</p> * * @param className a {@link java.lang.String} object. * @param methodName a {@link java.lang.String} object. * @param desc a {@link java.lang.String} object. * @return a boolean. */ public boolean hasTransformedArguments(String className, String methodName, String desc) { getMethodDesc(className, methodName, desc); if (!originalDesc.containsKey(className.replace('.', '/') + "/" + methodName + desc)) { return false; } else { String newDesc = originalDesc.get(className.replace('.', '/') + "/" + methodName + desc); for (Type type : Type.getArgumentTypes(newDesc)) { if (type.equals(Type.BOOLEAN_TYPE)) return true; } return false; } } /** * <p>isTransformedField</p> * * @param className a {@link java.lang.String} object. * @param fieldName a {@link java.lang.String} object. * @param desc a {@link java.lang.String} object. * @return a boolean. */ public boolean isTransformedField(String className, String fieldName, String desc) { getFieldDesc(className, fieldName, desc); return descriptorMapping.containsKey(className.replace('.', '/') + "/" + fieldName + desc); } /** * <p>isTransformedOrBooleanMethod</p> * * @param className a {@link java.lang.String} object. * @param methodName a {@link java.lang.String} object. * @param desc a {@link java.lang.String} object. * @return a boolean. */ public boolean isTransformedOrBooleanMethod(String className, String methodName, String desc) { logger.info("Checking method: " + className + "." + methodName + desc); String new_desc = getMethodDesc(className, methodName, desc); logger.info("Transformed desc is " + new_desc); String name = className.replace('.', '/') + "/" + methodName + desc; if (originalDesc.containsKey(name)) { logger.info("Desc is already transformed"); } return originalDesc.containsKey(name) || isBooleanMethod(desc); } private boolean isStringReplacement(String className, String methodName) { //FIXME the class TestabilityTransformation does not seem to exist any more... if (className.equals("org/evosuite/instrumentation/TestabilityTransformation")) { if (methodName.equals("StringEquals") || methodName.equals("StringEqualsIgnoreCase") || methodName.equals("StringIsEmpty") || methodName.equals("StringStartsWith") || methodName.equals("StringEndsWith")) return true; } return false; } /** * <p>isTransformedOrBooleanReturnMethod</p> * * @param className a {@link java.lang.String} object. * @param methodName a {@link java.lang.String} object. * @param desc a {@link java.lang.String} object. * @return a boolean. */ public boolean isTransformedOrBooleanReturnMethod(String className, String methodName, String desc) { if (isStringReplacement(className, methodName)) return true; logger.info("Checking method: " + className + "." + methodName + desc); String new_desc = getMethodDesc(className, methodName, desc); logger.info("Transformed desc is " + new_desc); String name = className.replace('.', '/') + "/" + methodName + desc; if (originalDesc.containsKey(name)) { return Type.getReturnType(originalDesc.get(name)).equals(Type.BOOLEAN_TYPE); } else { return Type.getReturnType(desc).equals(Type.BOOLEAN_TYPE); } } /** * <p>isTransformedOrBooleanField</p> * * @param className a {@link java.lang.String} object. * @param fieldName a {@link java.lang.String} object. * @param desc a {@link java.lang.String} object. * @return a boolean. */ public boolean isTransformedOrBooleanField(String className, String fieldName, String desc) { logger.info("Checking field: " + className + "." + fieldName + desc); String new_desc = getFieldDesc(className, fieldName, desc); logger.info("Transformed desc is " + new_desc); String name = className.replace('.', '/') + "/" + fieldName + desc; if (originalDesc.containsKey(name)) { logger.info("Desc is already transformed"); } return originalDesc.containsKey(name) || isBooleanField(desc); } /** * <p>isBooleanMethod</p> * * @param desc a {@link java.lang.String} object. * @return a boolean. */ public boolean isBooleanMethod(String desc) { Type[] types = Type.getArgumentTypes(desc); for (Type type : types) { if (type.equals(Type.BOOLEAN_TYPE)) { return true; } else if (type.getDescriptor().equals("[Z")) { return true; } } Type type = Type.getReturnType(desc); if (type.equals(Type.BOOLEAN_TYPE)) { return true; } else if (type.getDescriptor().equals("[Z")) { return true; } return false; } /** * <p>hasBooleanParameters</p> * * @param desc a {@link java.lang.String} object. * @return a boolean. */ public boolean hasBooleanParameters(String desc) { for (Type t : Type.getArgumentTypes(desc)) { if (t.equals(Type.BOOLEAN_TYPE)) return true; } return false; } private boolean isInside(String className) { //return false; String classNameWithDots = className.replace('/', '.'); switch (Properties.TT_SCOPE) { case ALL: if (!classNameWithDots.startsWith("java") && !classNameWithDots.startsWith("sun")) return true; case TARGET: if (classNameWithDots.equals(Properties.TARGET_CLASS) || classNameWithDots.startsWith(Properties.TARGET_CLASS + "$")) return true; break; case PREFIX: if (classNameWithDots.startsWith(Properties.PROJECT_PREFIX)) return true; } return false; } private boolean isBooleanField(String desc) { logger.info("Checkign type of field " + desc); return desc.endsWith("Z"); //Type type = Type.getType(desc); //return type.equals(Type.BOOLEAN_TYPE) // || (type.equals(Type.ARRAY) && type.getElementType().equals(Type.BOOLEAN_TYPE)); } private boolean isOutsideMethod(String className, String methodName, String desc) { Set<String> visited = new HashSet<String>(); Queue<String> parents = new LinkedList<String>(); parents.add(className); while (!parents.isEmpty()) { String name = parents.poll(); if (name == null) continue; visited.add(name); logger.info("Visiting class " + name + " while looking for source of " + className + "." + methodName); ClassReader reader; try { reader = new ClassReader(ResourceList.getInstance(TestGenerationContext.getInstance().getClassLoaderForSUT()).getClassAsStream(name)); ClassNode parent = new ClassNode(); reader.accept(parent, ClassReader.EXPAND_FRAMES); boolean isInside = isInside(parent.name); //boolean isInside = parent.name.startsWith(Properties.PROJECT_PREFIX.replace(".", // "/")) // || (!Properties.TARGET_CLASS_PREFIX.isEmpty() && parent.name.startsWith(Properties.TARGET_CLASS_PREFIX.replace(".", // "/"))); logger.info("Checking " + parent.name); for (Object o : parent.methods) { MethodNode mn2 = (MethodNode) o; if (mn2.name.equals(methodName) && mn2.desc.equals(desc)) { if (!isInside) { logger.info("Method " + name + " was defined outside the test package"); return true; } else { logger.info("Method " + name + " was defined outside the test package"); //return false; } } } for (Object o : parent.interfaces) { String par = (String) o; if (!visited.contains(par) && !parents.contains(par)) { parents.add(par); } } if (!visited.contains(parent.superName) && !parents.contains(parent.superName)) { parents.add(parent.superName); } } catch (IOException e) { logger.info("Error reading class " + name); } } return false; } private String transformMethodName(String className, String methodName, String desc, String transformedDesc) { Set<String> visited = new HashSet<String>(); Queue<String> parents = new LinkedList<String>(); parents.add(className); while (!parents.isEmpty()) { String name = parents.poll(); if (name == null) continue; visited.add(name); logger.info("Visiting class " + name + " while looking for name clashes of " + className + "." + methodName + transformedDesc); ClassReader reader; try { reader = new ClassReader(ResourceList.getInstance(TestGenerationContext.getInstance().getClassLoaderForSUT()).getClassAsStream(name)); ClassNode parent = new ClassNode(); reader.accept(parent, ClassReader.EXPAND_FRAMES); if (originalDesc.containsKey(className + "." + methodName + transformedDesc)) { logger.info("Method " + methodName + " has conflicting transformed method"); return methodName + "_transformed" + (id++); } for (Object o : parent.methods) { MethodNode mn2 = (MethodNode) o; //logger.info("Checking " + parent.name + "." + mn2.name + mn2.desc); if (mn2.name.equals(methodName) && mn2.desc.equals(transformedDesc)) { logger.info("Method " + methodName + " has conflicting method"); if (methodName.equals("<init>")) return null; // TODO: This should be a bit nicer return methodName + "_transformed" + (id++); } } for (Object o : parent.interfaces) { String par = (String) o; if (!visited.contains(par) && !parents.contains(par)) { parents.add(par); } } if (!visited.contains(parent.superName) && !parents.contains(parent.superName)) { parents.add(parent.superName); } } catch (IOException e) { logger.info("Error reading class " + name); } } return methodName; } private boolean isOutsideField(String className, String fieldName, String desc) { Set<String> visited = new HashSet<String>(); Queue<String> parents = new LinkedList<String>(); parents.add(className); while (!parents.isEmpty()) { String name = parents.poll(); if (name == null) continue; visited.add(name); logger.info("Checking class " + name + " while looking for definition of field " + fieldName); ClassReader reader; try { reader = new ClassReader(ResourceList.getInstance(TestGenerationContext.getInstance().getClassLoaderForSUT()).getClassAsStream(name)); ClassNode parent = new ClassNode(); reader.accept(parent, ClassReader.EXPAND_FRAMES); boolean isInside = isInside(parent.name); // boolean isInside = parent.name.startsWith(Properties.PROJECT_PREFIX.replace(".", // "/")) // | parent.name.startsWith(Properties.TARGET_CLASS_PREFIX.replace(".", // "/")); for (Object o : parent.fields) { FieldNode mn2 = (FieldNode) o; if (mn2.name.equals(fieldName) && mn2.desc.equals(desc)) { //if ((mn2.access & Opcodes.ACC_SYNTHETIC) == Opcodes.ACC_SYNTHETIC) { // logger.info("Not transforming synthetic field " + mn2.name); // return true; //} if (!isInside) { logger.info("Field " + name + " was defined outside the test package - " + parent.name); return true; } else { logger.info("Field " + name + " was defined inside the test package " + parent.name); return false; } } } for (Object o : parent.interfaces) { String par = (String) o; if (!visited.contains(par) && !parents.contains(par)) { parents.add(par); } } if (!visited.contains(parent.superName) && !parents.contains(parent.superName)) { parents.add(parent.superName); } } catch (IOException e) { logger.info("Error reading class " + name); } } return false; } private String transformMethodDescriptor(String desc) { String new_desc = "("; Type[] types = Type.getArgumentTypes(desc); for (Type type : types) { if (type.equals(Type.BOOLEAN_TYPE)) { new_desc += "I"; } else if (type.getDescriptor().equals("[Z")) { new_desc += "[I"; } else { new_desc += type.getDescriptor(); } } new_desc += ")"; Type type = Type.getReturnType(desc); if (type.equals(Type.BOOLEAN_TYPE)) { new_desc += "I"; } else if (type.getDescriptor().equals("[Z")) { new_desc += "[I"; } else { new_desc += type.getDescriptor(); } return new_desc; } private String transformFieldDescriptor(String className, String desc) { if (!shouldTransform(className)) { return desc; } logger.info("Transforming field instruction " + desc); if (isBooleanField(desc)) { // TODO: Check if this is actually transformed or not if (desc.equals("Z")) return "I"; else if (desc.equals("[Z")) return "[I"; else return desc; } else { return desc; } } /** * <p>getMethodName</p> * * @param className a {@link java.lang.String} object. * @param methodName a {@link java.lang.String} object. * @param desc a {@link java.lang.String} object. * @return a {@link java.lang.String} object. */ public String getMethodName(String className, String methodName, String desc) { if (!shouldTransform(className)) { return methodName; } String old = className + "." + methodName + desc; old = old.replace('.', '/'); if (isBooleanMethod(desc)) { getMethodDesc(className, methodName, desc); return nameMapping.get(old); } else { if (nameMapping.containsKey(old)) return nameMapping.get(old); else return methodName; } } /** * <p>getMethodDesc</p> * * @param className a {@link java.lang.String} object. * @param methodName a {@link java.lang.String} object. * @param desc a {@link java.lang.String} object. * @return a {@link java.lang.String} object. */ public String getMethodDesc(String className, String methodName, String desc) { if (!shouldTransform(className)) { return desc; } if (isBooleanMethod(desc)) { String old = className.replace('.', '/') + "/" + methodName + desc; //old = old.replace('.', '/'); if (!descriptorMapping.containsKey(old)) { if (isOutsideMethod(className, methodName, desc)) { logger.info("Is outside method: " + className + "." + methodName); descriptorMapping.put(old, desc); nameMapping.put(old, methodName); } else { logger.info("Is inside method: " + className + "." + methodName); String newDesc = transformMethodDescriptor(desc); String newName = transformMethodName(className, methodName, desc, newDesc); if (newName != null) { descriptorMapping.put(old, newDesc); nameMapping.put(old, newName); //nameMapping.put(className + "." + methodName + newDesc, // newName); logger.info("Keeping transformation from " + old + " to " + descriptorMapping.get(old) + " with new name " + newName); originalDesc.put(className.replace('.', '/') + "/" + newName + newDesc, desc); originalName.put(className.replace('.', '/') + "/" + newName + newDesc, methodName); } else { descriptorMapping.put(old, desc); nameMapping.put(old, methodName); // toDO: Original? originalDesc.put(className.replace('.', '/') + "/" + methodName + newDesc, desc); originalName.put(className.replace('.', '/') + "/" + methodName + newDesc, methodName); } } } return descriptorMapping.get(old); } else { return desc; } } /** * <p>getFieldDesc</p> * * @param className a {@link java.lang.String} object. * @param fieldName a {@link java.lang.String} object. * @param desc a {@link java.lang.String} object. * @return a {@link java.lang.String} object. */ public String getFieldDesc(String className, String fieldName, String desc) { if (!shouldTransform(className)) { return desc; } if (isBooleanField(desc)) { String old = className.replace('.', '/') + "/" + fieldName + desc; //old = old.replace('.', '/'); if (!descriptorMapping.containsKey(old)) { if (isOutsideField(className, fieldName, desc)) { descriptorMapping.put(old, desc); } else { descriptorMapping.put(old, transformFieldDescriptor(className, desc)); originalDesc.put(className.replace('.', '/') + "/" + fieldName + descriptorMapping.get(old), desc); } } return descriptorMapping.get(old); } else { return desc; } } /** * <p>Getter for the field <code>originalName</code>.</p> * * @param className a {@link java.lang.String} object. * @param methodName a {@link java.lang.String} object. * @param desc a {@link java.lang.String} object. * @return a {@link java.lang.String} object. */ public String getOriginalName(String className, String methodName, String desc) { String key = className.replace('.', '/') + "/" + methodName + desc; if (originalName.containsKey(key)) { logger.info("Found transformed version of " + className + "." + methodName + desc); return originalName.get(key); } else { logger.info("Don't have transformed version of " + className + "." + methodName + desc); return methodName; } } /** * <p>getOriginalDescriptor</p> * * @param className a {@link java.lang.String} object. * @param methodName a {@link java.lang.String} object. * @param desc a {@link java.lang.String} object. * @return a {@link java.lang.String} object. */ public String getOriginalDescriptor(String className, String methodName, String desc) { String key = className.replace('.', '/') + "/" + methodName + desc; if (originalDesc.containsKey(key)) { logger.info("Found transformed version of " + className + "." + methodName + desc); return originalDesc.get(key); } else { logger.info("Don't have transformed version of " + className + "." + methodName + desc); return desc; } } /** * <p>getOriginalTypes</p> * * @param className a {@link java.lang.String} object. * @param methodName a {@link java.lang.String} object. * @param desc a {@link java.lang.String} object. * @return an array of {@link org.objectweb.asm.Type} objects. */ public Type[] getOriginalTypes(String className, String methodName, String desc) { String key = className.replace('.', '/') + "/" + methodName + desc; if (originalDesc.containsKey(key)) return org.objectweb.asm.Type.getArgumentTypes(originalDesc.get(key)); else return org.objectweb.asm.Type.getArgumentTypes(desc); } }