001package daikon;
002
003import daikon.inv.Invariant;
004import java.util.Arrays;
005import java.util.List;
006import java.util.Set;
007import java.util.StringJoiner;
008import java.util.logging.Level;
009import java.util.logging.Logger;
010import org.checkerframework.checker.initialization.qual.UnknownInitialization;
011import org.checkerframework.checker.nullness.qual.NonNull;
012import org.checkerframework.checker.nullness.qual.Nullable;
013import org.plumelib.util.ArraysPlume;
014
015/**
016 * Debug class used with the logger to create standardized output. It can be setup to track
017 * combinations of classes, program points, and variables. The most common class to track is an
018 * invariant, but any class can be used.
019 *
020 * <p>This allows detailed information about a particular class/ppt/variable combination to be
021 * printed without getting lost in a mass of other information (which is a particular problem in
022 * Daikon due to the volume of data considered).
023 *
024 * <p>Note that each of the three items (class, ppt, variable) must match in order for printing to
025 * occur.
026 */
027public final class Debug {
028
029  /** Debug Logger. */
030  public static final Logger debugTrack = Logger.getLogger("daikon.Debug");
031
032  /**
033   * List of classes for logging. Each name listed is compared to the fully qualified class name. If
034   * it matches (shows up anywhere in the class name) it will be included in debug prints. This is
035   * not a regular expression match
036   *
037   * @see #log(Logger, Class, Ppt, String)
038   */
039  public static String[] debugTrackClass = {
040    // "Bound",
041    // "DynamicConstants",
042    // "EltNonZero",
043    // "EltNonZeroFloat",
044    // "EltOneOf",
045    // "Equality",
046    // "FunctionBinary",
047    // "IntEqual",
048    // "IntGreaterEqual",
049    // "IntGreaterThan",
050    // "IntLessEqual",
051    // "IntLessThan",
052    // "IntNonEqual",
053    // "LinearBinary",
054    // "LowerBound",
055    // "Member",
056    // "NonZero",
057    // "OneOfSequence",
058    // "PptSlice",
059    // "PptSlice2",
060    // "PptSliceEquality",
061    // "PptTopLevel",
062    // "SeqIndexComparison",
063    // "SeqIndexNonEqual",
064    // "SeqIntEqual",
065    // "SeqSeqIntEqual",
066    // "NonZero",
067    // "FunctionBinary",
068    // "OneOfSequence",
069    // "IntLessEqual",
070    // "IntGreaterEqual",
071    // "IntLessThan",
072    // "IntGreaterThan",
073    // "IntNonEqual",
074    // "Member",
075    // "FunctionBinary"
076    // "EltNonZero",
077    // "SeqSeqIntGreaterThan",
078    // "SeqSeqIntLessThan",
079    // "SubSet",
080    // "SuperSet",
081    // "EltOneOf",
082    // "Bound",
083    // "SeqSeqIntLessThan",
084    // "SeqSeqIntGreaterThan",
085    // "OneOf"
086    // "StringEqual",
087    // "StringLessThan",
088    // "StringGreaterThan",
089    // "Modlong_zxy",
090    // "UpperBound",
091  };
092
093  /**
094   * Restrict function binary prints to the specified method. Implementation is in the
095   * FunctionBinary specific log functions. If null, there is no restriction (all function binary
096   * methods are printed). See Functions.java for a list of function names.
097   */
098  public static @Nullable String function_binary_method = null
099      // "java.lang.Math.max("
100      // "java.lang.Math.min("
101      // "org.plumelib.util.MathPlume.logicalXor("
102      // "org.plumelib.util.MathPlume.gcd("
103      ;
104
105  /**
106   * List of Ppts for logging. Each name listed is compared to the full program point name. If it
107   * matches (shows up anywhere in the ppt name) it will be included in the debug prints. This is
108   * not a regular expression match.
109   *
110   * @see #log(Logger, Class, Ppt, String)
111   */
112  public static String[] debugTrackPpt = {
113    // "DataStructures.DisjSets.unionDisjoint(int, int):::EXIT",
114    // "DataStructures.StackAr.StackAr(int):::EXIT",
115    // "DataStructures.StackAr.makeEmpty():::EXIT",
116    // "DataStructures.StackAr.makeEmpty()V:::ENTER",
117    // "DataStructures.StackAr.top():::EXIT74",
118    // "GLOBAL",
119    // "std.main(int;char **;)int:::EXIT",
120  };
121
122  /**
123   * List of variable names for logging. Each name listed is compared to each variable in turn. If
124   * each matches exactly it will be included in track debug prints. This is not a regular
125   * expression match. Note that the number of variables must match the slice exactly.
126   *
127   * @see #log(Logger, Class, Ppt, String)
128   */
129  public static String[][] debugTrackVars = {
130    // {"::printstats"},
131    // {"misc.Fib.STEPS", "orig(misc.Fib.a)"},
132    // {"misc.Fib.a", "misc.Fib.STEPS"},
133    // {"return"},
134  };
135
136  // Ordinarily, a client would have to supply a Class, Ppt, and
137  // List<Varinfo> with each call to a log method.  But the client can
138  // instead provide those values once (they are cached in these variables)
139  // and omit them in subsequent calls.  The subsequent calls can use forms
140  // of log() that take fewer arguments.
141
142  /**
143   * True if the cached values should be printed --- that is, they match what is currently being
144   * debugged.
145   */
146  public boolean cache_match = true;
147
148  // Note that throughout this file, inv_class is not necessarily a
149  // subclass of Invariant -- for instance, it might be a subclass of
150  // BinaryDerivationFactory.
151  /** cached class: class to use by default when calling variants of log() with few arguments. */
152  public @Nullable Class<?> cache_class;
153
154  /** Cached ppt: ppt to use by default when calling variants of log() with few arguments. */
155  public @Nullable Ppt cache_ppt;
156
157  /**
158   * Cached variables: variables to use by default when calling variants of log() with few
159   * arguments.
160   */
161  public VarInfo @Nullable [] cache_vis;
162
163  /**
164   * Ordinarily, a client would have to supply a Class, Ppt, and {@code List<Varinfo>} with each
165   * call to a log method. This constructor sets as defaults c, ppt, and whatever variable (if any)
166   * from vis that is on the debugTrackVar list. Essentially this creates a debug object that will
167   * print if any of the variables in vis are being tracked (and c and ppt match).
168   */
169  public Debug(Class<?> c, Ppt ppt, VarInfo[] vis) {
170    set(c, ppt, vis);
171  }
172
173  /**
174   * Returns a Debug object if the specified class, ppt, and vis match what is being tracked.
175   * Otherwise, return NULL. Preferred over calling the constructor directly, since it doesn't
176   * create the object if it doesn't have to.
177   */
178  public static @Nullable Debug newDebug(Class<?> c, Ppt ppt, VarInfo[] vis) {
179    if (logOn() && class_match(c) && ppt_match(ppt) && var_match(vis)) {
180      return new Debug(c, ppt, vis);
181    } else {
182      return null;
183    }
184  }
185
186  /**
187   * Ordinarily, a client would have to supply a Class, Ppt, and {@code List<Varinfo>} with each
188   * call to a log method. This constructor sets as defaults c, ppt, and whatever variable (if any)
189   * from vis that is on the debugTrackVar list. Essentially this creates a debug object that will
190   * print if any of the variables in vis are being tracked (and c and ppt match).
191   */
192  public Debug(Class<?> c, Ppt ppt, List<VarInfo> vis) {
193
194    VarInfo v = visTracked(vis);
195    if (v != null) {
196      set(c, ppt, new VarInfo[] {v});
197    } else if (!vis.isEmpty()) {
198      set(c, ppt, new VarInfo[] {vis.get(0)});
199    } else {
200      set(c, ppt, null);
201    }
202  }
203
204  /**
205   * Looks for each of the variables in vis in the DebugTrackVar list. If any match, returns that
206   * variable. Returns null if there are no matches.
207   */
208  public @Nullable VarInfo visTracked(@UnknownInitialization Debug this, List<VarInfo> vis) {
209
210    for (VarInfo v : vis) {
211      Set<VarInfo> evars = null;
212      if (v.equalitySet != null) {
213        evars = v.equalitySet.getVars();
214      }
215      if (evars != null) {
216        for (VarInfo ev : evars) {
217          for (int k = 0; k < debugTrackVars.length; k++) {
218            if (ev.name().equals(debugTrackVars[k][0])) {
219              return v;
220            }
221          }
222        }
223      }
224    }
225
226    return null;
227  }
228
229  private static @Nullable String[] ourvars = new String[3];
230
231  private static final VarInfo[] vis1 = new VarInfo[1];
232  private static final VarInfo[] vis2 = new VarInfo[2];
233  private static final VarInfo[] vis3 = new VarInfo[3];
234
235  public static VarInfo[] vis(VarInfo v1) {
236    vis1[0] = v1;
237    return vis1;
238  }
239
240  public static VarInfo[] vis(VarInfo v1, VarInfo v2) {
241    vis2[0] = v1;
242    vis2[1] = v2;
243    return vis2;
244  }
245
246  public static VarInfo[] vis(VarInfo v1, VarInfo v2, VarInfo v3) {
247    vis3[0] = v1;
248    vis3[1] = v2;
249    vis3[2] = v3;
250    return vis3;
251  }
252
253  /**
254   * Sets the cache for class, ppt, and vis so that future calls to log don't have to set them -- in
255   * other words, future calls can use the versions of log with fewer arguments.
256   */
257  void set(
258      @UnknownInitialization Debug this,
259      @Nullable Class<?> c,
260      @Nullable Ppt ppt,
261      VarInfo @Nullable [] vis) {
262    cache_class = c;
263    cache_ppt = ppt;
264    cache_vis = vis;
265    if (c == null) {
266      System.out.println("Class = null");
267    }
268    if (ppt == null) {
269      System.out.println("ppt = null");
270    }
271    if (vis == null) {
272      System.out.println("vis = null");
273    } else {
274      for (int i = 0; i < vis.length; i++) {
275        if (vis[i] == null) {
276          System.out.println("vis[" + i + "] == null");
277        }
278      }
279    }
280    cache_match = class_match(c) && ppt_match(ppt) && var_match(vis);
281  }
282
283  /**
284   * When true, perform detailed internal checking. These are essentially additional, possibly
285   * costly assert statements.
286   */
287  public static boolean dkconfig_internal_check = false;
288
289  /** If true, show stack traces for errors such as file format errors. */
290  public static boolean dkconfig_show_stack_trace = false;
291
292  /**
293   * Returns true if traceback information is printed for each call to log.
294   *
295   * @see #log(Logger, Class, Ppt, String)
296   */
297  public static boolean dkconfig_showTraceback = false;
298
299  /**
300   * Returns true if detailed info (such as from {@code add_modified}) is printed.
301   *
302   * @see #log(Logger, Class, Ppt, String)
303   * @see #logDetail()
304   */
305  public static boolean dkconfig_logDetail = false;
306
307  /**
308   * Returns true if detailed logging is on. Note that this check is not performed inside the
309   * logging calls themselves, it must be performed by the caller.
310   *
311   * @see #log(Logger, Class, Ppt, String)
312   * @see #logOn()
313   */
314  public static final boolean logDetail() {
315    return dkconfig_logDetail && debugTrack.isLoggable(Level.FINE);
316  }
317
318  /**
319   * Returns true if logging is on.
320   *
321   * @see #log(Logger, Class, Ppt, String)
322   */
323  public static final boolean logOn() {
324    return debugTrack.isLoggable(Level.FINE);
325  }
326
327  /**
328   * Logs the cached class, cached ppt, cached variables and the specified msg via the logger as
329   * described in {@link #log(Logger, Class, Ppt, VarInfo[], String)}.
330   */
331  public void log(Logger debug, String msg) {
332    if (cache_match) {
333      log(debug, cache_class, cache_ppt, cache_vis, msg);
334    }
335  }
336
337  /**
338   * Logs a description of the class, ppt, ppt variables and the specified msg via the logger as
339   * described in {@link #log(Logger, Class, Ppt, VarInfo[], String)}.
340   */
341  public static void log(Logger debug, Class<?> inv_class, @Nullable Ppt ppt, String msg) {
342    if (ppt == null) {
343      log(debug, inv_class, ppt, null, msg);
344    } else {
345      log(debug, inv_class, ppt, ppt.var_infos, msg);
346    }
347  }
348
349  /**
350   * Logs a description of the class, ppt, variables and the specified msg via the logger. The
351   * class, ppt, and variables are checked against those described in {@link #debugTrackClass},
352   * {@link #debugTrackPpt}, and {@link #debugTrackVars}. Only those that match are printed.
353   * Variables will match if they are in the same equality set. The information is written as:
354   *
355   * <p>{@code class: ppt : var1 : var2 : var3 : msg }
356   *
357   * <p>Note that if {@link #debugTrack} is not enabled then nothing is printed. It is somewhat
358   * faster to check {@link #logOn()} directly rather than relying on the check here.
359   *
360   * <p>Other versions of this method (noted below) work without the Logger parameter and take
361   * class, ppt, and vis from the cached values, which were set by the constructor or by the set()
362   * method.
363   *
364   * @param debug a second Logger to query if debug tracking is turned off or does not match. If
365   *     this logger is enabled, the same information will be written to it. Note that the
366   *     information is never written to both loggers.
367   * @param inv_class the class. Can be obtained in a static context by ClassName.class
368   * @param ppt program point
369   * @param vis variables at the program point. These are sometimes different from the ones in the
370   *     ppt itself.
371   * @param msg string message to log
372   * @see #logOn()
373   * @see #logDetail()
374   * @see #log(Class, Ppt, VarInfo[], String)
375   * @see #log(Class, Ppt, String)
376   * @see #log(Logger, String)
377   * @see #log(String)
378   */
379  public static void log(
380      Logger debug,
381      @Nullable Class<?> inv_class,
382      @Nullable Ppt ppt,
383      VarInfo @Nullable [] vis,
384      String msg) {
385
386    // Try to log via the logger first
387    if (log(inv_class, ppt, vis, msg)) {
388      return;
389    }
390
391    // If debug isn't turned on, there is nothing to do
392    if (!debug.isLoggable(Level.FINE)) {
393      return;
394    }
395
396    // Get the non-qualified class name
397    String class_str;
398    if (inv_class == null) {
399      // when is inv_class null?
400      class_str = "null";
401    } else {
402      @SuppressWarnings("nullness") // getPackage(): invariant class always has a package
403      @NonNull String packageName = inv_class.getPackage().getName() + ".";
404      class_str = inv_class.getName().replace(packageName, "");
405    }
406
407    String vars = "";
408    if (vis == null) {
409      System.out.println("no var infos");
410    } else {
411
412      // Get a string with all of the variable names.  Each is separated by ': '
413      // 3 variable slots are always setup for consistency
414      for (VarInfo v : vis) {
415        vars += v.name() + ": ";
416      }
417      for (int i = vis.length; i < 3; i++) {
418        vars += ": ";
419      }
420    }
421
422    // Figure out the sample count if possible
423    String samp_str = "";
424    if (ppt instanceof PptSlice) {
425      PptSlice pslice = (PptSlice) ppt;
426      samp_str = " s" + pslice.num_samples();
427    }
428
429    String line = " line=" + FileIO.get_linenum();
430
431    String name = "ppt is null";
432    if (ppt != null) {
433      name = ppt.name();
434    }
435
436    debug.fine(class_str + ": " + name + samp_str + line + ": " + vars + msg);
437    if (dkconfig_showTraceback) {
438      Throwable stack = new Throwable("debug traceback");
439      stack.fillInStackTrace();
440      stack.printStackTrace();
441    }
442  }
443
444  /**
445   * Logs a description of the cached class, ppt, and variables and the specified msg via the logger
446   * as described in {@link #log(Logger, Class, Ppt, VarInfo[], String)}
447   *
448   * @return true if it logged anything
449   */
450  public boolean log(String msg) {
451    if (!logOn()) {
452      return false;
453    }
454    return log(cache_class, cache_ppt, cache_vis, msg);
455  }
456
457  /**
458   * Logs a description of the class, ppt, ppt variables and the specified msg via the logger as
459   * described in {@link #log(Logger, Class, Ppt, VarInfo[], String)}.
460   *
461   * @return true if it logged anything
462   */
463  // 3-argument form
464  public static boolean log(
465      Class<?> inv_class, @UnknownInitialization(PptTopLevel.class) Ppt ppt, String msg) {
466
467    return log(inv_class, ppt, ppt.var_infos, msg);
468  }
469
470  /**
471   * Logs a description of the class, ppt, variables and the specified msg via the logger as
472   * described in {@link #log(Logger, Class, Ppt, String)}. Accepts vis because sometimes the
473   * variables are different from those in the ppt.
474   *
475   * @param inv_class the class to be logged
476   * @param ppt the program point to be logged
477   * @param vis the VarInfos for the ppt
478   * @param msg the message to be logged
479   * @return true if it logged something
480   */
481  // 4-argument form
482  public static boolean log(
483      @Nullable Class<?> inv_class,
484      @Nullable @UnknownInitialization(PptTopLevel.class) Ppt ppt,
485      VarInfo @Nullable [] vis,
486      String msg) {
487
488    if (!debugTrack.isLoggable(Level.FINE)) {
489      return false;
490    }
491
492    // Make sure the class matches
493    if (!class_match(inv_class)) {
494      return false;
495    }
496
497    // Make sure the Ppt matches
498    if (!ppt_match(ppt)) {
499      return false;
500    }
501
502    // Make sure the variables match
503    if (!var_match(vis)) {
504      return false;
505    }
506
507    // Get the non-qualified class name
508    String class_str = "null";
509    if (inv_class != null) {
510      @SuppressWarnings("nullness") // getPackage(): invariant class always has a package
511      @NonNull String packageName = inv_class.getPackage().getName() + ".";
512      class_str = inv_class.getName().replace(packageName, "");
513    }
514
515    // Get a string with all of the variable names.  Each is separated by ': '.
516    // 3 variable slots are always setup for consistency.
517    String vars = "";
518    if (vis != null) {
519      int numvars = vis.length;
520      for (int i = 0; i < numvars; i++) {
521        VarInfo v = vis[i];
522        vars += v.name();
523        if (ourvars[i] != null) {
524          vars += " {" + ourvars[i] + "}";
525        }
526        vars += ": ";
527      }
528      for (int i = numvars; i < 3; i++) {
529        vars += ": ";
530      }
531    }
532
533    // Figure out the sample count if possible
534    String samp_str = "";
535    if (ppt instanceof PptSlice) {
536      PptSlice pslice = (PptSlice) ppt;
537      samp_str = " s" + pslice.num_samples();
538    }
539
540    String line = " line=" + FileIO.get_linenum();
541
542    debugTrack.fine(
543        class_str
544            + ": "
545            + ((ppt == null) ? "null" : ppt.name())
546            + samp_str
547            + line
548            + ": "
549            + vars
550            + msg);
551    if (dkconfig_showTraceback) {
552      Throwable stack = new Throwable("debug traceback");
553      stack.fillInStackTrace();
554      stack.printStackTrace();
555    }
556
557    return true;
558  }
559
560  /** Returns true if the specified class matches the classes being tracked. */
561  public static boolean class_match(@Nullable Class<?> inv_class) {
562
563    if ((debugTrackClass.length > 0) && (inv_class != null)) {
564      return strContainsElem(inv_class.getName(), debugTrackClass);
565    }
566    return true;
567  }
568
569  /** Returns true if the specified ppt matches the ppts being tracked. */
570  public static boolean ppt_match(
571      @Nullable @UnknownInitialization(daikon.PptTopLevel.class) Ppt ppt) {
572
573    if (debugTrackPpt.length > 0) {
574      return (ppt != null) && strContainsElem(ppt.name(), debugTrackPpt);
575    }
576    return true;
577  }
578
579  /**
580   * Returns true if the specified vars match the ones being tracked. Also, sets Debug.ourvars with
581   * the names of the variables matched if they are not the leader of their 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 preceded 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    StringJoiner vars_out = new StringJoiner(" ");
888    for (String[] cv : debugTrackVars) {
889      vars_out.add(Arrays.toString(cv));
890    }
891    debugTrack.fine("Track Vars: " + vars_out);
892    debugTrack.fine("Track Ppts: " + ArraysPlume.toString(debugTrackPpt, false));
893
894    return null;
895  }
896}