/**
 * DaikonSimple reads a declaration file and trace file and outputs a list of likely invariants
 * using the simple incremental algorithm. Its methods parallel those of Daikon but oftentimes
 * certain checks are eliminated from DaikonSimple's methods because there is less filtering of
 * invariants and variables.
 *
 * <p>DaikonSimple was written to check the implementation of the optimizations in Daikon.
 * DaikonSimple does not use an optimizations, and its processing will produce a complete set of
 * true invariants. Daikon does have flags to "turn off" some of its optimizations but there are
 * some optimizations are built into the way Daikon processes the samples (e.g. variable hierarchy
 * and bottom up processing). In addition, we want to check the optimizations, so we don't want to
 * bypass them. In Daikon, code was written to "undo" the optimizations, so we could recover the
 * invariants that were previously filtered out or not created (see Daikon.dkconfig_undo_opts flag).
 * By comparing the output from the two, we can find problems with the optimization implementation
 * by tracking the cause of the differences.
 */
@SuppressWarnings("nullness")
public class DaikonSimple {

  // logging information
  public static final Logger debug = Logger.getLogger("daikon.DaikonSimple");

  public static final Logger debug_detail = Logger.getLogger("daikon.DaikonSimple.Detail");

  // // inv file for storing the invariants in serialized form
  // public static File inv_file = null;

  private static String usage =
      UtilMDE.join(
          new String[] {
            "",
            "Usage: java daikon.DaikonSimple [OPTION]... <decls_file> <dtrace_file>",
            "  -h, --" + Daikon.help_SWITCH,
            "      Display this usage message",
            // "  -o, <inv_file> ",
            // "      Writes output to <inv_file>",
            "  --" + 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",
          },
          lineSep);

  // a pptMap that contains all the program points
  public static PptMap all_ppts;

  public static void main(final String[] args) throws IOException, FileNotFoundException {

    try {
      mainHelper(args);
    } catch (Daikon.TerminationMessage e) {
      String message = e.getMessage();
      if (Daikon.dkconfig_show_stack_trace) e.printStackTrace();
      if (message != null) {
        System.err.println(message);
        System.exit(1);
      }
      System.exit(0);
    }
    // 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.
   *
   * <p>Difference from Daikon's mainHelper: turn off optimization flags (equality, dynamic
   * constants, NIS suppression).
   *
   * @see #main(String[])
   * @see daikon.Daikon.TerminationMessage
   * @see daikon.Daikon#mainHelper(String[])
   */
  public static void mainHelper(final String[] args) throws IOException, FileNotFoundException {

    // set up logging information
    daikon.LogHelper.setupLogs(daikon.LogHelper.INFO);

    // No optimizations used in the simple incremental algorithm so
    // optimizations are turned off.
    Daikon.use_equality_optimization = false;
    Daikon.dkconfig_use_dynamic_constant_optimization = false;
    Daikon.suppress_implied_controlled_invariants = false;
    NIS.dkconfig_enabled = false;

    // The flag tells FileIO and Daikon to use DaikonSimple
    // specific methods (e.g. FileIO.read_declaration_file).
    // When FileIO reads and processes
    // samples, it must use the SimpleProcessor rather than the
    // default Processor.
    Daikon.using_DaikonSimple = true;

    // Read command line options
    Daikon.FileOptions files = Daikon.read_options(args, usage);
    // DaikonSimple does not supply nor use the spinfo_files and map_files
    Set<File> decls_files = files.decls;
    Set<String> dtrace_files = files.dtrace;

    if ((decls_files.size() == 0) && (dtrace_files.size() == 0)) {
      throw new Daikon.TerminationMessage("No .decls or .dtrace files specified");
    }

    // Create the list of all invariant types
    Daikon.setup_proto_invs();

    // Create the program points for enter and numbered exits and
    // initializes the points (adding orig and derived variables)
    all_ppts = FileIO.read_declaration_files(decls_files);

    // Create the combined exits (and add orig and derived vars)
    // Daikon.create_combined_exits(all_ppts);

    // Read and process the data trace files
    SimpleProcessor processor = new SimpleProcessor();
    FileIO.read_data_trace_files(dtrace_files, all_ppts, processor, true);

    // System.exit(0);

    // Print out the invariants for each program point (sort first)
    for (Iterator<PptTopLevel> t = all_ppts.pptIterator(); t.hasNext(); ) {
      PptTopLevel ppt = t.next();

      // We do not need to print out program points that have not seen
      // any samples.
      if (ppt.num_samples() == 0) {
        continue;
      }
      List<Invariant> invs = PrintInvariants.sort_invariant_list(ppt.invariants_vector());
      List<Invariant> filtered_invs = Daikon.filter_invs(invs);
      // The dkconfig_quiet printing is used for creating diffs between
      // DaikonSimple
      // and Daikon's output. The second kind of printing is used for
      // debugging. Since the names of the program points are the same for both
      // Daikon and DaikonSimple, diffing the two output will result in
      // only differences in the invariants, but we can not see at which program
      // points these differing invariants appear. Using the second kind of
      // printing,
      // Daikon's output does not have the '+' in the program point name, so in
      // addition
      // to the invariants showing up in the diff, we will also see the program
      // point
      // names.

      if (Daikon.dkconfig_quiet) {
        System.out.println("====================================================");
        System.out.println(ppt.name());
      } else {
        System.out.println("===================================================+");
        System.out.println(ppt.name() + " +");
      }

      // Sometimes the program points actually differ in number of
      // samples seen due to differences in how Daikon and DaikonSimple
      // see the variable hierarchy.
      System.out.println(ppt.num_samples());

      for (Invariant inv : filtered_invs) {
        System.out.println(inv.getClass());
        System.out.println(inv);
      }
    }
  }

  /**
   * Install views and the invariants. Duplicated from PptTopLevel's version because DaikonSimple
   * needs to use its own version of slice checking code.
   *
   * <p>Difference from PptTopLevel's version: 1. canonical (leader of equality set) check of
   * variables is turned off because every variable is in its own equality set 2. debugging
   * information turned off because DaikonSimple's code is more contained 3. less constraints on the
   * slices
   *
   * @see daikon.PptTopLevel#instantiate_views_and_invariants()
   */

  // Note that some slightly inefficient code has been added to aid
  // in debugging. When creating binary and ternary views and debugging
  // is on, the outer loops will not terminate prematurely on innapropriate
  // (i.e., non-canonical) variables. This allows explicit debug statements
  // for each possible combination, simplifying determining why certain
  // slices were not created.
  //
  // Note that '///*' indicates code duplicated from PptTopLevel's
  // version but commented out because DaikonSimple does not need
  // to perform these checks
  public static void instantiate_views_and_invariants(PptTopLevel ppt) {

    // used only for debugging
    int old_num_vars = ppt.var_infos.length;
    int old_num_views = ppt.numViews();
    boolean debug_on = debug.isLoggable(Level.FINE);

    // / 1. all unary views

    // Unary slices/invariants.
    // Currently, there are no constraints on the unary
    // slices. Since we are trying to create all of the invariants, the
    // variables does not have to be a leader and can be a constant.
    // Note that the always missing check is only applicable when the
    // dynamic constants optimization is turned on (so we do not do the
    // check here).

    Vector<PptSlice> unary_views = new Vector<PptSlice>(ppt.var_infos.length);
    for (VarInfo vi : ppt.var_infos) {

      // /* if (!is_slice_ok(vi))
      // /* continue;

      PptSlice1 slice1 = new PptSlice1(ppt, vi);
      slice1.instantiate_invariants();

      unary_views.add(slice1);
    }
    ppt.addViews(unary_views);
    unary_views = null;

    // / 2. all binary views

    // Binary slices/invariants.
    Vector<PptSlice> binary_views = new Vector<PptSlice>();
    for (int i1 = 0; i1 < ppt.var_infos.length; i1++) {
      VarInfo var1 = ppt.var_infos[i1];

      // Variables can be constant and missing in DaikonSimple invariants
      // /* if (!is_var_ok_binary(var1))
      // /* continue;

      for (int i2 = i1; i2 < ppt.var_infos.length; i2++) {
        VarInfo var2 = ppt.var_infos[i2];

        // Variables can be constant and missing in DaikonSimple invariants
        // /* if (!is_var_ok_binary(var2))
        // /* continue;

        if (!(var1.compatible(var2)
            || (var1.type.isArray() && var1.eltsCompatible(var2))
            || (var2.type.isArray() && var2.eltsCompatible(var1)))) {
          continue;
        }

        PptSlice2 slice2 = new PptSlice2(ppt, var1, var2);
        slice2.instantiate_invariants();

        binary_views.add(slice2);
      }
    }
    ppt.addViews(binary_views);
    binary_views = null;

    // 3. all ternary views
    Vector<PptSlice> ternary_views = new Vector<PptSlice>();
    for (int i1 = 0; i1 < ppt.var_infos.length; i1++) {
      VarInfo var1 = ppt.var_infos[i1];

      if (!is_var_ok(var1)) continue;

      for (int i2 = i1; i2 < ppt.var_infos.length; i2++) {
        VarInfo var2 = ppt.var_infos[i2];

        if (!is_var_ok(var2)) continue;

        for (int i3 = i2; i3 < ppt.var_infos.length; i3++) {
          VarInfo var3 = ppt.var_infos[i3];

          if (!is_var_ok(var3)) continue;

          if (!is_slice_ok(var1, var2, var3)) {
            continue;
          }
          PptSlice3 slice3 = new PptSlice3(ppt, var1, var2, var3);
          slice3.instantiate_invariants();
          ternary_views.add(slice3);
        }
      }
    }

    ppt.addViews(ternary_views);

    // This method didn't add any new variables.
    assert old_num_vars == ppt.var_infos.length;
    ppt.repCheck();
  }

