All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.evosuite.assertion.CheapPurityAnalyzer Maven / Gradle / Ivy

The newest version!
/**
 * 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.assertion;

import org.evosuite.instrumentation.BytecodeInstrumentation;
import org.evosuite.runtime.classhandling.ClassResetter;
import org.evosuite.runtime.mock.MockList;
import org.evosuite.setup.DependencyAnalysis;
import org.evosuite.setup.InheritanceTree;
import org.evosuite.setup.TestCluster;
import org.evosuite.utils.JdkPureMethodsList;
import org.objectweb.asm.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * This class performs a very cheap purity analysis by under-approximating the set of
 * pure methods. It does not use any kind of escape-analysis. The purity analysis
 * is solely based on already collected bytecode instructions during class loading.
 * A method is cheap-pure if and only if:
 * 
    *
  • The method is listed in the JdkPureMethodList
  • *
  • There is no declared overriding method that is not cheap-pure
  • *
  • All invoked classes are loaded in the inheritance tree
  • *
  • Has no PUTSTATIC nor PUTFIELD instructions
  • *
  • All static invocations (INVOKESTATIC) are made to cheap-pure static methods
  • *
  • All special invocations (INVOKESPECIAL) are also made to cheap-pure methods
  • *
  • All interface invocations (INVOKEINTERFACE) are also made to cheap-pure methods
  • *
* * @author galeotti * */ public class CheapPurityAnalyzer { private static Logger logger = LoggerFactory .getLogger(CheapPurityAnalyzer.class); private final HashSet updateFieldMethodList = new HashSet(); private final HashMap purityCache = new HashMap(); private final HashSet methodEntries = new HashSet(); /** * We return this value when we can't conclude if a given method is pure or not. */ private static final boolean DEFAULT_PURITY_VALUE = false; private static final CheapPurityAnalyzer instance = new CheapPurityAnalyzer(); public static CheapPurityAnalyzer getInstance() { return instance; } public List getPureMethods(String className) { ArrayList list = new ArrayList<>(); for (MethodEntry m : methodEntries) { if (m.className.equals(className) && isPure(m) && m.methodName != ClassResetter.STATIC_RESET) { list.add(m.methodName + m.descriptor); } } return list; } /** * Returns if the method is cheap-pure. * * @param className The declaring class name * @param methodName The method name * @param descriptor The method descriptor * @return true if the method is cheap-pure, false otherwise */ public boolean isPure(String className, String methodName, String descriptor) { MethodEntry entry = new MethodEntry(className, methodName, descriptor); return isPure(entry); } private boolean isPure(MethodEntry entry) { Stack emptyStack = new Stack(); return isPure(entry, emptyStack); } private boolean isCached(MethodEntry entry) { return this.purityCache.containsKey(entry); } private boolean getCacheValue(MethodEntry entry) { return this.purityCache.get(entry); } private void addCacheValue(MethodEntry entry, boolean new_value) { if (isCached(entry)) { boolean old_value = this.purityCache.get(entry); if (old_value == false && new_value == true) { String fullyQuantifiedMethodName = entry.className + "." + entry.methodName + entry.descriptor; logger.warn("Purity value in cache cannot evolve from NOT_PURE to PURE for method " + fullyQuantifiedMethodName); } } this.purityCache.put(entry, new_value); } private boolean isPure0(MethodEntry entry, Stack callStack) { if (isRandomCall(entry)) { return false; } if(isArrayCall(entry)) { return true; } if (isJdkPureMethod(entry)) { return true; } if (!BytecodeInstrumentation.checkIfCanInstrument(entry.className)) { return false; } if (this.updateFieldMethodList.contains(entry)) { // If the method has an implementation that // modifies any field, we conclude the method is // NOT PURE return false; } if (staticCalls.containsKey(entry)) { Set calls = staticCalls.get(entry); if (checkAnyCallImpure(calls, entry, callStack)) { // If the method has an implementation that // invokes at least one *static method* that is not // pure, we conclude the method is NOT PURE return false; } } if (specialCalls.containsKey(entry)) { Set calls = specialCalls.get(entry); if (checkAnyCallImpure(calls, entry, callStack)) { // If the method has an implementation that // has at least one *special call* that is not // pure, we conclude the method is NOT PURE return false; } } if (virtualCalls.containsKey(entry)) { Set calls = virtualCalls.get(entry); if (checkAnyCallImpure(calls, entry, callStack)) { // If the method has an implementation that // invokes at least one *virtual method* that is not // pure, we conclude the method is NOT PURE return false; } } if (interfaceCalls.containsKey(entry)) { Set calls = interfaceCalls.get(entry); if (checkAnyCallImpure(calls, entry, callStack)) { // If the method has an implementation that // has at least one *interface call* that is not // pure, we conclude the method is NOT PURE return false; } } // check overriding methods if (checkAnyOverridingMethodImpure(entry, callStack)) { // If there is any descendant of this class that // has a declaration for this method that is not pure, // we conclude the method is NOT PURE return false; } if (this.interfaceMethodEntries.contains(entry)) { // IF this is an interface method returns true // since we could not find any implementor of // this method interface that is impure return true; } if (this.methodsWithBodies.contains(entry)) { // This means a method body for Foo.m() declared in Foo // and there are no reasons to think Foo.m() // (namely no calls to impure methods, no field updates, // no descendant that has an impure implementation), // we conclude the method is *PURE* return true; } else { // This means there is no body for Foo.m() declared in Foo, // but there is no impure descendant of Foo.m() also. // Then, the closest implementation of m() in the // superclasses of Foo should be checked since this might // be called during runtime boolean purityValueClosestSuperclass = isPureSuperclass(entry, callStack); return purityValueClosestSuperclass; } } private boolean isPureSuperclass(MethodEntry entry, Stack callStack) { InheritanceTree inheritanceTree = TestCluster.getInheritanceTree(); for (String superClassName : inheritanceTree .getOrderedSuperclasses(entry.className)) { if (superClassName.equals(entry.className)) continue; MethodEntry superEntry = new MethodEntry(superClassName, entry.methodName, entry.descriptor); if (!callStack.contains(superEntry) && methodsWithBodies.contains(superEntry)) { // We can conclude the purity of this method because // we found an implementation in a super class for it Stack newStack = new Stack(); newStack.addAll(callStack); newStack.add(superEntry); boolean purityValueForSuperClass = isPure(superEntry, newStack); return purityValueForSuperClass; } } // We cannot conclusive decide if the // method is pure or not, so we fail // to default purity value return DEFAULT_PURITY_VALUE; } private boolean isRandomCall(MethodEntry entry) { if (entry.className.equals("java.util.Random")) return true; else if (entry.className.equals("java.security.SecureRandom")) return true; else if (entry.className.equals(org.evosuite.runtime.Random.class.getName())) return true; else if (entry.className.equals("java.lang.Math") && entry.methodName.equals("random")) return true; else return false; } /** * clone, equals, getClass, hashCode, toString seem pure * TODO: finalize, notify, notifyAll, wait? * @param entry * @return */ private boolean isArrayCall(MethodEntry entry) { if(entry.className.startsWith("[")) { return true; } return false; } private boolean isPure(MethodEntry entry, Stack callStack) { if (isCached(entry)) { return getCacheValue(entry); } else { boolean isPure = isPure0(entry, callStack); addCacheValue(entry, isPure); return isPure; } } private boolean checkAnyOverridingMethodImpure(MethodEntry entry, Stack callStack) { InheritanceTree inheritanceTree = DependencyAnalysis .getInheritanceTree(); String className = "" + entry.className; // while (className.contains("[L")) { // className = className.substring(2, className.length() - 1); // } if (!inheritanceTree.hasClass(className)) { logger.warn(className + " was not found in the inheritance tree. Using DEFAULT value for cheap-purity analysis"); return DEFAULT_PURITY_VALUE; } Set subclasses = inheritanceTree.getSubclasses(className); for (String subclassName : subclasses) { if (!entry.className.equals(subclassName)) { MethodEntry subclassEntry = new MethodEntry(subclassName, entry.methodName, entry.descriptor); if (!callStack.contains(subclassEntry) && methodEntries.contains(subclassEntry)) { Stack newStack = new Stack(); newStack.addAll(callStack); newStack.add(subclassEntry); if (!isPure(subclassEntry, newStack)) { return true; } } } } return false; } private boolean isJdkPureMethod(MethodEntry entry) { String paraz = entry.descriptor; Type[] parameters = org.objectweb.asm.Type.getArgumentTypes(paraz); String newParams = ""; if (parameters.length != 0) { for (Type i : parameters) { newParams = newParams + "," + i.getClassName(); } newParams = newParams.substring(1, newParams.length()); } String qualifiedName = entry.className + "." + entry.methodName + "(" + newParams + ")"; return (JdkPureMethodsList.instance.checkPurity(qualifiedName)); } private boolean checkAnyCallImpure(Set calls, MethodEntry entry, Stack callStack) { for (MethodEntry callMethodEntry : calls) { if (!callStack.contains(callMethodEntry)) { Stack copyOfStack = new Stack(); copyOfStack.addAll(callStack); copyOfStack.add(entry); if (!isPure(callMethodEntry, copyOfStack)) { return true; } } } return false; } /** * Returns if a Method is cheap-pure * @param method * @return true if the method is cheap-pure, otherwise false. */ public boolean isPure(java.lang.reflect.Method method) { // Using getName rather than getCanonicalName because that's what // the inheritancetree also uses String className = method.getDeclaringClass().getName(); if (MockList.isAMockClass(className)) { className = method.getDeclaringClass().getSuperclass().getName(); } String methodName = method.getName(); String descriptor = Type.getMethodDescriptor(method); MethodEntry entry = new MethodEntry(className, methodName, descriptor); boolean isPureValue = isPure(entry); return isPureValue; } private static class MethodEntry { private final String className; private final String methodName; private final String descriptor; public MethodEntry(String className, String methodName, String descriptor) { this.className = className; this.methodName = methodName; this.descriptor = descriptor; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + className.hashCode(); result = prime * result + descriptor.hashCode(); result = prime * result + methodName.hashCode(); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MethodEntry other = (MethodEntry) obj; return (className.equals(other.className) && methodName .equals(other.methodName)) && descriptor.equals(other.descriptor); } @Override public String toString() { return "MethodEntry [className=" + className + ", methodName=" + methodName + ", descriptor=" + String.valueOf(descriptor) + "]"; } } public void addMethod(String className, String methodName, String methodDescriptor) { MethodEntry entry = new MethodEntry(className, methodName, methodDescriptor); methodEntries.add(entry); } public void addUpdatesFieldMethod(String className, String methodName, String descriptor) { String classNameWithDots = className.replace('/', '.'); MethodEntry entry = new MethodEntry(classNameWithDots, methodName, descriptor); updateFieldMethodList.add(entry); } private final HashMap> staticCalls = new HashMap>(); private final HashMap> virtualCalls = new HashMap>(); private final HashMap> specialCalls = new HashMap>(); private final HashMap> interfaceCalls = new HashMap>(); public void addStaticCall(String sourceClassName, String sourceMethodName, String sourceDescriptor, String targetClassName, String targetMethodName, String targetDescriptor) { addCall(staticCalls, sourceClassName, sourceMethodName, sourceDescriptor, targetClassName, targetMethodName, targetDescriptor); } public void addVirtualCall(String sourceClassName, String sourceMethodName, String sourceDescriptor, String targetClassName, String targetMethodName, String targetDescriptor) { addCall(virtualCalls, sourceClassName, sourceMethodName, sourceDescriptor, targetClassName, targetMethodName, targetDescriptor); } public void addInterfaceCall(String sourceClassName, String sourceMethodName, String sourceDescriptor, String targetClassName, String targetMethodName, String targetDescriptor) { addCall(interfaceCalls, sourceClassName, sourceMethodName, sourceDescriptor, targetClassName, targetMethodName, targetDescriptor); } private static void addCall(HashMap> calls, String sourceClassName, String sourceMethodName, String sourceDescriptor, String targetClassName, String targetMethodName, String targetDescriptor) { MethodEntry sourceEntry = new MethodEntry(sourceClassName, sourceMethodName, sourceDescriptor); MethodEntry targetEntry = new MethodEntry(targetClassName, targetMethodName, targetDescriptor); if (!calls.containsKey(sourceEntry)) { calls.put(sourceEntry, new HashSet()); } calls.get(sourceEntry).add(targetEntry); } public void addSpecialCall(String sourceClassName, String sourceMethodName, String sourceDescriptor, String targetClassName, String targetMethodName, String targetDescriptor) { addCall(specialCalls, sourceClassName, sourceMethodName, sourceDescriptor, targetClassName, targetMethodName, targetDescriptor); } private final HashSet interfaceMethodEntries = new HashSet(); private final HashSet methodsWithBodies = new HashSet(); public void addInterfaceMethod(String className, String methodName, String methodDescriptor) { MethodEntry entry = new MethodEntry(className, methodName, methodDescriptor); interfaceMethodEntries.add(entry); } public void addMethodWithBody(String className, String methodName, String methodDescriptor) { MethodEntry entry = new MethodEntry(className, methodName, methodDescriptor); methodsWithBodies.add(entry); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy