//
// Copyright (C) 2007 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.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.ListIterator;
import java.util.Stack;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPF;
import gov.nasa.jpf.ListenerAdapter;
import gov.nasa.jpf.jvm.bytecode.EXECUTENATIVE;
import gov.nasa.jpf.report.ConsolePublisher;
import gov.nasa.jpf.report.Publisher;
import gov.nasa.jpf.search.Search;
import gov.nasa.jpf.vm.ElementInfo;
import gov.nasa.jpf.vm.Instruction;
import gov.nasa.jpf.vm.StackFrame;
import gov.nasa.jpf.vm.ThreadInfo;
import gov.nasa.jpf.vm.VM;
/**
* example of a listener that creates property specific traces. The interesting
* thing is that it does so without the need to store steps, i.e. it maintains
* it's own transition stack.
* this is still work in progress, analyzing the trace can be much more
* elaborate (we just dump up to a max history size for now)
*
* <2do> DeadlockAnalyzer output can be confusing if a reorganizing
* ThreadList is used (which reassigns thread ids)
*/
public class DeadlockAnalyzer extends ListenerAdapter {
enum OpType { block, lock, unlock, wait, notify, notifyAll, started, terminated };
static String[] opTypeMnemonic = { "B", "L", "U", "W", "N", "A", "S", "T" };
static class ThreadOp {
ThreadInfo ti;
ElementInfo ei;
Instruction insn;
// kind of redundant, but there might be context attributes in addition
// to the insn itself
OpType opType;
// we could identify this with the insn, but only in case this is
// a transition boundary, which is far less general than we can be
int stateId;
ThreadOp prevTransition;
ThreadOp prevOp;
ThreadOp (ThreadInfo ti, ElementInfo ei, OpType type) {
this.ti = ti;
this.ei = ei;
insn = getReportInsn(ti); // we haven't had the executeInsn notification yet
opType = type;
prevOp = null;
}
Instruction getReportInsn(ThreadInfo ti){
StackFrame frame = ti.getTopFrame();
if (frame != null) {
Instruction insn = frame.getPC().getValue();
if (insn instanceof EXECUTENATIVE) {
frame = frame.getPrevious();
if (frame != null) {
insn = frame.getPC().getValue();
}
}
return insn;
} else {
return null;
}
}
void printLocOn (PrintWriter pw) {
pw.print(String.format("%6d", new Integer(stateId)));
if (insn != null) {
pw.print(String.format(" %18.18s ", insn.getMnemonic()));
pw.print(insn.getFileLocation());
String line = insn.getSourceLine();
if (line != null){
pw.print( " : ");
pw.print(line.trim());
//pw.print(insn);
}
}
}
void printOn (PrintWriter pw){
pw.print( stateId);
pw.print( " : ");
pw.print( ti.getName());
pw.print( " : ");
pw.print( opType.name());
pw.print( " : ");
pw.println(ei);
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append( stateId);
sb.append( " : ");
sb.append( ti.getName());
sb.append( " : ");
sb.append( opType.name());
sb.append( " : ");
sb.append(ei);
return sb.toString();
}
void printColumnOn(PrintWriter pw, Collection<ThreadInfo> tlist){
for (ThreadInfo t : tlist) {
if (ti == t) {
if (opType == OpType.started || opType == OpType.terminated) {
pw.print(String.format(" %1$s ", opTypeMnemonic[opType.ordinal()]));
} else {
pw.print(String.format("%1$s:%2$-5x ", opTypeMnemonic[opType.ordinal()], ei.getObjectRef()));
}
//break;
} else {
pw.print(" | ");
}
}
}
}
ThreadOp lastOp;
ThreadOp lastTransition;
int maxHistory;
String format;
VM vm;
Search search;
public DeadlockAnalyzer (Config config, JPF jpf){
jpf.addPublisherExtension(ConsolePublisher.class, this);
maxHistory = config.getInt("deadlock.max_history", Integer.MAX_VALUE);
format = config.getString("deadlock.format", "essential");
vm = jpf.getVM();
search = jpf.getSearch();
}
boolean requireAllOps() {
return (format.equalsIgnoreCase("essential"));
}
void addOp (ThreadInfo ti, ElementInfo ei, OpType opType){
ThreadOp op = new ThreadOp(ti, ei, opType);
if (lastOp == null){
lastOp = op;
} else {
assert lastOp.stateId == 0;
op.prevOp = lastOp;
lastOp = op;
}
}
void printRawOps (PrintWriter pw) {
int i=0;
for (ThreadOp tOp = lastTransition; tOp != null; tOp = tOp.prevTransition){
for (ThreadOp op = tOp; op != null; op=op.prevOp) {
if (i++ >= maxHistory){
pw.println("...");
return;
}
op.printOn(pw);
}
}
}
/**
* include all threads that are currently blocked or waiting, and
* all the threads that had the last interaction with them. Note that
* we do this completely on the basis of the recorded ThreadOps, i.e.
* don't rely on when this is called
*/
void printEssentialOps(PrintWriter pw) {
LinkedHashSet<ThreadInfo> threads = new LinkedHashSet<ThreadInfo>();
ArrayList<ThreadOp> ops = new ArrayList<ThreadOp>();
HashMap<ElementInfo,ThreadInfo> waits = new HashMap<ElementInfo,ThreadInfo>();
HashMap<ElementInfo,ThreadInfo> blocks = new HashMap<ElementInfo,ThreadInfo>();
HashSet<ThreadInfo> runnables = new HashSet<ThreadInfo>();
// collect all relevant threads and ops
for (ThreadOp trans = lastTransition; trans != null; trans = trans.prevTransition){
for (ThreadOp tOp = trans; tOp != null; tOp = tOp.prevOp) {
OpType ot = tOp.opType;
ThreadInfo oti = tOp.ti;
if (ot == OpType.wait || ot == OpType.block) {
if (!runnables.contains(oti) && !threads.contains(oti)){
HashMap<ElementInfo, ThreadInfo> map = (ot == OpType.block) ? blocks : waits;
threads.add(oti);
map.put(tOp.ei, oti);
ops.add(tOp);
}
} else if (ot == OpType.notify || ot == OpType.notifyAll || ot == OpType.lock) {
HashMap<ElementInfo, ThreadInfo> map = (ot == OpType.lock) ? blocks : waits;
ThreadInfo ti = map.get(tOp.ei);
if (ti != null && ti != oti){
if (!threads.contains(oti)){
threads.add(oti);
}
map.remove(tOp.ei);
ops.add(tOp);
}
runnables.add(oti);
} else if (ot == OpType.unlock) {
// not relevant
runnables.add(oti);
} else if (ot == OpType.terminated || ot == OpType.started) {
ops.add(tOp); // might be removed later-on
}
}
}
// remove all starts/terminates of irrelevant threads
for (ListIterator<ThreadOp> it = ops.listIterator(); it.hasNext(); ){
ThreadOp tOp = it.next();
if (tOp.opType == OpType.terminated || tOp.opType == OpType.started) {
if (!threads.contains(tOp.ti)){
it.remove();
}
}
}
// now we are ready to print
printHeader(pw,threads);
for (ThreadOp tOp : ops) {
tOp.printColumnOn(pw,threads);
tOp.printLocOn(pw);
pw.println();
}
}
Collection<ThreadInfo> getThreadList() {
ArrayList<ThreadInfo> tcol = new ArrayList<ThreadInfo>();
boolean allOps = requireAllOps();
int i=0;
prevTrans:
for (ThreadOp tOp = lastTransition; tOp != null; tOp = tOp.prevTransition){
i++;
if (!allOps && (i >= maxHistory)){
break;
}
for (ThreadInfo ti : tcol) {
if (ti == tOp.ti) continue prevTrans;
}
tcol.add(tOp.ti);
}
return tcol;
}
void printHeader (PrintWriter pw, Collection<ThreadInfo> tlist){
for (ThreadInfo ti : tlist){
pw.print(String.format(" %1$2d ", ti.getId()));
}
pw.print(" trans insn loc : stmt");
pw.println();
for (int i=0; i<tlist.size(); i++){
pw.print("------- ");
}
pw.print("---------------------------------------------------");
pw.println();
}
void printColumnOps (PrintWriter pw){
int i = 0;
Collection<ThreadInfo> tlist = getThreadList();
printHeader(pw,tlist);
// and now the data
for (ThreadOp tOp = lastTransition; tOp != null; tOp = tOp.prevTransition){
for (ThreadOp op = tOp; op != null; op=op.prevOp) {
if (i++ >= maxHistory){
pw.println("...");
return;
}
op.printColumnOn(pw,tlist);
op.printLocOn(pw);
pw.println();
}
}
}
/**
* this is the workhorse - filter which ops should be shown, and which
* are irrelevant for the deadlock
*/
boolean showOp (ThreadOp op, ThreadInfo[] tlist,
boolean[] waitSeen, boolean[] notifySeen,
boolean[] blockSeen, boolean[] lockSeen,
Stack<ElementInfo>[] unlocked) {
ThreadInfo ti = op.ti;
ElementInfo ei = op.ei;
int idx;
for (idx=0; idx < tlist.length; idx++){
if (tlist[idx] == ti) break;
}
// we could delegate this to the enum type, but let's not be too fancy
switch (op.opType) {
case block:
// only report the last one if thread is blocked
if (ti.isBlocked()) {
if (!blockSeen[idx]) {
blockSeen[idx] = true;
return true;
}
}
return false;
case unlock:
unlocked[idx].push(ei);
return false;
case lock:
// if we had a corresponding unlock, ignore
if (!unlocked[idx].isEmpty() && (unlocked[idx].peek() == ei)) {
unlocked[idx].pop();
return false;
}
// only report the last one if there is a thread that's currently blocked on it
for (int i=0; i<tlist.length; i++){
if ((i != idx) && tlist[i].isBlocked() && (tlist[i].getLockObject() == ei)) {
if (!lockSeen[i]){
lockSeen[i] = true;
return true;
}
}
}
return false;
case wait:
if (ti.isWaiting()){ // only show the last one if this is a waiting thread
if (!waitSeen[idx]) {
waitSeen[idx] = true;
return true;
}
}
return false;
case notify:
case notifyAll:
// only report the last one if there's a thread waiting on it
for (int i=0; i<tlist.length; i++){
if ((i != idx) && tlist[i].isWaiting() && (tlist[i].getLockObject() == ei)) {
if (!notifySeen[i]) {
notifySeen[i] = true;
return true;
}
}
}
return false;
case started:
case terminated:
return true;
}
return false;
}
void storeLastTransition(){
if (lastOp != null) {
int stateId = search.getStateId();
for (ThreadOp op = lastOp; op != null; op = op.prevOp) {
assert op.stateId == 0;
op.stateId = stateId;
}
lastOp.prevTransition = lastTransition;
lastTransition = lastOp;
lastOp = null;
}
}
//--- VM listener interface
@Override
public void objectLocked (VM vm, ThreadInfo ti, ElementInfo ei) {
addOp(ti, ei, OpType.lock);
}
@Override
public void objectUnlocked (VM vm, ThreadInfo ti, ElementInfo ei) {
addOp(ti, ei, OpType.unlock);
}
@Override
public void objectWait (VM vm, ThreadInfo ti, ElementInfo ei) {
addOp(ti, ei, OpType.wait);
}
@Override
public void objectNotify (VM vm, ThreadInfo ti, ElementInfo ei) {
addOp(ti, ei, OpType.notify);
}
@Override
public void objectNotifyAll (VM vm, ThreadInfo ti, ElementInfo ei) {
addOp(ti, ei, OpType.notifyAll);
}
@Override
public void threadBlocked (VM vm, ThreadInfo ti, ElementInfo ei){
addOp(ti, ei, OpType.block);
}
@Override
public void threadStarted (VM vm, ThreadInfo ti){
addOp(ti, null, OpType.started);
}
@Override
public void threadTerminated (VM vm, ThreadInfo ti){
addOp(ti, null, OpType.terminated);
}
//--- SearchListener interface
@Override
public void stateAdvanced (Search search){
if (search.isNewState()) {
storeLastTransition();
}
}
@Override
public void stateBacktracked (Search search){
int stateId = search.getStateId();
while ((lastTransition != null) && (lastTransition.stateId > stateId)){
lastTransition = lastTransition.prevTransition;
}
lastOp = null;
}
// for HeuristicSearches. Ok, that's braindead but at least no need for cloning
HashMap<Integer,ThreadOp> storedTransition = new HashMap<Integer,ThreadOp>();
@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();
ThreadOp op = storedTransition.get(stateId);
if (op != null) {
lastTransition = op;
storedTransition.remove(stateId); // not strictly required, but we don't come back
}
}
@Override
public void publishPropertyViolation (Publisher publisher) {
PrintWriter pw = publisher.getOut();
publisher.publishTopicStart("thread ops " + publisher.getLastErrorId());
if ("column".equalsIgnoreCase(format)){
printColumnOps(pw);
} else if ("essential".equalsIgnoreCase(format)) {
printEssentialOps(pw);
} else {
printRawOps(pw);
}
}
}