  // This method is exclusively for checking variables participating
  // in ternary invariants. The variable must be integer or float, and
  // can not be an array.
  public static boolean is_var_ok(VarInfo var) {

    return (var.file_rep_type.isIntegral() || var.file_rep_type.isFloat())
        && !var.rep_type.isArray();
  }

  /**
   * Returns whether or not the specified binary slice should be created. The slice should not be
   * created if the vars not compatible.
   *
   * <p>Since we are trying to create all of the invariants, the variables does not have to be a
   * leader and can be a constant. Note that the always missing check is only applicable when the
   * dynamic constants optimization is turned on (so we do not do the check here).
   *
   * @see daikon.PptTopLevel#is_slice_ok(VarInfo, VarInfo)
   */
  public static boolean is_slice_ok(VarInfo v1, VarInfo v2) {

    return v1.compatible(v2);
  }

  /**
   * Returns whether or not the specified ternary slice should be created. The slice should not be
   * created if any of the following are true - Any var is an array - Any of the vars are not
   * compatible with the others - Any var is not (integral or float)
   *
   * <p>Since we are trying to create all of the invariants, the variables does not have to be a
   * leader and can be a constant. Note that the always missing check is only applicable when the
   * dynamic constants optimization is turned on (so we do not do the check here). In addition, we
   * do want to create the reflexive ones and partially reflexive invariants.
   *
   * @see daikon.PptTopLevel#is_slice_ok(VarInfo, VarInfo, VarInfo)
   */
  public static boolean is_slice_ok(VarInfo v1, VarInfo v2, VarInfo v3) {

    // Vars must be compatible
    return (v1.compatible(v2) && v1.compatible(v3) && v2.compatible(v3));
  }

  /**
   * The Call class helps the SimpleProcessor keep track of matching enter and exit program points
   * and also object program points. Each Call object represents one entry in the dtrace file, i.e.
   * enter, exit, object entry.
   */
  static final class Call {

    public PptTopLevel ppt;

    public ValueTuple vt;

    public Call(PptTopLevel ppt, ValueTuple vt) {

      this.ppt = ppt;
      this.vt = vt;
    }
  }

  /** The SimpleProcessor class processes each sample in the dtrace file. */
  public static class SimpleProcessor extends FileIO.Processor {
    PptMap all_ppts = null;

    /** nonce -> List<Call,Call> * */
    // The first Call is the enter entry and the second is the object entry
    Map<Integer, List<Call>> call_map = new LinkedHashMap<Integer, List<Call>>();

    // Flag for whether there are out of order entries in the
    // dtrace file. For unterminated calls (enter but
    // not exit entry in the dtrace file), because DaikonSimple had
    // processed each entry separately (not bottom up like Daikon),
    // DaikonSimple applied the enter and object call before seeing the
    // exit call, which is not consistent with Daikon. Daikon does not
    // process unterminated method calls.

    // The method of holding the enter and object calls until finding
    // a matching exit call assumes:
    // - enter always comes before exit
    // - first entry in dtrace is an enter
    // - order in dtrace is enter, exit, object [for constructors] or
    // enter, object, exit, object [for methods] but not necessarily
    // sequential
    boolean wait = false;

