/** * 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.testcarver.testcase; import gnu.trove.list.array.TIntArrayList; import gnu.trove.map.hash.TIntObjectHashMap; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import org.evosuite.TestGenerationContext; import org.evosuite.testcarver.capture.CaptureLog; import org.evosuite.testcarver.capture.CaptureUtil; import org.evosuite.testcase.AssignmentStatement; import org.evosuite.testcase.ConstructorStatement; import org.evosuite.testcase.DefaultTestCase; import org.evosuite.testcase.FieldReference; import org.evosuite.testcase.MethodStatement; import org.evosuite.testcase.NullStatement; import org.evosuite.testcase.PrimitiveStatement; import org.evosuite.testcase.TestCase; import org.evosuite.testcase.VariableReference; import org.evosuite.testcase.VariableReferenceImpl; import org.evosuite.utils.GenericConstructor; import org.evosuite.utils.GenericField; import org.evosuite.utils.GenericMethod; @Deprecated public class TestCaseCodeGenerator { private final CaptureLog log; //--- source generation public TestCaseCodeGenerator(final CaptureLog log) { if (log == null) { throw new NullPointerException(); } this.log = log.clone(); } // FIXME specifying classes here might not be needed anymore, if we only instrument observed classes... public TestCase generateCode(final Class<?>... observedClasses) { if (observedClasses == null || observedClasses.length == 0) { throw new IllegalArgumentException("No observed classes specified"); } //--- 1. step: extract class names final HashSet<String> observedClassNames = new HashSet<String>(); for (int i = 0; i < observedClasses.length; i++) { observedClassNames.add(observedClasses[i].getName()); } //--- 2. step: get all oids of the instances of the observed classes // NOTE: They are implicitly sorted by INIT_REC_NO because of the natural object creation order captured by the // instrumentation final TIntArrayList targetOIDs = new TIntArrayList(); final int numInfoRecs = this.log.oidClassNames.size(); for (int i = 0; i < numInfoRecs; i++) { if (observedClassNames.contains(this.log.oidClassNames.get(i))) { targetOIDs.add(this.log.oids.getQuick(i)); } } //--- 3. step: init compilation unit final TestCase testCase = new DefaultTestCase(); // no invocations on objects of observed classes -> return empty but compilable CompilationUnit if (targetOIDs.isEmpty()) { return testCase; } //--- 4. step: generating code starting with OID with lowest log rec no. final int numLogRecords = this.log.objectIds.size(); int currentOID = targetOIDs.getQuick(0); int captureId = -1; // TODO knowing last logRecNo for termination criterion belonging to an observed instance would prevent processing unnecessary statements for (int currentRecord = this.log.oidRecMapping.get(currentOID); currentRecord < numLogRecords; currentRecord++) { currentOID = this.log.objectIds.getQuick(currentRecord); if (targetOIDs.contains(currentOID)) { this.restorceCodeFromLastPosTo(currentOID, currentRecord, testCase); // forward to end of method call sequence captureId = this.log.captureIds.getQuick(currentRecord); while (!(this.log.objectIds.getQuick(currentRecord) == currentOID && this.log.captureIds.getQuick(currentRecord) == captureId && this.log.methodNames.get(currentRecord).equals(CaptureLog.END_CAPTURE_PSEUDO_METHOD))) { currentRecord++; } // each method call is considered as object state modification -> so save last object modification log.updateWhereObjectWasInitializedFirst(currentOID, currentRecord); } } return testCase; } private void restorceCodeFromLastPosTo(final int oid, final int end, TestCase testCase) { final int oidInfoRecNo = this.log.oidRecMapping.get(oid); // start from last OID modification point int currentRecord = log.getRecordIndexOfWhereObjectWasInitializedFirst(oid); if (currentRecord > 0) { // last modification of object happened here // -> we start looking for interesting records after retrieved record currentRecord++; } else { // object new instance statement // -> retrieved loc record no is included currentRecord = -currentRecord; } String methodName; int currentOID; int captureId; Object[] methodArgs; Integer methodArgOID; Integer returnValue; Object returnValueObj; for (; currentRecord <= end; currentRecord++) { currentOID = this.log.objectIds.getQuick(currentRecord); returnValueObj = this.log.returnValues.get(currentRecord); returnValue = returnValueObj.equals(CaptureLog.RETURN_TYPE_VOID) ? -1 : (Integer) returnValueObj; if (oid == currentOID || returnValue == oid) { methodName = this.log.methodNames.get(currentRecord); if (CaptureLog.PLAIN_INIT.equals(methodName)) // e.g. String var = "Hello World"; { this.createPlainInitStmt(currentRecord, testCase); // TODO: NOT NICE!!! DO IT ALSO FOR PLAIN AND NOT OBSERVED INIT to be consistent captureId = this.log.captureIds.getQuick(currentRecord); while (!(this.log.objectIds.getQuick(currentRecord) == currentOID && this.log.captureIds.getQuick(currentRecord) == captureId && this.log.methodNames.get(currentRecord).equals(CaptureLog.END_CAPTURE_PSEUDO_METHOD))) { currentRecord++; } } else if (CaptureLog.NOT_OBSERVED_INIT.equals(methodName)) // e.g. Person var = (Person) XSTREAM.fromXML("<xml/>"); { this.createUnobservedInitStmt(currentRecord, testCase); // TODO: NOT NICE!!! DO IT ALSO FOR PLAIN AND NOT OBSERVED INIT to be consistent captureId = this.log.captureIds.getQuick(currentRecord); while (!(this.log.objectIds.getQuick(currentRecord) == currentOID && this.log.captureIds.getQuick(currentRecord) == captureId && this.log.methodNames.get(currentRecord).equals(CaptureLog.END_CAPTURE_PSEUDO_METHOD))) { currentRecord++; } } else if (CaptureLog.PUTFIELD.equals(methodName) || CaptureLog.PUTSTATIC.equals(methodName) || // field write access such as p.id = id or Person.staticVar = "something" CaptureLog.GETFIELD.equals(methodName) || CaptureLog.GETSTATIC.equals(methodName)) // field READ access such as "int a = p.id" or "String var = Person.staticVar" { if (CaptureLog.PUTFIELD.equals(methodName) || CaptureLog.PUTSTATIC.equals(methodName)) { // a field assignment has always one argument methodArgs = this.log.params.get(currentRecord); methodArgOID = (Integer) methodArgs[0]; if (methodArgOID != null && methodArgOID != oid) { // create history of assigned value this.restorceCodeFromLastPosTo(methodArgOID, currentRecord, testCase); } this.createFieldWriteAccessStmt(currentRecord, testCase); } else { this.createFieldReadAccessStmt(currentRecord, testCase); } // TODO: NOT NICE!!! DO IT ALSO FOR PLAIN AND NOT OBSERVED INIT to be consistent captureId = this.log.captureIds.getQuick(currentRecord); while (!(this.log.objectIds.getQuick(currentRecord) == currentOID && this.log.captureIds.getQuick(currentRecord) == captureId && this.log.methodNames.get(currentRecord).equals(CaptureLog.END_CAPTURE_PSEUDO_METHOD))) { currentRecord++; } if (CaptureLog.GETFIELD.equals(methodName) || CaptureLog.GETSTATIC.equals(methodName)) { // GETFIELD and GETSTATIC should only happen, if we obtain an instance whose creation has not been observed log.updateWhereObjectWasInitializedFirst(currentOID, currentRecord); if (returnValue != -1) { log.updateWhereObjectWasInitializedFirst(returnValue, currentRecord); } } } else // var0.call(someArg) or Person var0 = new Person() { methodArgs = this.log.params.get(currentRecord); for (int i = 0; i < methodArgs.length; i++) { // there can only be OIDs or null methodArgOID = (Integer) methodArgs[i]; if (methodArgOID != null && methodArgOID != oid) { this.restorceCodeFromLastPosTo(methodArgOID, currentRecord, testCase); } } this.createMethodCallStmt(currentRecord, testCase); // forward to end of method call sequence captureId = this.log.captureIds.getQuick(currentRecord); while (!(this.log.objectIds.getQuick(currentRecord) == currentOID && this.log.captureIds.getQuick(currentRecord) == captureId && this.log.methodNames.get(currentRecord).equals(CaptureLog.END_CAPTURE_PSEUDO_METHOD))) { currentRecord++; } // each method call is considered as object state modification -> so save last object modification log.updateWhereObjectWasInitializedFirst(currentOID, currentRecord); if (returnValue != -1) { // if returnValue has not type VOID, mark current log record as record where the return value instance was created // --> if an object is created within an observed method, it would not be semantically correct // (and impossible to handle properly) to create an extra instance of the return value type outside this method log.updateWhereObjectWasInitializedFirst(returnValue, currentRecord); } // consider each passed argument as being modified at the end of the method call sequence for (int i = 0; i < methodArgs.length; i++) { // there can only be OIDs or null methodArgOID = (Integer) methodArgs[i]; if (methodArgOID != null && methodArgOID != oid) { log.updateWhereObjectWasInitializedFirst(methodArgOID, currentRecord); } } } } } } private void createMethodCallStmt(final int logRecNo, final TestCase testCase) { // assumption: all necessary statements are created and there is one variable for reach referenced object final int oid = this.log.objectIds.get(logRecNo); final Object[] methodArgs = this.log.params.get(logRecNo); final String methodName = this.log.methodNames.get(logRecNo); final String methodDesc = this.log.descList.get(logRecNo); final org.objectweb.asm.Type[] methodParamTypes = org.objectweb.asm.Type.getArgumentTypes(methodDesc); final Class<?>[] methodParamTypeClasses = new Class[methodParamTypes.length]; for (int i = 0; i < methodParamTypes.length; i++) { methodParamTypeClasses[i] = getClassFromType(methodParamTypes[i]); } final String typeName = this.log.oidClassNames.get(this.log.oidRecMapping.get(oid)); Class<?> type; try { type = getClassForName(typeName);// Class.forName(typeName, true, StaticTestCluster.classLoader); final ArrayList<VariableReference> args = new ArrayList<VariableReference>(); Integer argOID; // is either an oid or null for (int i = 0; i < methodArgs.length; i++) { argOID = (Integer) methodArgs[i]; if (argOID == null) { args.add(testCase.addStatement(new NullStatement(testCase, methodParamTypeClasses[i]))); } else { args.add(this.oidToVarRefMap.get(argOID)); } } if (CaptureLog.OBSERVED_INIT.equals(methodName)) { // Person var0 = new Person(); final ConstructorStatement constStmt = new ConstructorStatement( testCase, new GenericConstructor( type.getDeclaredConstructor(methodParamTypeClasses), type), args); this.oidToVarRefMap.put(oid, testCase.addStatement(constStmt)); } else //------------------ handling for ordinary method calls e.g. var1 = var0.doSth(); { final Object returnValue = this.log.returnValues.get(logRecNo); if (CaptureLog.RETURN_TYPE_VOID.equals(returnValue)) { final MethodStatement m = new MethodStatement( testCase, new GenericMethod( this.getDeclaredMethod(type, methodName, methodParamTypeClasses), type.getMethod(methodName, methodParamTypeClasses).getReturnType()), this.oidToVarRefMap.get(oid), args); testCase.addStatement(m); } else { final org.objectweb.asm.Type returnType = org.objectweb.asm.Type.getReturnType(methodDesc); // Person var0 = var.getPerson(); final MethodStatement m = new MethodStatement(testCase, new GenericMethod( this.getDeclaredMethod(type, methodName, methodParamTypeClasses), getClassFromType(returnType)), this.oidToVarRefMap.get(oid), args); final Integer returnValueOID = (Integer) returnValue; this.oidToVarRefMap.put(returnValueOID, testCase.addStatement(m)); } } } catch (final Exception e) { throw new RuntimeException(e); } } private final TIntObjectHashMap<VariableReference> oidToVarRefMap = new TIntObjectHashMap<VariableReference>(); @SuppressWarnings({ "unchecked", "rawtypes" }) private void createPlainInitStmt(final int logRecNo, final TestCase testCase) { // NOTE: PLAIN INIT: has always one non-null param // TODO: use primitives final int oid = this.log.objectIds.get(logRecNo); if (this.oidToVarRefMap.containsKey(oid)) { // TODO this might happen because of Integer.valueOf(), for example. . Is this approach ok? return; } final String type = this.log.oidClassNames.get(this.log.oidRecMapping.get(oid)); final Object value = this.log.params.get(logRecNo)[0]; if (value instanceof Class) // Class is a plain type according to log { // FIXME this code needs to get working // try // { // final VariableReference varRef = new VariableReferenceImpl(testCase, Class.class); // final VariableReference valueRef = new VariableReferenceImpl(testCase, getClassForName(type)); // // final AssignmentStatement assign = new AssignmentStatement(testCase, varRef, valueRef); // this.oidToVarRefMap.put(oid, testCase.addStatement(assign)); // } // catch(final Exception e) // { // throw new RuntimeException(e); // } } else { final PrimitiveStatement primitiveValue = PrimitiveStatement.getPrimitiveStatement(testCase, getClassForName(type)); primitiveValue.setValue(value); final VariableReference varRef = testCase.addStatement(primitiveValue); this.oidToVarRefMap.put(oid, varRef); } } private VariableReference xStreamRef; @SuppressWarnings({ "unchecked", "rawtypes" }) private void createUnobservedInitStmt(final int logRecNo, final TestCase testCase) { // NOTE: PLAIN INIT: has always one non-null param // TODO: use primitives final int oid = this.log.objectIds.get(logRecNo); final String type = this.log.oidClassNames.get(this.log.oidRecMapping.get(oid)); try { final Class<?> xStreamType = getClassForName("com.thoughtworks.xstream.XStream");//Class.forName("com.thoughtworks.xstream.XStream", true, StaticTestCluster.classLoader); final Class<?> typeClass = getClassForName(type);//Class.forName(type, true, StaticTestCluster.classLoader); final Object value = this.log.params.get(logRecNo)[0]; if (xStreamRef == null) { final ConstructorStatement constr = new ConstructorStatement( testCase, new GenericConstructor( xStreamType.getConstructor(new Class<?>[0]), xStreamType), Collections.EMPTY_LIST); xStreamRef = testCase.addStatement(constr); } final Class<?> stringType = getClassForName("java.lang.String");//Class.forName("java.lang.String", true, StaticTestCluster.classLoader); final PrimitiveStatement stringRep = PrimitiveStatement.getPrimitiveStatement(testCase, stringType); stringRep.setValue(value); final VariableReference stringRepRef = testCase.addStatement(stringRep); final MethodStatement m = new MethodStatement(testCase, new GenericMethod( xStreamType.getMethod("fromXML", stringType), typeClass), xStreamRef, Arrays.asList(stringRepRef)); this.oidToVarRefMap.put(oid, testCase.addStatement(m)); } catch (final Exception e) { throw new RuntimeException(e); } } private void createFieldWriteAccessStmt(final int logRecNo, final TestCase testCase) { // assumption: all necessary statements are created and there is one variable for reach referenced object final Object[] methodArgs = this.log.params.get(logRecNo); final int oid = this.log.objectIds.get(logRecNo); final int captureId = this.log.captureIds.get(logRecNo); final String fieldName = this.log.namesOfAccessedFields.get(captureId); final String typeName = this.log.oidClassNames.get(this.log.oidRecMapping.get(oid)); try { final Class<?> type = getClassForName(typeName); final String fieldDesc = this.log.descList.get(logRecNo); final Class<?> fieldType = CaptureUtil.getClassFromDesc(fieldDesc); final FieldReference targetFieldRef = new FieldReference(testCase, new GenericField(this.getDeclaredField(type, fieldName), type)); final AssignmentStatement assignment; final Integer arg = (Integer) methodArgs[0]; if (arg == null) { final NullStatement nullStmt = new NullStatement(testCase, fieldType); final VariableReference nullReference = testCase.addStatement(nullStmt); assignment = new AssignmentStatement(testCase, targetFieldRef, nullReference); } else { assignment = new AssignmentStatement(testCase, targetFieldRef, this.oidToVarRefMap.get(arg)); } final VariableReference varRef = testCase.addStatement(assignment); if (arg != null) { this.oidToVarRefMap.put(arg, varRef); } } catch (final Exception e) { throw new RuntimeException(e); } } private void createFieldReadAccessStmt(final int logRecNo, final TestCase testCase) { // assumption: all necessary statements are created and there is one variable for reach referenced object final int oid = this.log.objectIds.get(logRecNo); final int captureId = this.log.captureIds.get(logRecNo); final Object returnValue = this.log.returnValues.get(logRecNo); if (!CaptureLog.RETURN_TYPE_VOID.equals(returnValue)) // TODO necessary? { Integer returnValueOID = (Integer) returnValue; final String descriptor = this.log.descList.get(logRecNo); final org.objectweb.asm.Type fieldTypeType = org.objectweb.asm.Type.getType(descriptor); final String typeName = this.log.oidClassNames.get(this.log.oidRecMapping.get(oid)); final String fieldName = this.log.namesOfAccessedFields.get(captureId); try { final Class<?> fieldType = getClassFromType(fieldTypeType); //Class.forName(fieldTypeName, true, StaticTestCluster.classLoader); final Class<?> type = getClassForName(typeName);// Class.forName(typeName, true, StaticTestCluster.classLoader); final FieldReference valueRef = new FieldReference(testCase, new GenericField(type.getField(fieldName), type)); final VariableReference targetVar = new VariableReferenceImpl(testCase, fieldType); final AssignmentStatement assignment = new AssignmentStatement(testCase, targetVar, valueRef); VariableReference varRef = testCase.addStatement(assignment); this.oidToVarRefMap.put(returnValueOID, varRef); } catch (final Exception e) { throw new RuntimeException(e); } } } private final Class<?> getClassFromType(final org.objectweb.asm.Type type) { if (type.equals(org.objectweb.asm.Type.BOOLEAN_TYPE)) { return Boolean.TYPE; } else if (type.equals(org.objectweb.asm.Type.BYTE_TYPE)) { return Byte.TYPE; } else if (type.equals(org.objectweb.asm.Type.CHAR_TYPE)) { return Character.TYPE; } else if (type.equals(org.objectweb.asm.Type.DOUBLE_TYPE)) { return Double.TYPE; } else if (type.equals(org.objectweb.asm.Type.FLOAT_TYPE)) { return Float.TYPE; } else if (type.equals(org.objectweb.asm.Type.INT_TYPE)) { return Integer.TYPE; } else if (type.equals(org.objectweb.asm.Type.LONG_TYPE)) { return Long.TYPE; } else if (type.equals(org.objectweb.asm.Type.SHORT_TYPE)) { return Short.TYPE; } else if (type.getSort() == org.objectweb.asm.Type.ARRAY) { final org.objectweb.asm.Type elementType = type.getElementType(); if (elementType.equals(org.objectweb.asm.Type.BOOLEAN_TYPE)) { return boolean[].class; } else if (elementType.equals(org.objectweb.asm.Type.BYTE_TYPE)) { return byte[].class; } else if (elementType.equals(org.objectweb.asm.Type.CHAR_TYPE)) { return char[].class; } else if (elementType.equals(org.objectweb.asm.Type.DOUBLE_TYPE)) { return double[].class; } else if (elementType.equals(org.objectweb.asm.Type.FLOAT_TYPE)) { return float[].class; } else if (elementType.equals(org.objectweb.asm.Type.INT_TYPE)) { return int[].class; } else if (elementType.equals(org.objectweb.asm.Type.LONG_TYPE)) { return long[].class; } else if (elementType.equals(org.objectweb.asm.Type.SHORT_TYPE)) { return short[].class; } } try { return Class.forName(type.getClassName(), true, TestGenerationContext.getClassLoader()); } catch (final ClassNotFoundException e) { throw new RuntimeException(e); } } private final Class<?> getClassForName(final String type) { try { if (type.equals("Boolean")) { return Boolean.TYPE; } else if (type.equals("Byte")) { return Byte.TYPE; } else if (type.equals("Character")) { return Character.TYPE; } else if (type.equals("Double")) { return Double.TYPE; } else if (type.equals("Float")) { return Float.TYPE; } else if (type.equals("Integer")) { return Integer.TYPE; } else if (type.equals("Long")) { return Long.TYPE; } else if (type.equals("Short")) { return Short.TYPE; } else if (type.equals("String")) { return Class.forName("java.lang.String", true, TestGenerationContext.getClassLoader()); } return Class.forName(type, true, TestGenerationContext.getClassLoader()); } catch (final ClassNotFoundException e) { throw new RuntimeException(e); } } private Field getDeclaredField(final Class<?> clazz, final String fieldName) throws NoSuchFieldException { if (clazz == null || Object.class.equals(clazz)) { throw new NoSuchFieldException(fieldName); } try { final Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); return field; } catch (final NoSuchFieldException e) { return getDeclaredField(clazz.getSuperclass(), fieldName); } } private Method getDeclaredMethod(final Class<?> clazz, final String methodName, Class<?>[] paramTypes) throws NoSuchFieldException { if (clazz == null || Object.class.equals(clazz)) { throw new NoSuchFieldException(methodName + "(" + Arrays.toString(paramTypes) + ")"); } try { final Method m = clazz.getDeclaredMethod(methodName, paramTypes); m.setAccessible(true); return m; } catch (final NoSuchMethodException e) { return getDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes); } } }