/** * 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.instrumentation; import java.io.PrintWriter; import java.io.StringWriter; import org.evosuite.PackageInfo; import org.evosuite.Properties; import org.evosuite.assertion.CheapPurityAnalyzer; import org.evosuite.classpath.ResourceList; import org.evosuite.graphs.cfg.CFGClassAdapter; import org.evosuite.instrumentation.error.ErrorConditionClassAdapter; import org.evosuite.instrumentation.testability.BooleanTestabilityTransformation; import org.evosuite.instrumentation.testability.ComparisonTransformation; import org.evosuite.instrumentation.testability.ContainerTransformation; import org.evosuite.instrumentation.testability.StringTransformation; import org.evosuite.junit.writer.TestSuiteWriterUtils; import org.evosuite.runtime.instrumentation.*; import org.evosuite.seeding.PrimitiveClassAdapter; import org.evosuite.setup.DependencyAnalysis; import org.evosuite.setup.TestCluster; import org.evosuite.testcarver.instrument.Instrumenter; import org.evosuite.testcarver.instrument.TransformerUtil; import org.evosuite.runtime.util.ComputeClassWriter; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.util.TraceClassVisitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The bytecode transformer - transforms bytecode depending on package and * whether it is the class under test * * @author Gordon Fraser */ public class BytecodeInstrumentation { private static Logger logger = LoggerFactory.getLogger(BytecodeInstrumentation.class); private final Instrumenter testCarvingInstrumenter; /** * <p> * Constructor for BytecodeInstrumentation. * </p> */ public BytecodeInstrumentation() { this.testCarvingInstrumenter = new Instrumenter(); } private static String[] getEvoSuitePackages() { return new String[] { PackageInfo.getEvoSuitePackage(), "org.exsyst", "de.unisb.cs.st.testcarver", "de.unisb.cs.st.evosuite", "testing.generation.evosuite", "de.unisl.cs.st.bugex" }; } /** * Check if we can instrument the given class * * @param className * a {@link java.lang.String} object. * @return a boolean. */ public static boolean checkIfCanInstrument(String className) { return RuntimeInstrumentation.checkIfCanInstrument(className); } /** * Check if we the class belongs to an EvoSuite package * * @param className * a {@link java.lang.String} object. * @return a boolean. */ public static boolean checkIfEvoSuitePackage(String className) { for (String s : BytecodeInstrumentation.getEvoSuitePackages()) { if (className.startsWith(s)) { return true; } } return false; } /** * <p> * shouldTransform * </p> * * @param className * a {@link java.lang.String} object. * @return a boolean. */ public boolean shouldTransform(String className) { if (!Properties.TT) return false; switch (Properties.TT_SCOPE) { case ALL: logger.info("Allowing transformation of " + className); 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; } logger.info("Preventing transformation of " + className); return false; } private boolean isTargetClassName(String className) { // TODO: Need to replace this in the long term return TestCluster.isTargetClassName(className); } /** * <p> * transformBytes * </p> * * @param className * a {@link java.lang.String} object. * @param reader * a {@link org.objectweb.asm.ClassReader} object. * @return an array of byte. */ public byte[] transformBytes(ClassLoader classLoader, String className, ClassReader reader) { int readFlags = ClassReader.SKIP_FRAMES; if (Properties.INSTRUMENTATION_SKIP_DEBUG) readFlags |= ClassReader.SKIP_DEBUG; String classNameWithDots = ResourceList.getClassNameFromResourcePath(className); if (!checkIfCanInstrument(classNameWithDots)) { throw new RuntimeException("Should not transform a shared class (" + classNameWithDots + ")! Load by parent (JVM) classloader."); } TransformationStatistics.reset(); /* * To use COMPUTE_FRAMES we need to remove JSR commands. Therefore, we * have a JSRInlinerAdapter in NonTargetClassAdapter as well as * CFGAdapter. */ int asmFlags = ClassWriter.COMPUTE_FRAMES; ClassWriter writer = new ComputeClassWriter(asmFlags); ClassVisitor cv = writer; if (logger.isDebugEnabled()) { cv = new TraceClassVisitor(cv, new PrintWriter(System.err)); } if (Properties.RESET_STATIC_FIELDS) { cv = new StaticAccessClassAdapter(cv, className); } if (Properties.PURE_INSPECTORS) { CheapPurityAnalyzer purityAnalyzer = CheapPurityAnalyzer.getInstance(); cv = new PurityAnalysisClassVisitor(cv, className, purityAnalyzer); } if (Properties.MAX_LOOP_ITERATIONS >= 0) { cv = new LoopCounterClassAdapter(cv); } // Apply transformations to class under test and its owned classes if (DependencyAnalysis.shouldAnalyze(classNameWithDots)) { logger.debug("Applying target transformation to class " + classNameWithDots); if (!Properties.TEST_CARVING && Properties.MAKE_ACCESSIBLE) { cv = new AccessibleClassAdapter(cv, className); } cv = new RemoveFinalClassAdapter(cv); cv = new ExecutionPathClassAdapter(cv, className); cv = new CFGClassAdapter(classLoader, cv, className); if (Properties.EXCEPTION_BRANCHES) { cv = new ExceptionTransformationClassAdapter(cv, className); } if (Properties.ERROR_BRANCHES) { cv = new ErrorConditionClassAdapter(cv, className); } } else { logger.debug("Not applying target transformation"); cv = new NonTargetClassAdapter(cv, className); if (Properties.MAKE_ACCESSIBLE) { cv = new AccessibleClassAdapter(cv, className); } // If we are doing testability transformation on all classes we need // to create the CFG first if (Properties.TT && classNameWithDots.startsWith(Properties.CLASS_PREFIX)) { cv = new CFGClassAdapter(classLoader, cv, className); } } // Collect constant values for the value pool cv = new PrimitiveClassAdapter(cv, className); if (Properties.RESET_STATIC_FIELDS) { cv = handleStaticReset(className, cv); } // Mock instrumentation (eg File and TCP). if (TestSuiteWriterUtils.needToUseAgent()) { cv = new MethodCallReplacementClassAdapter(cv, className); } // Testability Transformations if (classNameWithDots.startsWith(Properties.PROJECT_PREFIX) || (!Properties.TARGET_CLASS_PREFIX.isEmpty() && classNameWithDots.startsWith(Properties.TARGET_CLASS_PREFIX)) || shouldTransform(classNameWithDots)) { ClassNode cn = new AnnotatedClassNode(); reader.accept(cn, readFlags); logger.info("Starting transformation of " + className); if (Properties.STRING_REPLACEMENT) { StringTransformation st = new StringTransformation(cn); if (isTargetClassName(classNameWithDots) || shouldTransform(classNameWithDots)) cn = st.transform(); } ComparisonTransformation cmp = new ComparisonTransformation(cn); if (isTargetClassName(classNameWithDots) || shouldTransform(classNameWithDots)) { cn = cmp.transform(); ContainerTransformation ct = new ContainerTransformation(cn); cn = ct.transform(); } if (shouldTransform(classNameWithDots)) { logger.info("Testability Transforming " + className); BooleanTestabilityTransformation tt = new BooleanTestabilityTransformation(cn, classLoader); try { cn = tt.transform(); } catch (Throwable t) { throw new Error(t); } logger.info("Testability Transformation done: " + className); } // ----- cn.accept(cv); if (Properties.TEST_CARVING && TransformerUtil.isClassConsideredForInstrumentation(className)) { return handleCarving(className, writer); } } else { reader.accept(cv, readFlags); } return writer.toByteArray(); } private byte[] handleCarving(String className, ClassWriter writer) { ClassReader cr = new ClassReader(writer.toByteArray()); ClassNode cn2 = new ClassNode(); cr.accept(cn2, ClassReader.EXPAND_FRAMES); this.testCarvingInstrumenter.transformClassNode(cn2, className); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); cn2.accept(cw); if (logger.isDebugEnabled()) { final StringWriter sw = new StringWriter(); cn2.accept(new TraceClassVisitor(new PrintWriter(sw))); logger.debug("test carving instrumentation result:\n{}", sw); } return cw.toByteArray(); } /** * Adds the instrumentation to deal with re-iniatilizing classes: adding * __STATIC_RESET() methods, inserting callbacks for PUTSTATIC and GETSTATIC * instructions * * @param className * @param cv * @return */ private static ClassVisitor handleStaticReset(String className, ClassVisitor cv) { // Create a __STATIC_RESET() cloning the original <clinit> method or // create one by default final CreateClassResetClassAdapter resetClassAdapter; if (Properties.RESET_STATIC_FINAL_FIELDS) { resetClassAdapter= new CreateClassResetClassAdapter(cv, className, true); } else { resetClassAdapter= new CreateClassResetClassAdapter(cv, className, false); } cv = resetClassAdapter; // Adds a callback before leaving the <clinit> method EndOfClassInitializerVisitor exitClassInitAdapter = new EndOfClassInitializerVisitor(cv, className); cv = exitClassInitAdapter; return cv; } }