    // pointer to last nonce so we can associate the object entry
    // with the right enter entry
    Integer last_nonce = new Integer(-1);

    /**
     * Creates a valuetuple for the receiver using the vt of the original. The method copies over
     * the values of variables shared by both program points and sets the rest of the variables in
     * the receiver's valuetuple as missing. Also, adds the orig and derived variables to the
     * receiver and returns the newly created valuetuple.
     */
    private static ValueTuple copySample(
        PptTopLevel receiver, PptTopLevel original, ValueTuple vt, int nonce) {

      // Make the vt for the receiver ppt
      //      Object values[] = new Object[receiver.num_tracevars];
      //      int mods[] = new int[receiver.num_tracevars];
      Object values[] = new Object[receiver.var_infos.length - receiver.num_static_constant_vars];
      int mods[] = new int[receiver.var_infos.length - receiver.num_static_constant_vars];

      // Build the vt for the receiver ppt by looking through the current
      // vt and filling in the gaps.
      int k = 0;
      for (Iterator<VarInfo> i = receiver.var_info_iterator(); i.hasNext(); ) {

        VarInfo var = i.next();
        if (var.is_static_constant) continue;
        boolean found = false;
        for (Iterator<VarInfo> j = original.var_info_iterator(); j.hasNext(); ) {
          VarInfo var2 = j.next();

          if (var.name().equals(var2.name())) {
            values[k] = vt.getValueOrNull(var2);
            mods[k] = vt.getModified(var2);
            found = true;
            break;
          }
        }

        if (!found) {
          values[k] = null;
          mods[k] = 2;
        }
        k++;
      }

      ValueTuple receiver_vt = new ValueTuple(values, mods);

      FileIO.add_orig_variables(receiver, receiver_vt.vals, receiver_vt.mods, nonce);
      FileIO.add_derived_variables(receiver, receiver_vt.vals, receiver_vt.mods);

      return receiver_vt;
    }

    /**
     * Process the sample by checking it against each existing invariant at the program point and
     * removing the invariant from the list of possibles if any invariant is falsified.
     */
    public void process_sample(
        PptMap all_ppts, PptTopLevel ppt, ValueTuple vt, /*@Nullable*/ Integer nonce) {
      this.all_ppts = all_ppts;

      // Add samples to 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);

      // DaikonSimple must make the object program point manually because
      // the new Chicory produced dtrace files do not contain object ppts
      // in the dtrace part of the file (the program point is declared).

      // Make the object ppt
      PptName ppt_name = ppt.ppt_name;

      PptTopLevel object_ppt = null;
      PptTopLevel class_ppt = null;
      ValueTuple object_vt = null;
      ValueTuple class_vt = null;

      if ((ppt_name.isEnterPoint() && !ppt_name.isConstructor()) || ppt_name.isExitPoint()) {
        object_ppt = all_ppts.get(ppt_name.makeObject());
        class_ppt = all_ppts.get(ppt_name.makeClassStatic());
      }

      // C programs do not have object ppts
      // check whether the ppt is a static or instance method
      // that decides whether the sample is copied over to the object and/or
      // class ppt
      if (object_ppt != null) {

        // the check assumes that static fields are not stored first in the
        // object ppt
        if (ppt.find_var_by_name(object_ppt.var_infos[0].name()) != null) {
          // object and class ppt should be created
          object_vt = copySample(object_ppt, ppt, vt, nonce);

          if (class_ppt != null) {
            class_vt = copySample(class_ppt, ppt, vt, nonce);
          }

        } else {
          // only class ppt should be created
          if (class_ppt != null) {
            class_vt = copySample(class_ppt, ppt, vt, nonce);
          }

          object_vt = null;
          object_ppt = null;
        }
      }

      // If this is an enter point, just remember it for later
      if (ppt_name.isEnterPoint()) {
        assert nonce != null;
        assert call_map.get(nonce) == null;
        List<Call> value = new ArrayList<Call>();
        value.add(new Call(ppt, vt));

        if (object_ppt != null) {
          value.add(new Call(object_ppt, object_vt));
        }

        if (class_ppt != null) {
          value.add(new Call(class_ppt, class_vt));
        }

        call_map.put(nonce, value);
        last_nonce = nonce;
        wait = true;
        return;
      }

      // If this is an exit point, process the saved enter (and sometimes
      // object) point
      if (ppt_name.isExitPoint()) {
        assert nonce != null;
        List<Call> value = call_map.remove(nonce);

        add(ppt, vt, nonce);

        for (Call ec : value) {
          add(ec.ppt, ec.vt, nonce);
        }
        wait = false;
      }

      if (object_ppt != null) add(object_ppt, object_vt, nonce); // apply object vt

      if (class_ppt != null) add(class_ppt, class_vt, nonce);
    }

    // The method iterates through all of the invariants in the ppt
    // and manually adds the sample to the invariant and removing the
    // invariant if it is falsified

    private void add(PptTopLevel ppt, ValueTuple vt, int nonce) {

      // if this is a numbered exit, apply to the combined exit as well
      if (ppt.ppt_name.isNumberedExitPoint()) {

        // Daikon.create_combined_exits(all_ppts);
        PptTopLevel parent = all_ppts.get(ppt.ppt_name.makeExit());
        if (parent != null) {
          parent.get_missingOutOfBounds(ppt, vt);
          add(parent, vt, nonce);

        } else {
          // make parent and apply

          // this is a hack. it should probably filter out orig and derived
          // vars instead of taking the first n.
          int len = ppt.num_tracevars + ppt.num_static_constant_vars;
          VarInfo[] exit_vars = new VarInfo[len];
          for (int j = 0; j < len; j++) {
            exit_vars[j] = new VarInfo(ppt.var_infos[j]);
            exit_vars[j].varinfo_index = ppt.var_infos[j].varinfo_index;
            exit_vars[j].value_index = ppt.var_infos[j].value_index;
            exit_vars[j].equalitySet = null;
          }

          parent = new PptTopLevel(ppt.ppt_name.makeExit().getName(), exit_vars);
          Daikon.init_ppt(parent, all_ppts);
          all_ppts.add(parent);
          parent.get_missingOutOfBounds(ppt, vt);
          add(parent, vt, nonce);
        }
      }

      // If the point has no variables, skip it
      if (ppt.var_infos.length == 0) {
        // The sample should be skipped but Daikon does not do this so
        // DaikonSimple will not do this to be consistent.
        // The better idea is for Daikon to assert that these valuetuples are
        // empty and then skip the sample.
        assert vt.size() == 0;
        return;
      }

      // Instantiate slices and invariants if this is the first sample
      if (ppt.num_samples() == 0) {
        instantiate_views_and_invariants(ppt);
      }

      // manually inc the sample number because DaikonSimple does not
      // use any of PptTopLevel's add methods which increase the sample
      // number
      ppt.incSampleNumber();

      // Loop through each slice
      for (Iterator<PptSlice> i = ppt.views_iterator(); i.hasNext(); ) {
        PptSlice slice = i.next();
        Iterator<Invariant> k = slice.invs.iterator();
        boolean missing = false;

        for (VarInfo v : slice.var_infos) {
          // If any var has encountered out of array bounds values,
          // stop all invariants in this slice. The presumption here is that
          // an index out of bounds implies that the derived variable (eg a[i])
          // doesn't really make any sense (essentially that i is not a valid
          // index for a). Invariants on the derived variable are thus not
          // relevant.
          // If any variables are out of bounds, remove the invariants
          if (v.missingOutOfBounds()) {
            while (k.hasNext()) {
              Invariant inv = k.next();
              k.remove();
            }
            missing = true;
            break;
          }

          // If any variables are missing, skip this slice
          if (v.isMissing(vt)) {
            missing = true;
            break;
          }
        }

        // keep a list of the falsified invariants
        if (!missing) {
          while (k.hasNext()) {

            Invariant inv = k.next();
            Invariant pre_inv = inv.clone();
            for (VarInfo vi : inv.ppt.var_infos) {
              assert vt.getValue(vi) != null : vi;
            }
            if (inv.ppt instanceof PptSlice2) assert inv.ppt.var_infos.length == 2;
            InvariantStatus status = inv.add_sample(vt, 1);
            if (status == InvariantStatus.FALSIFIED) {
              k.remove();
            }
          }
        }

        // update num_samples and num_values of a slice manually
        // because DaikonSimple does not call any of PptTopLevel's
        // add methods
        for (int j = 0; j < vt.vals.length; j++) {
          if (!vt.isMissing(j)) {
            ValueSet vs = ppt.value_sets[j];
            vs.add(vt.vals[j]);
          }
        }
        ppt.mbtracker.add(vt, 1);
      }
    }
  }
}
/**
 * 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();
  }
}