//
// Copyright (C) 2006 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.util.logging.Logger;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.JPFConfigException;
import gov.nasa.jpf.PropertyListenerAdapter;
import gov.nasa.jpf.jvm.bytecode.ArrayStoreInstruction;
import gov.nasa.jpf.jvm.bytecode.InvokeInstruction;
import gov.nasa.jpf.search.Search;
import gov.nasa.jpf.util.DynamicObjectArray;
import gov.nasa.jpf.vm.ClassInfo;
import gov.nasa.jpf.vm.Instruction;
import gov.nasa.jpf.vm.MethodInfo;
import gov.nasa.jpf.vm.ThreadInfo;
import gov.nasa.jpf.vm.VM;
/**
* simple combined listener that checks if a thread seems to do idle loops that
* might starve other threads or JPF. The most classical case is a "busy wait" loop
* like
*
* for (long l=0; l<1000000; l++);
*
* which would give us a pretty long path. Even worse, things like
*
* while (true);
*
* would (just like in a normal VM) never terminate in JPF, even though people
* familiar with model checking would expect state matching. Only that without
* a transition break, JPF has no reason to match states, so we have to
* automatically add a break on the backjump. We shouldn't add one on every
* backjump though because that might cause a lot of overhead in programs that
* do terminate.
*
* IdleFilter has two options:
* idle.max_backjumps : sets the number of backjumps after which we break
* idle.action : what to do if max_backjumps are exceeded in the same thread
* on the same location and stackframe
* warn : only print warning for backjumps exceeding the max_backjumps
* break : break the transition to allow state matching
* prune : unconditionally prune the search
* jump : jump past the backjump (this is dangerous if the loop has side effects)
*/
public class IdleFilter extends PropertyListenerAdapter {
static Logger log = JPF.getLogger("gov.nasa.jpf.listener.IdleFilter");
static class ThreadStat {
String tname;
int backJumps;
boolean isCleared = false;
int loopStartPc;
int loopEndPc;
int loopStackDepth;
ThreadStat(String tname) {
this.tname = tname;
}
}
static enum Action { JUMP, PRUNE, BREAK, YIELD, WARN }
DynamicObjectArray<ThreadStat> threadStats = new DynamicObjectArray<ThreadStat>(4,16);
ThreadStat ts;
// we use this to remember that we just broke the transition
boolean brokeTransition;
int maxBackJumps;
Action action;
// ----------------------------------------------------- SearchListener
// interface
public IdleFilter(Config config) {
maxBackJumps = config.getInt("idle.max_backjumps", 500);
String act = config.getString("idle.action", "break");
if ("warn".equalsIgnoreCase(act)){
action = Action.WARN;
} else if ("break".equalsIgnoreCase(act)){
action = Action.BREAK;
} else if ("yield".equalsIgnoreCase(act)){
action = Action.YIELD;
} else if ("prune".equalsIgnoreCase(act)){
action = Action.PRUNE;
} else if ("jump".equalsIgnoreCase(act)){
action = Action.JUMP;
} else {
throw new JPFConfigException("unknown IdleFilter action: " +act);
}
}
@Override
public void stateAdvanced(Search search) {
ts.backJumps = 0;
ts.isCleared = false;
ts.loopStackDepth = 0;
ts.loopStartPc = ts.loopEndPc = 0;
brokeTransition = false;
}
@Override
public void stateBacktracked(Search search) {
ts.backJumps = 0;
ts.isCleared = false;
ts.loopStackDepth = 0;
ts.loopStartPc = ts.loopEndPc = 0;
}
// ----------------------------------------------------- VMListener interface
@Override
public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
int tid = ti.getId();
ts = threadStats.get(tid);
if (ts == null) {
ts = new ThreadStat(ti.getName());
threadStats.set(tid, ts);
}
if (executedInsn.isBackJump()) {
ts.backJumps++;
int loopStackDepth = ti.getStackDepth();
int loopPc = nextInsn.getPosition();
if ((loopStackDepth != ts.loopStackDepth) || (loopPc != ts.loopStartPc)) {
// new loop, reset
ts.isCleared = false;
ts.loopStackDepth = loopStackDepth;
ts.loopStartPc = loopPc;
ts.loopEndPc = executedInsn.getPosition();
ts.backJumps = 0;
} else {
if (!ts.isCleared) {
if (ts.backJumps > maxBackJumps) {
ti.reschedule("idleFilter"); // this breaks the executePorStep loop
MethodInfo mi = executedInsn.getMethodInfo();
ClassInfo ci = mi.getClassInfo();
int line = mi.getLineNumber(executedInsn);
String file = ci.getSourceFileName();
switch (action) {
case JUMP:
// pretty bold, we jump past the loop end and go on from there
Instruction next = executedInsn.getNext();
ti.setNextPC(next);
log.warning("jumped past loop in: " + ti.getName() +
"\n\tat " + ci.getName() + "." + mi.getName() + "(" + file + ":" + line + ")");
break;
case PRUNE:
// cut this sucker off - we declare this a visited state
vm.ignoreState();
log.warning("pruned thread: " + ti.getName() +
"\n\tat " + ci.getName() + "." + mi.getName() + "(" + file + ":" + line + ")");
break;
case BREAK:
// just break the transition and let the state matching take over
brokeTransition = true;
ti.breakTransition("breakIdleLoop");
log.warning("breaks transition on suspicious loop in thread: " + ti.getName() +
"\n\tat " + ci.getName() + "." + mi.getName() + "(" + file + ":" + line + ")");
break;
case YIELD:
// give other threads a chance to run
brokeTransition = true;
ti.yield();
log.warning("yield on suspicious loop in thread: " + ti.getName() +
"\n\tat " + ci.getName() + "." + mi.getName() + "(" + file + ":" + line + ")");
break;
case WARN:
log.warning("detected suspicious loop in thread: " + ti.getName() +
"\n\tat " + ci.getName() + "." + mi.getName() + "(" + file + ":" + line + ")");
break;
}
}
}
}
} else if (!ts.isCleared) {
// if we call methods or set array elements inside the loop in question,
// we assume this is not an idle loop and terminate the checks
// <2do> this is too restrictive - we should leave this to state matching
if ((executedInsn instanceof InvokeInstruction)
|| (executedInsn instanceof ArrayStoreInstruction)) {
int stackDepth = ti.getStackDepth();
int pc = executedInsn.getPosition();
if (stackDepth == ts.loopStackDepth) {
if ((pc >= ts.loopStartPc) && (pc < ts.loopEndPc)) {
ts.isCleared = true;
}
}
}
}
}
// thread ids are reused, so we have to clean up
@Override
public void threadTerminated (VM vm, ThreadInfo ti){
int tid = ti.getId();
threadStats.set(tid, null);
}
}