//
// Copyright (C) 2008 United States Government as represented by the
// Administrator of the National Aeronautics and Space Administration
// (NASA). All Rights Reserved.
//
// This software is distributed under the NASA Open Source Agreement
// (NOSA), version 1.3. The NOSA has been approved by the Open Source
// Initiative. See the file NOSA-1.3-JPF at the top of the distribution
// directory tree for the complete NOSA document.
//
// THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
// KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
// LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
// SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
// A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
// THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
// DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
//
package gov.nasa.jpf.listener;
import java.io.PrintWriter;
import java.util.HashMap;
import de.fosd.typechef.featureexpr.FeatureExprFactory;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.ListenerAdapter;
import gov.nasa.jpf.jvm.bytecode.InstanceInvocation;
import gov.nasa.jpf.jvm.bytecode.InvokeInstruction;
import gov.nasa.jpf.jvm.bytecode.ReturnInstruction;
import gov.nasa.jpf.report.ConsolePublisher;
import gov.nasa.jpf.report.Publisher;
import gov.nasa.jpf.search.Search;
import gov.nasa.jpf.util.StringSetMatcher;
import gov.nasa.jpf.vm.ElementInfo;
import gov.nasa.jpf.vm.Instruction;
import gov.nasa.jpf.vm.MJIEnv;
import gov.nasa.jpf.vm.MethodInfo;
import gov.nasa.jpf.vm.StackFrame;
import gov.nasa.jpf.vm.ThreadInfo;
import gov.nasa.jpf.vm.VM;
/**
* analyzes call/execute sequences of methods
* closely modeled after the DeadlockAnalyzer, i.e. keeps it's own
* log and doesn't require full instruction trace
*
* <2do> this needs to be refactored with DeadlockAnalyzer - the whole
* trace mgnt (except of the printing) can be made generic
*/
public class MethodAnalyzer extends ListenerAdapter {
enum OpType { CALL ("> "), // invokeX breaks transition (e.g. blocked sync)
EXECUTE (" - "), // method entered method after transition break
CALL_EXECUTE (">- "), // call & enter within same transition
RETURN (" <"), // method returned
EXEC_RETURN (" -<"), // enter & return in consecutive ops
CALL_EXEC_RETURN (">-<"); // call & enter & return in consecutive ops
String code;
OpType (String code){ this.code = code; }
};
static class MethodOp {
OpType type;
ThreadInfo ti;
ElementInfo ei;
Instruction insn; // the caller
MethodInfo mi; // the callee
int stackDepth;
// this is used to keep our own trace
int stateId = Integer.MIN_VALUE;
MethodOp prevTransition;
MethodOp p; // prev during execution
MethodOp (OpType type, MethodInfo mi, ThreadInfo ti, ElementInfo ei, int stackDepth){
this.type = type;
this.ti = ti;
this.mi = mi;
this.ei = ei;
this.stackDepth = stackDepth;
}
MethodOp clone (OpType newType){
MethodOp op = new MethodOp(newType, mi, ti, ei, stackDepth);
op.p = p;
return op;
}
boolean isMethodEnter() {
return (type == OpType.CALL_EXECUTE) || (type == OpType.EXECUTE);
}
boolean isSameMethod(MethodOp op) {
return (mi == op.mi) && (ti == op.ti) && (ei == op.ei) && (stackDepth == op.stackDepth);
}
void printOn(PrintWriter pw, MethodAnalyzer analyzer) {
pw.print(ti.getId());
pw.print(": ");
pw.print(type.code);
pw.print(' ');
if (analyzer.showDepth){
for (int i = 0; i < stackDepth; i++) {
pw.print('.');
}
pw.print(' ');
}
if (!mi.isStatic()){
if (ei.getClassInfo() != mi.getClassInfo()){ // method is in superclass
pw.print(mi.getClassName());
pw.print('<');
pw.print(ei);
pw.print('>');
} else { // method is in concrete object class
pw.print(ei);
}
} else {
pw.print(mi.getClassName());
}
pw.print('.');
pw.print(mi.getUniqueName());
}
public String toString() {
return "Op {" + ti.getName() + ',' + type.code +
',' + mi.getFullName() + ',' + ei + '}';
}
}
// report options
StringSetMatcher includes = null; // means all
StringSetMatcher excludes = null; // means none
int maxHistory;
String format;
boolean skipInit;
boolean showDepth;
boolean showTransition;
boolean showCompleted;
// execution environment
VM vm;
Search search;
OpType opType;
// this is used to keep our own trace
MethodOp lastOp;
MethodOp lastTransition;
boolean isFirstTransition = true;
// this is set after we call revertAndFlatten during reporting
// (we can't call revertAndFlatten twice since it is destructive, but
// we might have to report several times in case we have several publishers)
MethodOp firstOp = null;
// for HeuristicSearches. Ok, that's braindead but at least no need for cloning
HashMap<Integer,MethodOp> storedTransition = new HashMap<Integer,MethodOp>();
public MethodAnalyzer (Config config, JPF jpf){
jpf.addPublisherExtension(ConsolePublisher.class, this);
maxHistory = config.getInt("method.max_history", Integer.MAX_VALUE);
format = config.getString("method.format", "raw");
skipInit = config.getBoolean("method.skip_init", true);
showDepth = config.getBoolean("method.show_depth", false);
showTransition = config.getBoolean("method.show_transition", true);
includes = StringSetMatcher.getNonEmpty(config.getStringArray("method.include"));
excludes = StringSetMatcher.getNonEmpty(config.getStringArray("method.exclude"));
vm = jpf.getVM();
search = jpf.getSearch();
}
void addOp (VM vm, OpType opType, MethodInfo mi, ThreadInfo ti, ElementInfo ei, int stackDepth){
if (!(skipInit && isFirstTransition)) {
MethodOp op = new MethodOp(opType, mi, ti, ei, stackDepth);
if (lastOp == null){
lastOp = op;
} else {
op.p = lastOp;
lastOp = op;
}
}
}
boolean isAnalyzedMethod (MethodInfo mi){
if (mi != null){
String mthName = mi.getFullName();
return StringSetMatcher.isMatch(mthName, includes, excludes);
} else {
return false;
}
}
void printOn (PrintWriter pw) {
MethodOp start = firstOp;
int lastStateId = Integer.MIN_VALUE;
int transition = skipInit ? 1 : 0;
int lastTid = start.ti.getId();
for (MethodOp op = start; op != null; op = op.p) {
if (showTransition) {
if (op.stateId != lastStateId) {
lastStateId = op.stateId;
pw.print("------------------------------------------ #");
pw.println(transition++);
}
} else {
int tid = op.ti.getId();
if (tid != lastTid) {
lastTid = tid;
pw.println("------------------------------------------");
}
}
op.printOn(pw, this);
pw.println();
}
}
// warning - this rotates pointers in situ, i.e. destroys the original structure
MethodOp revertAndFlatten (MethodOp start) {
MethodOp last = null;
MethodOp prevTransition = start.prevTransition;
for (MethodOp op = start;;) {
MethodOp opp = op.p;
op.p = last;
if (opp == null) {
if (prevTransition == null) {
return op;
} else {
last = op;
op = prevTransition;
prevTransition = op.prevTransition;
}
} else {
last = op;
op = opp;
}
}
}
//--- SearchListener interface
// <2do> this is the same as DeadlockAnalyzer, except of xxOp type -> refactor
@Override
public void stateAdvanced (Search search){
if (search.isNewState() && (lastOp != null)) {
int stateId = search.getStateId();
for (MethodOp op=lastOp; op != null; op=op.p) {
op.stateId = stateId;
}
lastOp.prevTransition = lastTransition;
lastTransition = lastOp;
}
lastOp = null;
isFirstTransition = false;
}
@Override
public void stateBacktracked (Search search){
int stateId = search.getStateId();
while ((lastTransition != null) && (lastTransition.stateId > stateId)){
lastTransition = lastTransition.prevTransition;
}
lastOp = null;
}
@Override
public void stateStored (Search search) {
// always called after stateAdvanced
storedTransition.put(search.getStateId(), lastTransition);
}
@Override
public void stateRestored (Search search) {
int stateId = search.getStateId();
MethodOp op = storedTransition.get(stateId);
if (op != null) {
lastTransition = op;
storedTransition.remove(stateId); // not strictly required, but we don't come back
}
}
//--- VMlistener interface
@Override
public void instructionExecuted (VM vm, ThreadInfo thread, Instruction nextInsn, Instruction executedInsn) {
ThreadInfo ti;
MethodInfo mi;
ElementInfo ei = null;
if (executedInsn instanceof InvokeInstruction) {
InvokeInstruction call = (InvokeInstruction)executedInsn;
ti = thread;
mi = call.getInvokedMethod(FeatureExprFactory.True(), ti);
if (isAnalyzedMethod(mi)) {
OpType type;
// check if this was actually executed, or is a blocked sync call
if (ti.getNextPC().getValue() == call) { // re-executed -> blocked or overlayed
type = OpType.CALL;
} else { // executed
if (ti.isFirstStepInsn()) {
type = OpType.EXECUTE;
} else {
type = OpType.CALL_EXECUTE;
}
}
if (call instanceof InstanceInvocation) {
ei = ((InstanceInvocation)call).getThisElementInfo(ti);
}
addOp(vm,type,mi,ti,ei, ti.getStackDepth());
}
} else if (executedInsn instanceof ReturnInstruction) {
ReturnInstruction ret = (ReturnInstruction)executedInsn;
ti = thread;
StackFrame frame = ret.getReturnFrame();
mi = frame.getMethodInfo();
if (isAnalyzedMethod(mi)) {
if (!mi.isStatic()) {
int ref = frame.getThis().getValue();
if (ref != MJIEnv.NULL) {
ei = ti.getElementInfo(ref);
}
}
addOp(vm,OpType.RETURN,mi,ti,ei, ti.getStackDepth()+1); // postExec-> frame already popped
}
}
}
//--- the PubisherExtension part
@Override
public void publishPropertyViolation (Publisher publisher) {
if (firstOp == null && lastTransition != null){ // do this just once
firstOp = revertAndFlatten(lastTransition);
}
if (firstOp == null){
return;
}
PrintWriter pw = publisher.getOut();
publisher.publishTopicStart("method ops " + publisher.getLastErrorId());
printOn(pw);
}
}