package daikon.tools;
import daikon.*;
import daikon.inv.*;
import daikon.inv.filter.InvariantFilters;
import utilMDE.*;
import java.util.*;
import java.io.*;
import gnu.getopt.*;
import java.util.logging.Logger;
import java.util.logging.Level;
/**
* InvariantChecker reads an invariant file and trace file. It prints errors
* for any invariants that are violated by the trace file.
*/
public class InvariantChecker {
private InvariantChecker() { throw new Error("do not instantiate"); }
public static final Logger debug
= Logger.getLogger ("daikon.tools.InvariantChecker");
public static final Logger debug_detail
= Logger.getLogger ("daikon.tools.InvariantCheckerDetail");
private static final String output_SWITCH = "output";
private static final String dir_SWITCH = "dir";
private static final String conf_SWITCH = "conf";
private static final String filter_SWITCH = "filter";
private static String usage =
UtilMDE.joinLines(
"Usage: java daikon.InvariantChecker [OPTION]... <inv_file> "
+ "<dtrace_file>",
" -h, --" + Daikon.help_SWITCH,
" Display this usage message",
" --" + output_SWITCH + " output file",
" --" + conf_SWITCH,
" Checks only invariants that are above the default confidence level",
" --" + filter_SWITCH,
" Checks only invariants that are not filtered by the default filters",
" --" + dir_SWITCH + " directory with invariant and dtrace files",
" We output how many invariants failed for each invariant file. We check for failure against any sample in any dtrace file.",
" --" + Daikon.config_option_SWITCH + " config_var=val",
" Sets the specified configuration variable. ",
" --" + Daikon.debugAll_SWITCH,
" Turns on all debug flags (voluminous output)",
" --" + Daikon.debug_SWITCH + " logger",
" Turns on the specified debug logger",
" --" + Daikon.track_SWITCH + " class<var1,var2,var3>@ppt",
" Print debug info on the specified invariant class, vars, and ppt"
);
public static File inv_file = null;
public static List<String> dtrace_files = new ArrayList<String>();
static File output_file;
static PrintStream output_stream = System.out;
static int error_cnt = 0;
static int sample_cnt = 0;
static File dir_file; //Yoav added
static boolean doFilter;
static boolean doConf;
static boolean quiet = true;
static HashSet<Invariant> failedInvariants = new HashSet<Invariant>(); //Yoav added
static HashSet<Invariant> testedInvariants = new HashSet<Invariant>(); //Yoav added
static HashSet<Invariant> activeInvariants = new HashSet<Invariant>(); //Yoav added
static LinkedHashSet<String> outputComma = new LinkedHashSet<String>(); //Yoav added
public static void main(String[] args)
throws FileNotFoundException, StreamCorruptedException,
OptionalDataException, IOException, ClassNotFoundException {
try {
if (args.length==0) {
throw new Daikon.TerminationMessage(usage);
}
mainHelper(args);
} catch (Daikon.TerminationMessage e) {
System.err.println(e.getMessage());
System.exit(1);
}
// Any exception other than Daikon.TerminationMessage gets propagated.
// This simplifies debugging by showing the stack trace.
}
/**
* This does the work of main, but it never calls System.exit, so it
* is appropriate to be called progrmmatically.
* Termination of the program with a message to the user is indicated by
* throwing Daikon.TerminationMessage.
* @see #main(String[])
* @see daikon.Daikon.TerminationMessage
**/
public static void mainHelper(final String[] args)
throws FileNotFoundException, StreamCorruptedException,
OptionalDataException, IOException, ClassNotFoundException {
daikon.LogHelper.setupLogs(daikon.LogHelper.INFO);
LongOpt[] longopts = new LongOpt[] {
new LongOpt(Daikon.config_option_SWITCH, LongOpt.REQUIRED_ARGUMENT,
null, 0),
new LongOpt(output_SWITCH, LongOpt.REQUIRED_ARGUMENT, null, 0),
new LongOpt(dir_SWITCH, LongOpt.REQUIRED_ARGUMENT, null, 0),
new LongOpt(conf_SWITCH, LongOpt.NO_ARGUMENT, null, 0),
new LongOpt(filter_SWITCH, LongOpt.NO_ARGUMENT, null, 0),
new LongOpt(Daikon.debugAll_SWITCH, LongOpt.NO_ARGUMENT, null, 0),
new LongOpt(Daikon.debug_SWITCH, LongOpt.REQUIRED_ARGUMENT, null, 0),
new LongOpt(Daikon.ppt_regexp_SWITCH, LongOpt.REQUIRED_ARGUMENT, null,
0),
new LongOpt(Daikon.track_SWITCH, LongOpt.REQUIRED_ARGUMENT, null, 0),
};
Getopt g = new Getopt("daikon.tools.InvariantChecker", args, "h", longopts);
int c;
while ((c = g.getopt()) != -1) {
switch(c) {
case 0:
// got a long option
String option_name = longopts[g.getLongind()].getName();
if (Daikon.help_SWITCH.equals(option_name)) {
System.out.println(usage);
throw new Daikon.TerminationMessage();
} else if (conf_SWITCH.equals (option_name)) {
doConf = true;
} else if (filter_SWITCH.equals (option_name)) {
doFilter = true;
} else if (dir_SWITCH.equals (option_name)) {
dir_file = new File (g.getOptarg());
if (!dir_file.exists() || !dir_file.isDirectory())
throw new Daikon.TerminationMessage ("Error reading the directory "+dir_file);
} else if (output_SWITCH.equals (option_name)) {
output_file = new File (g.getOptarg());
output_stream = new PrintStream (new FileOutputStream (output_file));
} else if (Daikon.config_option_SWITCH.equals(option_name)) {
String item = g.getOptarg();
daikon.config.Configuration.getInstance().apply(item);
break;
} else if (Daikon.debugAll_SWITCH.equals(option_name)) {
Global.debugAll = true;
} else if (Daikon.debug_SWITCH.equals(option_name)) {
LogHelper.setLevel(g.getOptarg(), LogHelper.FINE);
} else if (Daikon.track_SWITCH.equals (option_name)) {
LogHelper.setLevel("daikon.Debug", LogHelper.FINE);
String error = Debug.add_track (g.getOptarg());
if (error != null) {
throw new Daikon.TerminationMessage ("Error parsing track argument '"
+ g.getOptarg() + "' - " + error);
}
} else {
throw new RuntimeException("Unknown long option received: " +
option_name);
}
break;
case 'h':
System.out.println(usage);
throw new Daikon.TerminationMessage();
case '?':
break; // getopt() already printed an error
default:
System.out.println("getopt() returned " + c);
break;
}
}
// Loop through each filename specified
for (int i=g.getOptind(); i<args.length; i++) {
// Get the file and make sure it exists
File file = new File(args[i]);
if (! file.exists()) {
throw new Error("File " + file + " not found.");
}
// These aren't "endsWith()" because there might be a suffix on the end
// (eg, a date).
String filename = file.toString();
if (filename.indexOf(".inv") != -1) {
if (inv_file != null) {
throw new Daikon.TerminationMessage ("multiple inv files specified", usage);
}
inv_file = file;
} else if (filename.indexOf(".dtrace") != -1) {
dtrace_files.add(filename);
} else {
throw new Error("Unrecognized argument: " + file);
}
}
if (dir_file==null) {
checkInvariants();
return;
}
// Yoav additions:
File[] filesInDir = dir_file.listFiles();
if (filesInDir == null || filesInDir.length==0)
throw new Daikon.TerminationMessage("The directory "+dir_file+" is empty", usage);
ArrayList<File> invariants = new ArrayList<File>();
for (File f: filesInDir)
if (f.toString().indexOf(".inv") != -1) invariants.add(f);
if (invariants.size()==0)
throw new Daikon.TerminationMessage("Did not find any invariant files in the directory "+dir_file, usage);
ArrayList<File> dtraces = new ArrayList<File>();
for (File f: filesInDir)
if (f.toString().indexOf(".dtrace") != -1) dtraces.add(f);
if (dtraces.size()==0)
throw new Daikon.TerminationMessage("Did not find any dtrace files in the directory "+dir_file, usage);
System.out.println("Collecting data for invariants files "+invariants+" and dtrace files "+dtraces);
dtrace_files.clear();
for (File dtrace: dtraces) {
dtrace_files.add(dtrace.toString());
}
String commaLine = "";
for (File inFile : invariants) {
String name = inFile.getName().replace(".inv","").replace(".gz","");
commaLine += ","+name;
}
outputComma.add(commaLine);
commaLine = "";
for (File inFile : invariants) {
inv_file = inFile;
failedInvariants.clear();
testedInvariants.clear();
error_cnt = 0;
output_stream = new PrintStream (new FileOutputStream (inFile.toString().replace(".inv","").replace(".gz","")+".false-positives.txt"));
checkInvariants();
output_stream.close();
int failedCount = failedInvariants.size();
int testedCount = testedInvariants.size();
String percent = toPercentage(failedCount, testedCount);
commaLine += ","+percent;
}
outputComma.add(commaLine);
System.out.println();
for (String output : outputComma)
System.out.println(output);
}
private static String toPercentage(int portion, int total) {
double s = portion * 100;
return String.format("%.2f",s /total)+"%";
}
private static void checkInvariants() throws IOException {
// Read the invariant file
PptMap ppts = FileIO.read_serialized_pptmap (inv_file, true );
//Yoav: make sure we have unique invariants
InvariantFilters fi = InvariantFilters.defaultFilters();
//Set<String> allInvariantsStr = new HashSet<String>();
Set<Invariant> allInvariants = new HashSet<Invariant>();
for (PptTopLevel ppt : ppts.all_ppts())
for (Iterator<PptSlice> i = ppt.views_iterator(); i.hasNext(); ) {
PptSlice slice = i.next();
for (Invariant inv : slice.invs) {
if (doConf &&
inv.getConfidence()<Invariant.dkconfig_confidence_limit){
// System.out.printf ("inv ignored (conf): %s:%s\n", inv.ppt.name(),
// inv.format());
continue;
}
if (doFilter && fi.shouldKeep(inv)==null) {
// System.out.printf ("inv ignored (filter): %s:%s\n",
// inv.ppt.name(), inv.format());
continue;
}
activeInvariants.add(inv);
//String n = invariant2str(ppt, inv);
//if (!allInvariants.contains(inv) && allInvariantsStr.contains(n)) throw new Daikon.TerminationMessage("Two invariants have the same ppt.name+inv.rep:"+n);
allInvariants.add(inv);
//allInvariantsStr.add(n);
}
}
// Read and process the data trace files
FileIO.Processor processor = new InvariantCheckProcessor();
Daikon.FileIOProgress progress = new Daikon.FileIOProgress();
progress.start();
progress.clear();
FileIO.read_data_trace_files (dtrace_files, ppts, processor, false);
progress.shouldStop = true;
System.out.println ();
System.out.printf ("%s: %,d errors found in %,d samples (%s)\n", inv_file,
error_cnt, sample_cnt,
toPercentage (error_cnt, sample_cnt));
int failedCount = failedInvariants.size();
int testedCount = testedInvariants.size();
String percent = toPercentage(failedCount, testedCount);
System.out.println(inv_file+": "+failedCount+" false positives, out of "+testedCount+", which is "+percent+".");
if (false) {
for (Invariant inv : failedInvariants) {
System.out.printf ("+%s:%s\n", inv.ppt.name(), inv.format());
}
}
}
/** Class to track matching ppt and its values. */
static final class EnterCall {
public PptTopLevel ppt;
public ValueTuple vt;
public EnterCall (PptTopLevel ppt, ValueTuple vt) {
this.ppt = ppt;
this.vt = vt;
}
}
public static class InvariantCheckProcessor extends FileIO.Processor {
PptMap all_ppts = null;
Map<Integer,EnterCall> call_map = new LinkedHashMap<Integer,EnterCall>();
/**
* process the sample by checking it against each existing invariant
* and issuing an error if any invariant is falsified or weakened.
*/
public void process_sample (PptMap all_ppts, PptTopLevel ppt,
ValueTuple vt, Integer nonce) {
this.all_ppts = all_ppts;
debug.fine ("processing sample from: " + ppt.name);
// Add orig and derived variables
FileIO.add_orig_variables(ppt, vt.vals, vt.mods, nonce);
FileIO.add_derived_variables(ppt, vt.vals, vt.mods);
// Intern the sample
vt = new ValueTuple(vt.vals, vt.mods);
// If this is an enter point, just remember it for later
if (ppt.ppt_name.isEnterPoint()) {
Assert.assertTrue (nonce != null);
if (dir_file!=null) {
//Yoav: I had to do a hack to handle the case that several dtrace files are concatenated together,
// and Sung's dtrace files have unterminated calls, and when concatenating two files you can have the same nonce.
// So I have to remove the nonce found from the call_map
call_map.remove(nonce);
} else
Assert.assertTrue (call_map.get (nonce) == null);
call_map.put (nonce, new EnterCall (ppt, vt));
debug.fine ("Skipping enter sample");
return;
}
// If this is an exit point, process the saved enter point
if (ppt.ppt_name.isExitPoint()) {
Assert.assertTrue (nonce != null);
EnterCall ec = call_map.get (nonce);
if (ec != null) {
call_map.remove (nonce);
debug.fine ("Processing enter sample from " + ec.ppt.name);
add (ec.ppt, ec.vt);
} else { // didn't find the enter
if (!quiet)
System.out.printf ("couldn't find enter for nonce %d at ppt %s\n",
nonce, ppt.name());
return;
}
}
add (ppt, vt);
}
private void add (PptTopLevel ppt, ValueTuple vt) {
// Add the sample to any splitters
if (ppt.has_splitters()) {
for (PptSplitter ppt_split : ppt.splitters) {
PptConditional ppt_cond = ppt_split.choose_conditional (vt);
if (ppt_cond != null)
add (ppt_cond, vt);
else
debug.fine (": sample doesn't pick conditional");
}
}
// if this is a numbered exit, apply to the combined exit as well
if (!(ppt instanceof PptConditional)
&& ppt.ppt_name.isNumberedExitPoint()) {
PptTopLevel parent = all_ppts.get (ppt.ppt_name.makeExit());
if (parent != null) {
parent.get_missingOutOfBounds (ppt, vt);
add (parent, vt);
}
}
// If the point has no variables, skip it
if (ppt.var_infos.length == 0)
return;
// We should have received sample here before, or there is nothing
// to check.
// Yoav added: It can be that the different dtrace and inv files have different program points
if (false && ppt.num_samples() <= 0)
Assert.assertTrue (ppt.num_samples() > 0, "ppt " + ppt.name
+ " has 0 samples and "
+ ppt.var_infos.length + " variables");
// Loop through each slice
slice_loop:
for (Iterator<PptSlice> i = ppt.views_iterator(); i.hasNext(); ) {
PptSlice slice = i.next();
if (debug_detail.isLoggable (Level.FINE))
debug_detail.fine (": processing slice " + slice + "vars: "
+ Debug.toString (slice.var_infos, vt));
// If any variables are missing, skip this slice
for (int j = 0; j < slice.var_infos.length; j++) {
VarInfo v = slice.var_infos[j];
int mod = vt.getModified (v);
if (v.isMissing (vt)) {
if (debug_detail.isLoggable (Level.FINE))
debug_detail.fine (": : Skipping slice, " + v.name()
+ " missing");
continue slice_loop;
}
if (v.missingOutOfBounds()) {
if (debug_detail.isLoggable (Level.FINE))
debug.fine (": : Skipping slice, " + v.name()
+ " out of bounds");
continue slice_loop;
}
}
// Loop through each invariant
for (Invariant inv : slice.invs) {
if (debug_detail.isLoggable (Level.FINE))
debug_detail.fine (": : Processing invariant: " + inv);
if (!inv.isActive()) {
if (debug_detail.isLoggable (Level.FINE))
debug_detail.fine (": : skipped non-active " + inv);
continue;
}
//Yoav added
if (!activeInvariants.contains(inv)) {
// System.out.printf ("skipping invariant %s:%s\n", inv.ppt.name(),
// inv.format());
continue;
}
//String invRep = invariant2str(ppt, inv);
testedInvariants.add(inv);
InvariantStatus status = inv.add_sample (vt, 1);
sample_cnt++;
if (status != InvariantStatus.NO_CHANGE) {
LineNumberReader lnr = FileIO.data_trace_state.reader;
String line = (lnr == null) ? "?"
: String.valueOf(lnr.getLineNumber());
if (!quiet) {
output_stream.println ("At ppt " + ppt.name + ", Invariant '"
+ inv.format() + "' invalidated by sample "
+ Debug.toString (slice.var_infos, vt)
+ "at line " + line + " in file "
+FileIO.data_trace_state.filename);
}
failedInvariants.add(inv);
activeInvariants.remove(inv);
error_cnt++;
}
}
}
}
}
private static String invariant2str(PptTopLevel ppt, Invariant inv) {
return ppt.name+" == "+inv.repr()+inv.getClass()+ inv.varNames() + ": " + inv.format();
}
}