001package daikon;
002
003import daikon.inv.Invariant;
004import java.util.Arrays;
005import java.util.List;
006import java.util.Set;
007import java.util.logging.Level;
008import java.util.logging.Logger;
009import org.checkerframework.checker.initialization.qual.UnknownInitialization;
010import org.checkerframework.checker.nullness.qual.NonNull;
011import org.checkerframework.checker.nullness.qual.Nullable;
012import org.plumelib.util.ArraysPlume;
013
014/**
015 * Debug class used with the logger to create standardized output. It can be setup to track
016 * combinations of classes, program points, and variables. The most common class to track is an
017 * invariant, but any class can be used.
018 *
019 * <p>This allows detailed information about a particular class/ppt/variable combination to be
020 * printed without getting lost in a mass of other information (which is a particular problem in
021 * Daikon due to the volume of data considered).
022 *
023 * <p>Note that each of the three items (class, ppt, variable) must match in order for printing to
024 * occur.
025 */
026public final class Debug {
027
028  /** Debug Logger. */
029  public static final Logger debugTrack = Logger.getLogger("daikon.Debug");
030
031  /**
032   * List of classes for logging. Each name listed is compared to the fully qualified class name. If
033   * it matches (shows up anywhere in the class name) it will be included in debug prints. This is
034   * not a regular expression match
035   *
036   * @see #log(Logger, Class, Ppt, String)
037   */
038  public static String[] debugTrackClass = {
039    // "Bound",
040    // "DynamicConstants",
041    // "EltNonZero",
042    // "EltNonZeroFloat",
043    // "EltOneOf",
044    // "Equality",
045    // "FunctionBinary",
046    // "IntEqual",
047    // "IntGreaterEqual",
048    // "IntGreaterThan",
049    // "IntLessEqual",
050    // "IntLessThan",
051    // "IntNonEqual",
052    // "LinearBinary",
053    // "LowerBound",
054    // "Member",
055    // "NonZero",
056    // "OneOfSequence",
057    // "PptSlice",
058    // "PptSlice2",
059    // "PptSliceEquality",
060    // "PptTopLevel",
061    // "SeqIndexComparison",
062    // "SeqIndexNonEqual",
063    // "SeqIntEqual",
064    // "SeqSeqIntEqual",
065    // "NonZero",
066    // "FunctionBinary",
067    // "OneOfSequence",
068    // "IntLessEqual",
069    // "IntGreaterEqual",
070    // "IntLessThan",
071    // "IntGreaterThan",
072    // "IntNonEqual",
073    // "Member",
074    // "FunctionBinary"
075    // "EltNonZero",
076    // "SeqSeqIntGreaterThan",
077    // "SeqSeqIntLessThan",
078    // "SubSet",
079    // "SuperSet",
080    // "EltOneOf",
081    // "Bound",
082    // "SeqSeqIntLessThan",
083    // "SeqSeqIntGreaterThan",
084    // "OneOf"
085    // "StringEqual",
086    // "StringLessThan",
087    // "StringGreaterThan",
088    // "Modlong_zxy",
089    // "UpperBound",
090  };
091
092  /**
093   * Restrict function binary prints to the specified method. Implementation is in the
094   * FunctionBinary specific log functions. If null, there is no restriction (all function binary
095   * methods are printed). See Functions.java for a list of function names.
096   */
097  public static @Nullable String function_binary_method = null
098      // "java.lang.Math.max("
099      // "java.lang.Math.min("
100      // "org.plumelib.util.MathPlume.logicalXor("
101      // "org.plumelib.util.MathPlume.gcd("
102      ;
103
104  /**
105   * List of Ppts for logging. Each name listed is compared to the full program point name. If it
106   * matches (shows up anywhere in the ppt name) it will be included in the debug prints. This is
107   * not a regular expression match.
108   *
109   * @see #log(Logger, Class, Ppt, String)
110   */
111  public static String[] debugTrackPpt = {
112    // "DataStructures.DisjSets.unionDisjoint(int, int):::EXIT",
113    // "DataStructures.StackAr.StackAr(int):::EXIT",
114    // "DataStructures.StackAr.makeEmpty():::EXIT",
115    // "DataStructures.StackAr.makeEmpty()V:::ENTER",
116    // "DataStructures.StackAr.top():::EXIT74",
117    // "GLOBAL",
118    // "std.main(int;char **;)int:::EXIT",
119  };
120
121  /**
122   * List of variable names for logging. Each name listed is compared to each variable in turn. If
123   * each matches exactly it will be included in track debug prints. This is not a regular
124   * expression match. Note that the number of variables must match the slice exactly.
125   *
126   * @see #log(Logger, Class, Ppt, String)
127   */
128  public static String[][] debugTrackVars = {
129    // {"::printstats"},
130    // {"misc.Fib.STEPS", "orig(misc.Fib.a)"},
131    // {"misc.Fib.a", "misc.Fib.STEPS"},
132    // {"return"},
133  };
134
135  // Ordinarily, a client would have to supply a Class, Ppt, and
136  // List<Varinfo> with each call to a log method.  But the client can
137  // instead provide those values once (they are cached in these variables)
138  // and omit them in subsequent calls.  The subsequent calls can use forms
139  // of log() that take fewer arguments.
140
141  /**
142   * True if the cached values should be printed --- that is, they match what is currently being
143   * debugged.
144   */
145  public boolean cache_match = true;
146
147  // Note that throughout this file, inv_class is not necessarily a
148  // subclass of Invariant -- for instance, it might be a subclass of
149  // BinaryDerivationFactory.
150  /** cached class: class to use by default when calling variants of log() with few arguments. */
151  public @Nullable Class<?> cache_class;
152
153  /** Cached ppt: ppt to use by default when calling variants of log() with few arguments. */
154  public @Nullable Ppt cache_ppt;
155
156  /**
157   * Cached variables: variables to use by default when calling variants of log() with few
158   * arguments.
159   */
160  public VarInfo @Nullable [] cache_vis;
161
162  /**
163   * Ordinarily, a client would have to supply a Class, Ppt, and {@code List<Varinfo>} with each
164   * call to a log method. This constructor sets as defaults c, ppt, and whatever variable (if any)
165   * from vis that is on the debugTrackVar list. Essentially this creates a debug object that will
166   * print if any of the variables in vis are being tracked (and c and ppt match).
167   */
168  public Debug(Class<?> c, Ppt ppt, VarInfo[] vis) {
169    set(c, ppt, vis);
170  }
171
172  /**
173   * Returns a Debug object if the specified class, ppt, and vis match what is being tracked.
174   * Otherwise, return NULL. Preferred over calling the constructor directly, since it doesn't
175   * create the object if it doesn't have to.
176   */
177  public static @Nullable Debug newDebug(Class<?> c, Ppt ppt, VarInfo[] vis) {
178    if (logOn() && class_match(c) && ppt_match(ppt) && var_match(vis)) {
179      return new Debug(c, ppt, vis);
180    } else {
181      return null;
182    }
183  }
184
185  /**
186   * Ordinarily, a client would have to supply a Class, Ppt, and {@code List<Varinfo>} with each
187   * call to a log method. This constructor sets as defaults c, ppt, and whatever variable (if any)
188   * from vis that is on the debugTrackVar list. Essentially this creates a debug object that will
189   * print if any of the variables in vis are being tracked (and c and ppt match).
190   */
191  public Debug(Class<?> c, Ppt ppt, List<VarInfo> vis) {
192
193    VarInfo v = visTracked(vis);
194    if (v != null) {
195      set(c, ppt, new VarInfo[] {v});
196    } else if (vis.size() > 0) {
197      set(c, ppt, new VarInfo[] {vis.get(0)});
198    } else {
199      set(c, ppt, null);
200    }
201  }
202
203  /**
204   * Looks for each of the variables in vis in the DebugTrackVar list. If any match, returns that
205   * variable. Returns null if there are no matches.
206   */
207  public @Nullable VarInfo visTracked(@UnknownInitialization Debug this, List<VarInfo> vis) {
208
209    for (VarInfo v : vis) {
210      Set<VarInfo> evars = null;
211      if (v.equalitySet != null) {
212        evars = v.equalitySet.getVars();
213      }
214      if (evars != null) {
215        for (VarInfo ev : evars) {
216          for (int k = 0; k < debugTrackVars.length; k++) {
217            if (ev.name().equals(debugTrackVars[k][0])) {
218              return v;
219            }
220          }
221        }
222      }
223    }
224
225    return null;
226  }
227
228  private static @Nullable String[] ourvars = new String[3];
229
230  private static final VarInfo[] vis1 = new VarInfo[1];
231  private static final VarInfo[] vis2 = new VarInfo[2];
232  private static final VarInfo[] vis3 = new VarInfo[3];
233
234  public static VarInfo[] vis(VarInfo v1) {
235    vis1[0] = v1;
236    return vis1;
237  }
238
239  public static VarInfo[] vis(VarInfo v1, VarInfo v2) {
240    vis2[0] = v1;
241    vis2[1] = v2;
242    return vis2;
243  }
244
245  public static VarInfo[] vis(VarInfo v1, VarInfo v2, VarInfo v3) {
246    vis3[0] = v1;
247    vis3[1] = v2;
248    vis3[2] = v3;
249    return vis3;
250  }
251
252  /**
253   * Sets the cache for class, ppt, and vis so that future calls to log don't have to set them -- in
254   * other words, future calls can use the versions of log with fewer arguments.
255   */
256  void set(
257      @UnknownInitialization Debug this,
258      @Nullable Class<?> c,
259      @Nullable Ppt ppt,
260      VarInfo @Nullable [] vis) {
261    cache_class = c;
262    cache_ppt = ppt;
263    cache_vis = vis;
264    if (c == null) {
265      System.out.println("Class = null");
266    }
267    if (ppt == null) {
268      System.out.println("ppt = null");
269    }
270    if (vis == null) {
271      System.out.println("vis = null");
272    } else {
273      for (int i = 0; i < vis.length; i++) {
274        if (vis[i] == null) {
275          System.out.println("vis[" + i + "] == null");
276        }
277      }
278    }
279    cache_match = class_match(c) && ppt_match(ppt) && var_match(vis);
280  }
281
282  /**
283   * When true, perform detailed internal checking. These are essentially additional, possibly
284   * costly assert statements.
285   */
286  public static boolean dkconfig_internal_check = false;
287
288  /** If true, show stack traces for errors such as file format errors. */
289  public static boolean dkconfig_show_stack_trace = false;
290
291  /**
292   * Determines whether or not traceback information is printed for each call to log.
293   *
294   * @see #log(Logger, Class, Ppt, String)
295   */
296  public static boolean dkconfig_showTraceback = false;
297
298  /**
299   * Determines whether or not detailed info (such as from {@code add_modified}) is printed.
300   *
301   * @see #log(Logger, Class, Ppt, String)
302   * @see #logDetail()
303   */
304  public static boolean dkconfig_logDetail = false;
305
306  /**
307   * Returns whether or not detailed logging is on. Note that this check is not performed inside the
308   * logging calls themselves, it must be performed by the caller.
309   *
310   * @see #log(Logger, Class, Ppt, String)
311   * @see #logOn()
312   */
313  public static final boolean logDetail() {
314    return dkconfig_logDetail && debugTrack.isLoggable(Level.FINE);
315  }
316
317  /**
318   * Returns whether or not logging is on.
319   *
320   * @see #log(Logger, Class, Ppt, String)
321   */
322  public static final boolean logOn() {
323    return debugTrack.isLoggable(Level.FINE);
324  }
325
326  /**
327   * Logs the cached class, cached ppt, cached variables and the specified msg via the logger as
328   * described in {@link #log(Logger, Class, Ppt, VarInfo[], String)}.
329   */
330  public void log(Logger debug, String msg) {
331    if (cache_match) {
332      log(debug, cache_class, cache_ppt, cache_vis, msg);
333    }
334  }
335
336  /**
337   * Logs a description of the class, ppt, ppt variables and the specified msg via the logger as
338   * described in {@link #log(Logger, Class, Ppt, VarInfo[], String)}.
339   */
340  public static void log(Logger debug, Class<?> inv_class, @Nullable Ppt ppt, String msg) {
341    if (ppt == null) {
342      log(debug, inv_class, ppt, null, msg);
343    } else {
344      log(debug, inv_class, ppt, ppt.var_infos, msg);
345    }
346  }
347
348  /**
349   * Logs a description of the class, ppt, variables and the specified msg via the logger. The
350   * class, ppt, and variables are checked against those described in {@link #debugTrackClass},
351   * {@link #debugTrackPpt}, and {@link #debugTrackVars}. Only those that match are printed.
352   * Variables will match if they are in the same equality set. The information is written as:
353   *
354   * <p>{@code class: ppt : var1 : var2 : var3 : msg }
355   *
356   * <p>Note that if {@link #debugTrack} is not enabled then nothing is printed. It is somewhat
357   * faster to check {@link #logOn()} directly rather than relying on the check here.
358   *
359   * <p>Other versions of this method (noted below) work without the Logger parameter and take
360   * class, ppt, and vis from the cached values, which were set by the constructor or by the set()
361   * method.
362   *
363   * @param debug a second Logger to query if debug tracking is turned off or does not match. If
364   *     this logger is enabled, the same information will be written to it. Note that the
365   *     information is never written to both loggers.
366   * @param inv_class the class. Can be obtained in a static context by ClassName.class
367   * @param ppt program point
368   * @param vis variables at the program point. These are sometimes different from the ones in the
369   *     ppt itself.
370   * @param msg string message to log
371   * @see #logOn()
372   * @see #logDetail()
373   * @see #log(Class, Ppt, VarInfo[], String)
374   * @see #log(Class, Ppt, String)
375   * @see #log(Logger, String)
376   * @see #log(String)
377   */
378  public static void log(
379      Logger debug,
380      @Nullable Class<?> inv_class,
381      @Nullable Ppt ppt,
382      VarInfo @Nullable [] vis,
383      String msg) {
384
385    // Try to log via the logger first
386    if (log(inv_class, ppt, vis, msg)) {
387      return;
388    }
389
390    // If debug isn't turned on, there is nothing to do
391    if (!debug.isLoggable(Level.FINE)) {
392      return;
393    }
394
395    // Get the non-qualified class name
396    String class_str;
397    if (inv_class == null) {
398      // when is inv_class null?
399      class_str = "null";
400    } else {
401      @SuppressWarnings("nullness") // getPackage(): invariant class always has a package
402      @NonNull String packageName = inv_class.getPackage().getName() + ".";
403      class_str = inv_class.getName().replace(packageName, "");
404    }
405
406    String vars = "";
407    if (vis == null) {
408      System.out.println("no var infos");
409    } else {
410
411      // Get a string with all of the variable names.  Each is separated by ': '
412      // 3 variable slots are always setup for consistency
413      for (VarInfo v : vis) {
414        vars += v.name() + ": ";
415      }
416      for (int i = vis.length; i < 3; i++) {
417        vars += ": ";
418      }
419    }
420
421    // Figure out the sample count if possible
422    String samp_str = "";
423    if (ppt instanceof PptSlice) {
424      PptSlice pslice = (PptSlice) ppt;
425      samp_str = " s" + pslice.num_samples();
426    }
427
428    String line = " line=" + FileIO.get_linenum();
429
430    String name = "ppt is null";
431    if (ppt != null) {
432      name = ppt.name();
433    }
434
435    debug.fine(class_str + ": " + name + samp_str + line + ": " + vars + msg);
436    if (dkconfig_showTraceback) {
437      Throwable stack = new Throwable("debug traceback");
438      stack.fillInStackTrace();
439      stack.printStackTrace();
440    }
441  }
442
443  /**
444   * Logs a description of the cached class, ppt, and variables and the specified msg via the logger
445   * as described in {@link #log(Logger, Class, Ppt, VarInfo[], String)}
446   *
447   * @return whether or not it logged anything
448   */
449  public boolean log(String msg) {
450    if (!logOn()) {
451      return false;
452    }
453    return log(cache_class, cache_ppt, cache_vis, msg);
454  }
455
456  /**
457   * Logs a description of the class, ppt, ppt variables and the specified msg via the logger as
458   * described in {@link #log(Logger, Class, Ppt, VarInfo[], String)}.
459   *
460   * @return whether or not it logged anything
461   */
462  // 3-argument form
463  public static boolean log(
464      Class<?> inv_class, @UnknownInitialization(PptTopLevel.class) Ppt ppt, String msg) {
465
466    return log(inv_class, ppt, ppt.var_infos, msg);
467  }
468
469  /**
470   * Logs a description of the class, ppt, variables and the specified msg via the logger as
471   * described in {@link #log(Logger, Class, Ppt, String)}. Accepts vis because sometimes the
472   * variables are different from those in the ppt.
473   *
474   * @param inv_class the class to be logged
475   * @param ppt the program point to be logged
476   * @param vis the VarInfos for the ppt
477   * @param msg the message to be logged
478   * @return true if it logged something
479   */
480  // 4-argument form
481  public static boolean log(
482      @Nullable Class<?> inv_class,
483      @Nullable @UnknownInitialization(PptTopLevel.class) Ppt ppt,
484      VarInfo @Nullable [] vis,
485      String msg) {
486
487    if (!debugTrack.isLoggable(Level.FINE)) {
488      return false;
489    }
490
491    // Make sure the class matches
492    if (!class_match(inv_class)) {
493      return false;
494    }
495
496    // Make sure the Ppt matches
497    if (!ppt_match(ppt)) {
498      return false;
499    }
500
501    // Make sure the variables match
502    if (!var_match(vis)) {
503      return false;
504    }
505
506    // Get the non-qualified class name
507    String class_str = "null";
508    if (inv_class != null) {
509      @SuppressWarnings("nullness") // getPackage(): invariant class always has a package
510      @NonNull String packageName = inv_class.getPackage().getName() + ".";
511      class_str = inv_class.getName().replace(packageName, "");
512    }
513
514    // Get a string with all of the variable names.  Each is separated by ': '.
515    // 3 variable slots are always setup for consistency.
516    String vars = "";
517    if (vis != null) {
518      int numvars = vis.length;
519      for (int i = 0; i < numvars; i++) {
520        VarInfo v = vis[i];
521        vars += v.name();
522        if (ourvars[i] != null) {
523          vars += " {" + ourvars[i] + "}";
524        }
525        vars += ": ";
526      }
527      for (int i = numvars; i < 3; i++) {
528        vars += ": ";
529      }
530    }
531
532    // Figure out the sample count if possible
533    String samp_str = "";
534    if (ppt instanceof PptSlice) {
535      PptSlice pslice = (PptSlice) ppt;
536      samp_str = " s" + pslice.num_samples();
537    }
538
539    String line = " line=" + FileIO.get_linenum();
540
541    debugTrack.fine(
542        class_str
543            + ": "
544            + ((ppt == null) ? "null" : ppt.name())
545            + samp_str
546            + line
547            + ": "
548            + vars
549            + msg);
550    if (dkconfig_showTraceback) {
551      Throwable stack = new Throwable("debug traceback");
552      stack.fillInStackTrace();
553      stack.printStackTrace();
554    }
555
556    return true;
557  }
558
559  /** Returns whether or not the specified class matches the classes being tracked. */
560  public static boolean class_match(@Nullable Class<?> inv_class) {
561
562    if ((debugTrackClass.length > 0) && (inv_class != null)) {
563      return strContainsElem(inv_class.getName(), debugTrackClass);
564    }
565    return true;
566  }
567
568  /** Returns whether or not the specified ppt matches the ppts being tracked. */
569  public static boolean ppt_match(
570      @Nullable @UnknownInitialization(daikon.PptTopLevel.class) Ppt ppt) {
571
572    if (debugTrackPpt.length > 0) {
573      return (ppt != null) && strContainsElem(ppt.name(), debugTrackPpt);
574    }
575    return true;
576  }
577
578  /**
579   * Returns whether or not the specified vars match the ones being tracked. Also, sets
580   * Debug.ourvars with the names of the variables matched if they are not the leader of their
581   * equality sets.
582   */
583  public static boolean var_match(VarInfo @Nullable [] vis) {
584
585    if (debugTrackVars.length == 0) {
586      return true;
587    }
588    if (vis == null) {
589      return false;
590    }
591
592    boolean match = false;
593
594    // Loop through each set of specified debug variables.
595    outer:
596    for (String[] cv : debugTrackVars) {
597      if (cv.length != vis.length) {
598        continue;
599      }
600      for (int j = 0; j < ourvars.length; j++) {
601        ourvars[j] = null;
602      }
603
604      // Flags to insure that we don't match a variable more than once
605      boolean[] used = {false, false, false};
606
607      // Loop through each variable in this set of debug variables
608      for (int j = 0; j < cv.length; j++) {
609        boolean this_match = false;
610
611        // Loop through each variable at this point
612        eachvis:
613        for (int k = 0; k < vis.length; k++) {
614
615          // Get the matching equality set
616          Set<VarInfo> evars = null;
617          if (vis[k].equalitySet != null) {
618            evars = vis[k].equalitySet.getVars();
619          }
620
621          // If there is an equality set
622          if ((evars != null) && vis[k].isCanonical()) {
623
624            // Loop through each variable in the equality set
625            for (VarInfo v : evars) {
626              if (!used[k] && (cv[j].equals("*") || cv[j].equals(v.name()))) {
627                used[k] = true;
628                this_match = true;
629                if (!cv[j].equals(vis[j].name())) {
630                  ourvars[j] = v.name();
631                  if (j != k) {
632                    ourvars[j] += " (" + j + "/" + k + ")";
633                  }
634                  if (v.isCanonical()) {
635                    ourvars[j] += " (Leader)";
636                  }
637                }
638                break eachvis;
639              }
640            }
641          } else { // sometimes, no equality set
642            if (cv[j].equals("*") || cv[j].equals(vis[k].name())) {
643              this_match = true;
644            }
645          }
646        }
647        if (!this_match) {
648          continue outer;
649        }
650      }
651      match = true;
652      break outer;
653    }
654
655    return match;
656  }
657
658  /** Looks for an element in arr that is a substring of str. */
659  private static boolean strContainsElem(String str, String[] arr) {
660
661    for (String elt : arr) {
662      if (str.indexOf(elt) >= 0) {
663        return true;
664      }
665    }
666    return false;
667  }
668
669  /**
670   * Looks through entire ppt tree and checks for any items we are interested in. If found, prints
671   * them out.
672   */
673  public static void check(PptMap all_ppts, String msg) {
674
675    boolean found = false;
676
677    for (PptTopLevel ppt : all_ppts.ppt_all_iterable()) {
678      if (ppt_match(ppt)) {
679        debugTrack.fine("Matched ppt '" + ppt.name() + "' at " + msg);
680      }
681      for (PptSlice slice : ppt.views_iterable()) {
682        for (int k = 0; k < slice.invs.size(); k++) {
683          Invariant inv = slice.invs.get(k);
684          if (inv.log("%s: found #%s=%s in slice %s", msg, k, inv.format(), slice)) {
685            found = true;
686          }
687        }
688      }
689    }
690    if (!found) {
691      debugTrack.fine("Found no points at '" + msg + "'");
692    }
693  }
694
695  /** Returns a string containing the integer variables and their values. */
696  public static String int_vars(PptTopLevel ppt, ValueTuple vt) {
697
698    String out = "";
699
700    for (VarInfo v : ppt.var_infos) {
701      if (!v.isCanonical()) {
702        continue;
703      }
704      if (v.file_rep_type != ProglangType.INT) {
705        continue;
706      }
707      out += v.name() + "=" + toString(v.getValueOrNull(vt)) + " [" + vt.getModified(v) + "]: ";
708    }
709    return out;
710  }
711
712  /**
713   * Returns a string containing the variable values for any variables that are currently being
714   * tracked in ppt. The string is of the form 'v1 = val1: v2 = val2, etc.
715   */
716  public static String related_vars(PptTopLevel ppt, ValueTuple vt) {
717
718    String out = "";
719
720    for (VarInfo v : ppt.var_infos) {
721      for (String[] cv : debugTrackVars) {
722        for (String cv_elt : cv) {
723          if (cv_elt.equals(v.name())) {
724            Object val = v.getValue(vt);
725            int mod = vt.getModified(v);
726            out += v.name() + "=";
727            out += toString(val);
728            if ((mod == ValueTuple.MISSING_FLOW) || (mod == ValueTuple.MISSING_NONSENSICAL)) {
729              out += " (missing)";
730            }
731            if (v.missingOutOfBounds()) {
732              out += " (out of bounds)";
733            }
734            if (v.equalitySet != null) {
735              if (!v.isCanonical()) {
736                out += " (leader=" + v.canonicalRep().name() + ")";
737              }
738            }
739            // out += " mod=" + mod;
740            out += ": ";
741          }
742        }
743      }
744    }
745
746    return out;
747  }
748
749  /** Like Object.toString(), but handles null, and has special handling for arrays. */
750  public static String toString(@Nullable Object val) {
751    if (val == null) {
752      return "none";
753    }
754    if (val instanceof String) {
755      return "\"" + val + "\"";
756    }
757    if (val instanceof VarInfo[]) {
758      return Arrays.toString((VarInfo[]) val);
759    }
760    if (val instanceof String[]) {
761      return Arrays.toString((String[]) val);
762    }
763    if (val instanceof boolean[]) {
764      return Arrays.toString((boolean[]) val);
765    }
766    if (val instanceof byte[]) {
767      return Arrays.toString((byte[]) val);
768    }
769    if (val instanceof char[]) {
770      return Arrays.toString((char[]) val);
771    }
772    if (val instanceof double[]) {
773      return Arrays.toString((double[]) val);
774    }
775    if (val instanceof float[]) {
776      return Arrays.toString((float[]) val);
777    }
778    if (val instanceof int[]) {
779      return Arrays.toString((int[]) val);
780    }
781    if (val instanceof long[]) {
782      return Arrays.toString((long[]) val);
783    }
784    if (val instanceof short[]) {
785      return Arrays.toString((short[]) val);
786    }
787    return val.toString();
788  }
789
790  /**
791   * Returns a string containing each variable and its value.
792   *
793   * <p>The string is of the form "v1 = val1: v2 = val2, ...".
794   */
795  public static String toString(VarInfo[] vis, ValueTuple vt) {
796
797    StringBuilder out = new StringBuilder();
798
799    for (VarInfo v : vis) {
800      Object val = v.getValue(vt);
801      // int mod = vt.getModified(v);
802      out.append(v.name());
803      out.append("=");
804      out.append(toString(val));
805      if (v.isMissing(vt)) {
806        out.append(" (missing)");
807      }
808      if (v.missingOutOfBounds()) {
809        out.append(" (out of bounds)");
810      }
811      if (v.equalitySet != null) {
812        if (!v.isCanonical()) {
813          out.append(" (leader=" + v.canonicalRep().name() + ")");
814        }
815      }
816      out.append(": ");
817    }
818
819    return out.toString();
820  }
821
822  /**
823   * Parses the specified argument to {@code --track} and sets up the track arrays accordingly. The
824   * syntax of the argument is
825   *
826   * <pre>{@code class|class|...<var,var,var>@ppt}</pre>
827   *
828   * As shown, multiple class arguments can be specified separated by pipe symbols (|). The
829   * variables are specified in angle brackets ({@code <>}) and the program point is preceeded by an
830   * at sign (@). Each is optional and can be left out. The add_track routine can be called multiple
831   * times. An invariant that matches any of the specifications will be tracked.
832   */
833  public static @Nullable String add_track(String def) {
834
835    String classes = null;
836    String vars = null;
837    String ppt = null;
838
839    // Get the classes, vars, and ppt
840    int var_start = def.indexOf('<');
841    int ppt_start = def.indexOf("@");
842    if ((var_start == -1) && (ppt_start == -1)) {
843      classes = def;
844    } else if (var_start != -1) {
845      if (var_start > 0) {
846        classes = def.substring(0, var_start);
847      }
848      if (ppt_start == -1) {
849        vars = def.substring(var_start + 1, def.length() - 1);
850      } else {
851        vars = def.substring(var_start + 1, ppt_start - 1);
852        ppt = def.substring(ppt_start + 1, def.length());
853      }
854    } else {
855      if (ppt_start > 0) {
856        classes = def.substring(0, ppt_start);
857      }
858      ppt = def.substring(ppt_start + 1, def.length());
859    }
860
861    // If classes were specified, get each class
862    if (classes != null) {
863      String[] class_arr = classes.split("\\|");
864      debugTrackClass = ArraysPlume.concat(debugTrackClass, class_arr);
865    }
866
867    // If vars were specified, get each var
868    if (vars != null) {
869      String[] var_arr = vars.split(", *");
870      String[][] new_var = new String[debugTrackVars.length + 1][];
871      for (int ii = 0; ii < debugTrackVars.length; ii++) {
872        new_var[ii] = debugTrackVars[ii];
873      }
874      new_var[debugTrackVars.length] = var_arr;
875      debugTrackVars = new_var;
876    }
877
878    // if a ppt was specified, add it to the array of tracked ppts
879    if (ppt != null) {
880      String[] newPpt = new String[] {ppt};
881      debugTrackPpt = ArraysPlume.concat(debugTrackPpt, newPpt);
882    }
883
884    System.out.println();
885    debugTrack.fine("After --track: " + def);
886    debugTrack.fine("Track Classes: " + ArraysPlume.toString(debugTrackClass, false));
887    String vars_out = "";
888    for (int ii = 0; ii < debugTrackVars.length; ii++) {
889      vars_out += Arrays.toString(debugTrackVars[ii]) + " ";
890    }
891    debugTrack.fine("Track Vars: " + vars_out);
892    debugTrack.fine("Track Ppts: " + ArraysPlume.toString(debugTrackPpt, false));
893
894    return null;
895  }
896}