org.evosuite.testcarver.codegen.CaptureLogAnalyzer 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.testcarver.codegen;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.evosuite.TimeController;
import org.evosuite.classpath.ResourceList;
import org.evosuite.testcarver.capture.CaptureLog;
import org.evosuite.utils.CollectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class CaptureLogAnalyzer implements ICaptureLogAnalyzer
{
private static Logger logger = LoggerFactory.getLogger(CaptureLogAnalyzer.class);
@SuppressWarnings("rawtypes")
@Override
public void analyze(final CaptureLog originalLog, final ICodeGenerator generator, final Class>... observedClasses)
{
this.analyze(originalLog, generator, new HashSet>(), observedClasses);
}
@SuppressWarnings("rawtypes")
public void analyze(final CaptureLog originalLog, final ICodeGenerator generator, final Set> blackList, final Class>... observedClasses)
{
if(originalLog == null)
throw new IllegalArgumentException("captured log must not be null");
if(generator == null)
throw new IllegalArgumentException("code generator must not be null");
if(blackList == null)
throw new IllegalArgumentException("set containing black listed classes must not be null");
if(observedClasses == null)
throw new IllegalArgumentException("array of observed classes must not be null");
if(observedClasses.length == 0)
throw new IllegalArgumentException("array of observed classes must not be empty");
final CaptureLog log = originalLog.clone();
final HashSet observedClassNames = extractObservedClassNames(observedClasses);
CaptureLogAnalyzerException.check(! CollectionUtil.isNullOrEmpty(observedClassNames), "could not extract class names for ", Arrays.toString(observedClasses));
final List targetOIDs = log.getTargetOIDs(observedClassNames);
if(targetOIDs.isEmpty())
{
logger.info("could not find any oids for {} -> {} ==> no code is generated\n", observedClassNames, Arrays.toString(observedClasses));
return;
} else {
logger.debug("Target oids: {}", targetOIDs);
}
final int[] oidExchange = analyzeLog(generator, blackList, log, targetOIDs);
logger.debug("Going to postprocess stage");
postProcessLog(originalLog, generator, blackList, log, oidExchange, observedClasses);
}
private void postProcessLog(final CaptureLog originalLog,
final ICodeGenerator> generator, final Set> blackList,
CaptureLog log, int[] oidExchange,
final Class>... observedClasses) throws RuntimeException {
if(oidExchange == null){
generator.after(log);
} else {
try
{
final Class> origClass = this.getClassFromOID(log, oidExchange[0]);
final Class> destClass = this.getClassFromOID(log, oidExchange[1]);
for(int i = 0; i < observedClasses.length; i++)
{
if(origClass.equals(observedClasses[i]))
{
observedClasses[i] = destClass;
}
}
generator.clear();
this.analyze(originalLog, generator, blackList, observedClasses);
}
catch(final Exception e)
{
throw new RuntimeException(e);
}
}
}
private int[] analyzeLog(final ICodeGenerator> generator,
final Set> blackList, CaptureLog log,
List targetOIDs) {
//--- 3. step: analyze log
generator.before(log);
final int numLogRecords = log.objectIds.size();
CaptureLogAnalyzerException.check(numLogRecords > 0, "list of captured object ids is empty for log %s", log);
int currentOID = targetOIDs.get(0);
int[] oidExchange = null;
// TODO knowing last logRecNo for termination criterion belonging to an observed instance would prevent processing unnecessary statements
for(int currentRecord = Math.abs(log.getRecordIndexOfWhereObjectWasInitializedFirst(currentOID)); currentRecord < numLogRecords; currentRecord++)
//for(int currentRecord = log.getRecordIndex(currentOID); currentRecord < numLogRecords; currentRecord++)
{
currentOID = log.objectIds.get(currentRecord);
logger.debug("Current record {}, current oid {} type {}", currentRecord, currentOID, log.getTypeName(currentOID));
if(generator.isMaximumLengthReached()) {
logger.debug("Max length reached, stopping carving");
break;
}
if( targetOIDs.contains(currentOID) && ! blackList.contains(getClassFromOID(log, currentOID)))
{
logger.debug("Analyzing record in position {}", currentRecord);
try {
oidExchange = this.restoreCodeFromLastPosTo(log, generator, currentOID, currentRecord + 1, blackList);
} catch(Throwable t) {
logger.debug("Error: "+t);
for(StackTraceElement elem : t.getStackTrace()) {
logger.debug(elem.toString());
}
break;
}
if(oidExchange != null){
logger.debug("oidExchange is not null");
break;
}
// forward to end of method call sequence
currentRecord = findEndOfMethod(log, currentRecord, currentOID);
logger.debug("Current record: {}", currentRecord);
// each method call is considered as object state modification -> so save last object modification
/*
* FIXME: a log object is got as input to be analyzed, but then in the analyzer there are side effects on it...
* need re-factoring
*/
log.updateWhereObjectWasInitializedFirst(currentOID, currentRecord);
logger.debug("Log updated");
} else {
logger.debug("Skipping record in position {}",currentRecord);
}
}
return oidExchange;
}
private HashSet extractObservedClassNames(
final Class>... observedClasses) {
//--- 1. step: extract class names
final HashSet observedClassNames = new HashSet();
for(int i = 0; i < observedClasses.length; i++){
observedClassNames.add(observedClasses[i].getName());
}
return observedClassNames;
}
private Class> getClassFromOID(final CaptureLog log, final int oid) {
try {
final String typeName = log.getTypeName(oid);
return this.getClassForName(typeName);
} catch(final Exception e) {
throw new RuntimeException(e);
}
}
private final Class> getClassForName(String type)
{
try
{
if( type.equals("boolean"))
{
return Boolean.TYPE;
}
else if(type.equals("byte"))
{
return Byte.TYPE;
}
else if( type.equals("char"))
{
return Character.TYPE;
}
else if( type.equals("double"))
{
return Double.TYPE;
}
else if(type.equals("float"))
{
return Float.TYPE;
}
else if(type.equals("int"))
{
return Integer.TYPE;
}
else if( type.equals("long"))
{
return Long.TYPE;
}
else if(type.equals("short"))
{
return Short.TYPE;
}
else if(type.equals("String") ||type.equals("Boolean") || type.equals("Short") ||type.equals("Long") ||
type.equals("Integer") || type.equals("Float") || type.equals("Double") ||type.equals("Byte") ||
type.equals("Character") )
{
return Class.forName("java.lang." + type);
}
else if(type.startsWith("$Proxy")) // FIXME is this approach correct?...
{
return Proxy.class;
}
if(type.endsWith("[]"))
{
type = type.replace("[]", "");
return Class.forName("[L" + type + ";");
}
else
{
return Class.forName(ResourceList.getClassNameFromResourcePath(type));
}
}
catch (final ClassNotFoundException e)
{
CaptureLogAnalyzerException.propagateError(e, "an error occurred while resolving class for type %s", type);
return null; // just to satisfy compiler
}
}
/**
*
* @param log
* @param currentRecord
* @return -1, if there is no caller (very first method call),
* caller oid otherwise
*/
private int findCaller(final CaptureLog log, final int currentRecord)
{
logger.debug("Looking for caller of {}", currentRecord);
final int numRecords = log.objectIds.size();
logger.debug("numRecords = {}", numRecords);
//--- look for the end of the calling method
int record = currentRecord;
logger.debug("Starting with {}", record);
do
{
record = this.findEndOfMethod(log, record, log.objectIds.get(record));
record++;
logger.debug("Now is {}", record);
}
while( record < numRecords &&
! log.methodNames.get(record).equals(CaptureLog.END_CAPTURE_PSEUDO_METHOD)); // is not the end of the calling method
logger.debug("records = {}", record);
if(record >= numRecords)
{
// did not find any caller -> must be very first method call
logger.info("[currentRecord={}] - could not find caller for currentRecord -> must be very first method call", currentRecord);
return -1;
}
else
{
logger.debug("Found caller {}: {}", record, log.objectIds.size());
// found caller
return log.objectIds.get(record);
}
}
private void updateInitRec(final CaptureLog log, final int currentOID, final int currentRecord){
if(Math.abs(currentRecord) > Math.abs(log.getRecordIndexOfWhereObjectWasInitializedFirst(currentOID))){
log.updateWhereObjectWasInitializedFirst(currentOID, currentRecord);
}
}
private int findEndOfMethod(final CaptureLog log, final int currentRecord, final int currentOID)
{
// final int numRecords = log.objectIds.size();
//
// int record = currentRecord;
//
// final int captureId = log.captureIds.get(currentRecord);
// while( record < numRecords &&
// ! ( log.objectIds.get(record) == currentOID &&
// log.captureIds.get(record) == captureId &&
// log.methodNames.get(record).equals(CaptureLog.END_CAPTURE_PSEUDO_METHOD)))
// {
// record++;
// }
//
// return record;
int record = currentRecord;
final int captureId = log.captureIds.get(record);
logger.debug("captureId {}, record {}", captureId, record);
int nestedCalls = 0;
while(true){
if(log.captureIds.size() <= record || log.objectIds.size() <= record) {
logger.debug("Screw this: {}, {}, {}", log.captureIds.size(), log.objectIds.size(), record);
break;
}
logger.debug("Current record: {}: {} <-> {}, {} <-> {}", record, captureId, log.captureIds.get(record), currentOID, log.objectIds.get(record));
if(log.captureIds.get(record) == captureId &&
log.objectIds.get(record) == currentOID){
logger.debug(log.methodNames.get(record));
if(log.methodNames.get(record).equals(CaptureLog.END_CAPTURE_PSEUDO_METHOD)){
nestedCalls--;
if(nestedCalls == 0)
{
break;
}
} else{
nestedCalls++;
}
}
record++;
}
return record;
}
@SuppressWarnings({ "rawtypes" })
private void restoreArgs(final Object[] args, final int currentRecord, final CaptureLog log, final ICodeGenerator generator, final Set> blackList)
{
Integer oid;
for(int i = 0; i < args.length; i++)
{
// there can only be OIDs or null
oid = (Integer) args[i];
if(oid != null)
{
this.restoreCodeFromLastPosTo(log, generator, oid, currentRecord, blackList);
}
}
}
@SuppressWarnings({ "rawtypes" })
private int[] restoreCodeFromLastPosTo(final CaptureLog log, final ICodeGenerator generator,final int oid, final int end, final Set> blackList){
logger.debug("Restoring code from last pos");
// start from last OID modification point
int currentRecord = log.getRecordIndexOfWhereObjectWasInitializedFirst(oid);
logger.debug("Current record: "+currentRecord);
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;
}
logger.debug("-> Current record {}, end {} ", currentRecord, end);
String methodName;
int currentOID;
Object[] methodArgs;
Integer methodArgOID;
Integer returnValue;
Object returnValueObj;
int[] exchange;
for(; currentRecord < end; currentRecord++) {
if(generator.isMaximumLengthReached())
break;
if(!TimeController.getInstance().isThereStillTimeInThisPhase())
break;
// for(; currentRecord <= end; currentRecord++) {
currentOID = log.objectIds.get(currentRecord);
returnValueObj = log.returnValues.get(currentRecord);
returnValue = returnValueObj.equals(CaptureLog.RETURN_TYPE_VOID) ? -1 : (Integer) returnValueObj;
logger.debug("Checking: "+currentRecord+": "+log.getTypeName(currentOID) +" to generate "+log.getTypeName(oid));
if(oid == currentOID || returnValue == oid) {
logger.debug("Current record is currentOID {} or returnvalue", currentOID);
if(oid != currentOID)
{
logger.debug("No, is not currentOID");
// currentOID differs to the targetOID. this happens if the targetOID appears the first time as return value
// -> so we have to make sure that currentOID is restored till this position in order to deliver the correct
// target instance
exchange = this.restoreCodeFromLastPosTo(log, generator, currentOID, currentRecord, blackList);
if(exchange != null)
{
return exchange;
}
}
methodName = log.methodNames.get(currentRecord);
if(CaptureLog.PLAIN_INIT.equals(methodName)) {
logger.debug("Plain init");
currentRecord = handlePlainInit(log, generator, currentRecord, currentOID);
}
else if(CaptureLog.COLLECTION_INIT.equals(methodName)){
logger.debug("Collection init");
currentRecord = handleCollectionInit(log, generator, blackList, currentRecord, currentOID);
}
else if(CaptureLog.MAP_INIT.equals(methodName)){
logger.debug("Map init");
currentRecord = handleMapInit(log, generator, blackList, currentRecord, currentOID);
}
else if(CaptureLog.ARRAY_INIT.equals(methodName)){
logger.debug("Array init");
currentRecord = handleArrayInit(log, generator, blackList, currentRecord, currentOID);
}
else if(CaptureLog.NOT_OBSERVED_INIT.equals(methodName)) {
logger.debug("Unobserved init");
// e.g. Person var = (Person) XSTREAM.fromXML(" ");
final int dependencyOID = log.getDependencyOID(oid);
if(dependencyOID != CaptureLog.NO_DEPENDENCY)
{
exchange = this.restoreCodeFromLastPosTo(log, generator, dependencyOID, currentRecord, blackList);
if(exchange != null)
{
return exchange;
}
}
generator.createUnobservedInitStmt(log, currentRecord);
currentRecord = findEndOfMethod(log, currentRecord, currentOID);
}
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"
{
logger.debug("Field access");
final int dependencyOID = log.getDependencyOID(oid);
if(dependencyOID != CaptureLog.NO_DEPENDENCY)
{
exchange = this.restoreCodeFromLastPosTo(log, generator, dependencyOID, currentRecord, blackList);
if(exchange != null)
{
return exchange;
}
}
if(CaptureLog.PUTFIELD.equals(methodName) || CaptureLog.PUTSTATIC.equals(methodName))
{
// a field assignment has always one argument
methodArgs = log.params.get(currentRecord);
methodArgOID = (Integer) methodArgs[0];
if(methodArgOID != null && methodArgOID != oid)
{
// create history of assigned value
exchange = this.restoreCodeFromLastPosTo(log, generator, methodArgOID, currentRecord, blackList);
if(exchange != null) {
return exchange;
}
}
generator.createFieldWriteAccessStmt(log, currentRecord);
} else {
generator.createFieldReadAccessStmt(log, currentRecord);
}
currentRecord = findEndOfMethod(log, currentRecord, currentOID);
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
this.updateInitRec(log, currentOID, currentRecord);
if(returnValue != -1) {
this.updateInitRec(log, returnValue, currentRecord);
}
}
} else {
//the rest
logger.debug("The rest: "+methodName);
// var0.call(someArg) or Person var0 = new Person()
final int dependencyOID = log.getDependencyOID(oid);
logger.debug("Dependency oid for {} is {} ", oid, dependencyOID);
if(dependencyOID != CaptureLog.NO_DEPENDENCY)
{
logger.debug("Dependencies");
exchange = this.restoreCodeFromLastPosTo(log, generator, dependencyOID, currentRecord, blackList);
if(exchange != null)
{
return exchange;
}
}
int callerOID = this.findCaller(log, currentRecord);
logger.debug("Caller oid {} ", callerOID);
methodArgs = log.params.get(currentRecord);
logger.debug("Getting "+methodArgs.length+" method args: {}", methodArgs);
for(int i = 0; i < methodArgs.length; i++)
{
// there can only be OIDs or null
methodArgOID = (Integer) methodArgs[i];
logger.debug("Argument {}", methodArgOID);
//====================================================
// if method argument is equal to caller oid look for alternative instance which is restorable
if(methodArgOID != null && (methodArgOID == callerOID )) {
int r = currentRecord;
while(isBlackListed(callerOID, blackList, log)){
callerOID = this.findCaller(log, ++r);
}
// replace class to which the current oid belongs to with callerOID
blackList.add(this.getClassFromOID(log, oid));
return new int[]{oid, callerOID};
}
else if(methodArgOID != null && isBlackListed(methodArgOID, blackList, log)){
logger.debug("arg in blacklist >>>> {}", blackList.contains(this.getClassFromOID(log, methodArgOID)));
return getExchange(log, currentRecord, oid, blackList); //new int[]{oid, callerOID};
}
//====================================================
if(methodArgOID != null && methodArgOID != oid) {
logger.debug("Setting up code for argument {}", methodArgOID);
exchange = this.restoreCodeFromLastPosTo(log, generator, methodArgOID, currentRecord, blackList);
if(exchange != null) {
// we can not resolve all dependencies because they rely on other unresolvable object
blackList.add(this.getClassFromOID(log, oid));
return exchange;
}
}
}
// TODO in arbeit
if(isBlackListed(currentOID, blackList, log)) {
logger.debug("-> is blacklisted... " + blackList + " oid: " + currentOID + " class: " + getClassFromOID(log, currentOID));
// we can not resolve all dependencies because they rely on other unresolvable object
blackList.add(this.getClassFromOID(log, oid));
return getExchange(log, currentRecord, currentOID, blackList);
}
logger.debug("Adding method call {}", methodName);
generator.createMethodCallStmt(log, currentRecord);
// forward to end of method call sequence
currentRecord = findEndOfMethod(log, currentRecord, currentOID);
// each method call is considered as object state modification -> so save last object modification
this.updateInitRec(log, 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
this.updateInitRec(log, 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) {
this.updateInitRec(log, methodArgOID, currentRecord);
}
}
}
}
}
return null;
}
private int handleArrayInit(final CaptureLog log,
final ICodeGenerator> generator, final Set> blackList,
int currentRecord, int currentOID) {
try
{
final Object[] methodArgs = log.params.get(currentRecord);
restoreArgs(methodArgs, currentRecord, log, generator, blackList);
generator.createArrayInitStmt(log, currentRecord);
currentRecord = findEndOfMethod(log, currentRecord, currentOID);
this.updateInitRec(log, currentOID, currentRecord);
return currentRecord;
}
catch(final Exception e)
{
CaptureLogAnalyzerException.propagateError(e, "[currentRecord = %s, currentOID = %s, blackList = %s] - an error occurred while creating array init stmt\n", currentRecord, currentOID, blackList);
return -1; // just to satisfy compiler
}
}
private int handleMapInit(final CaptureLog log,
final ICodeGenerator> generator, final Set> blackList,
int currentRecord, int currentOID) {
try
{
final Object[] methodArgs = log.params.get(currentRecord);
restoreArgs(methodArgs, currentRecord, log, generator, blackList);
generator.createMapInitStmt(log, currentRecord);
currentRecord = findEndOfMethod(log, currentRecord, currentOID);
this.updateInitRec(log, currentOID, currentRecord);
return currentRecord;
}
catch(final Exception e)
{
CaptureLogAnalyzerException.propagateError(e, "[currentRecord = %s, currentOID = %s, blackList = %s] - an error occurred while creating map init stmt\n", currentRecord, currentOID, blackList);
return -1; // just to satisfy compiler
}
}
private int handleCollectionInit(final CaptureLog log,
final ICodeGenerator> generator, final Set> blackList,
int currentRecord, int currentOID) {
try
{
final Object[] methodArgs = log.params.get(currentRecord);
restoreArgs(methodArgs, currentRecord, log, generator, blackList);
generator.createCollectionInitStmt(log, currentRecord);
currentRecord = findEndOfMethod(log, currentRecord, currentOID);
this.updateInitRec(log, currentOID, currentRecord);
return currentRecord;
}
catch(final Exception e)
{
CaptureLogAnalyzerException.propagateError(e, "[currentRecord = %s, currentOID = %s, blackList = %s] - an error occurred while creating collection init stmt\n", currentRecord, currentOID, blackList);
return -1; // just to satisfy compiler
}
}
private int handlePlainInit(final CaptureLog log,
final ICodeGenerator> generator, int currentRecord, int currentOID) {
// e.g. String var = "Hello World";
generator.createPlainInitStmt(log, currentRecord);
currentRecord = findEndOfMethod(log, currentRecord, currentOID);
this.updateInitRec(log, currentOID, currentRecord);
return currentRecord;
}
private boolean isBlackListed(final int oid, final Set> blackList, final CaptureLog log)
{
final String typeName = log.getTypeName(oid);
if(typeName.contains("$Proxy"))
{
return true;
}
return blackList.contains(this.getClassFromOID(log, oid));
}
private int[] getExchange(final CaptureLog log, final int currentRecord, final int oid, final Set> blackList)
{
int callerOID;
int r = currentRecord;
do
{
callerOID = this.findCaller(log, ++r);
}
while(this.isBlackListed(callerOID, blackList, log)); // blackList.contains(this.getClassFromOID(log, callerOID)));
blackList.add(this.getClassFromOID(log, oid));
return new int[]{oid, callerOID};
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy