//
// 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.io.PrintWriter;
import java.io.StringWriter;
import de.fosd.typechef.featureexpr.FeatureExpr;
import de.fosd.typechef.featureexpr.FeatureExprFactory;
import gov.nasa.jpf.Config;
import gov.nasa.jpf.PropertyListenerAdapter;
import gov.nasa.jpf.jvm.bytecode.ArrayElementInstruction;
import gov.nasa.jpf.jvm.bytecode.FieldInstruction;
import gov.nasa.jpf.search.Search;
import gov.nasa.jpf.util.StringSetMatcher;
import gov.nasa.jpf.vm.ChoiceGenerator;
import gov.nasa.jpf.vm.ElementInfo;
import gov.nasa.jpf.vm.FieldInfo;
import gov.nasa.jpf.vm.Instruction;
import gov.nasa.jpf.vm.MethodInfo;
import gov.nasa.jpf.vm.ThreadInfo;
import gov.nasa.jpf.vm.VM;
import gov.nasa.jpf.vm.choice.ThreadChoiceFromSet;
/**
* This is a Race Detection Algorithm that is precise in its calculation of races, i.e. no false warnings.
* It exploits the fact that every thread choice selection point could be due to a possible race. It just runs
* through all the thread choices and checks whether there are more than one thread trying to read & write to the
* same field of an object.
*
* Current limitation is that it is only sound, i.e. will not miss a race, if the sync-detection is switched off
* during model checking. This is due to the fact that the sync-detection guesses that an acess is lock-protected
* when it in reality might not be.
*
* The listener also checks races for array elements, but in order to do so you have to set
* "cg.threads.break_arrays=true" (note that it is false by default because this can cause serious state
* explosion)
*
* This algorithm came out of a discussion with Franck van Breugel and Sergey Kulikov from the University of York.
* All credits for it goes to Franck and Sergey, all the bugs are mine.
*
*
* Author: Willem Visser
*
*/
public class PreciseRaceDetector extends PropertyListenerAdapter {
static class Race {
Race prev; // linked list
ThreadInfo ti1, ti2;
Instruction insn1, insn2;
ElementInfo ei;
boolean isRace() {
return insn2 != null && ti1 != null && ti2 != null && ( ! ti1.equals(ti2) );
}
void printOn(PrintWriter pw){
pw.print(" ");
pw.print( ti1.getName());
pw.print(" at ");
pw.println(insn1.getSourceLocation());
String line = insn1.getSourceLine();
if (line != null){
pw.print("\t\t\"" + line.trim());
}
pw.print("\" : ");
pw.println(insn1);
if (insn2 != null){
pw.print(" ");
pw.print(ti2.getName());
pw.print(" at ");
pw.println(insn2.getSourceLocation());
line = insn2.getSourceLine();
if (line != null){
pw.print("\t\t\"" + line.trim());
}
pw.print("\" : ");
pw.println(insn2);
}
}
}
static class FieldRace extends Race {
FieldInfo fi;
static Race check (Race prev, ThreadInfo ti, Instruction insn, ElementInfo ei, FieldInfo fi){
for (Race r = prev; r != null; r = r.prev){
if (r instanceof FieldRace){
FieldRace fr = (FieldRace)r;
if (fr.ei == ei && fr.fi == fi){
if (!((FieldInstruction)fr.insn1).isRead() || !((FieldInstruction)insn).isRead()){
fr.ti2 = ti;
fr.insn2 = insn;
return fr;
}
}
}
}
FieldRace fr = new FieldRace();
fr.ei = ei;
fr.ti1 = ti;
fr.insn1 = insn;
fr.fi = fi;
fr.prev = prev;
return fr;
}
void printOn(PrintWriter pw){
pw.print("race for field ");
pw.print(ei);
pw.print('.');
pw.println(fi.getName());
super.printOn(pw);
}
}
static class ArrayElementRace extends Race {
int idx;
static Race check (Race prev, ThreadInfo ti, Instruction insn, ElementInfo ei, int idx){
for (Race r = prev; r != null; r = r.prev){
if (r instanceof ArrayElementRace){
ArrayElementRace ar = (ArrayElementRace)r;
if (ar.ei == ei && ar.idx == idx){
if (!((ArrayElementInstruction)ar.insn1).isRead() || !((ArrayElementInstruction)insn).isRead()){
ar.ti2 = ti;
ar.insn2 = insn;
return ar;
}
}
}
}
ArrayElementRace ar = new ArrayElementRace();
ar.ei = ei;
ar.ti1 = ti;
ar.insn1 = insn;
ar.idx = idx;
ar.prev = prev;
return ar;
}
void printOn(PrintWriter pw){
pw.print("race for array element ");
pw.print(ei);
pw.print('[');
pw.print(idx);
pw.println(']');
super.printOn(pw);
}
}
// this is where we store if we detect one
Race race;
// our matchers to determine which code we have to check
StringSetMatcher includes = null; // means all
StringSetMatcher excludes = null; // means none
public PreciseRaceDetector (Config conf) {
includes = StringSetMatcher.getNonEmpty(conf.getStringArray("race.include"));
excludes = StringSetMatcher.getNonEmpty(conf.getStringArray("race.exclude"));
}
@Override
public boolean check(Search search, VM vm) {
return (race == null);
}
@Override
public void reset() {
race = null;
}
public String getErrorMessage () {
if (race != null){
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
race.printOn(pw);
pw.flush();
return sw.toString();
} else {
return null;
}
}
boolean checkRace (ThreadInfo[] threads){
Race candidate = null;
for (int i = 0; i < threads.length; i++) {
ThreadInfo ti = threads[i];
Instruction insn = ti.getPC().getValue();
MethodInfo mi = insn.getMethodInfo();
if (StringSetMatcher.isMatch(mi.getBaseName(), includes, excludes)) {
if (insn instanceof FieldInstruction) {
FieldInstruction finsn = (FieldInstruction) insn;
FieldInfo fi = finsn.getFieldInfo(null);
ElementInfo ei = finsn.peekElementInfo(ti);
candidate = FieldRace.check(candidate, ti, finsn, ei, fi);
} else if (insn instanceof ArrayElementInstruction) {
ArrayElementInstruction ainsn = (ArrayElementInstruction) insn;
int aref = ainsn.getArrayRef(ti);
ElementInfo ei = ti.getElementInfo(aref);
// these insns have been through their top half since they created CGs, but they haven't
// removed the operands from the stack
int idx = ainsn.peekIndex(FeatureExprFactory.True(), ti).getValue();
candidate = ArrayElementRace.check(candidate, ti, ainsn, ei, idx);
}
}
if (candidate != null && candidate.isRace()){
race = candidate;
return true;
}
}
return false;
}
//----------- our VMListener interface
// All we rely on here is that the scheduler breaks transitions at all
// insns that could be races. We then just have to look at all currently
// executed insns and don't rely on any past-exec info, PROVIDED that we only
// use execution parameters (index or reference values) that are retrieved
// from the operand stack, and not cached in the insn from a previous exec
// (all the insns we look at are pre-exec, i.e. don't have their caches
// updated yet)
@Override
public void choiceGeneratorSet(VM vm, ChoiceGenerator<?> newCG) {
if (newCG instanceof ThreadChoiceFromSet) {
ThreadInfo[] threads = ((ThreadChoiceFromSet)newCG).getAllThreadChoices();
checkRace(threads);
}
}
@Override
public void executeInstruction (FeatureExpr ctx, VM vm, ThreadInfo ti, Instruction insnToExecute) {
if (race != null) {
// we're done, report as quickly as possible
//ti.skipInstruction();
ti.breakTransition("dataRace");
}
}
}