001package daikon;
002
003import static daikon.PptRelation.PptRelationType;
004import static daikon.PptTopLevel.PptFlags;
005import static daikon.PptTopLevel.PptType;
006import static daikon.VarInfo.LangFlags;
007import static daikon.VarInfo.RefType;
008import static daikon.VarInfo.VarFlags;
009import static daikon.VarInfo.VarKind;
010import static daikon.tools.nullness.NullnessUtil.castNonNullDeep;
011import static java.nio.charset.StandardCharsets.UTF_8;
012
013import daikon.Daikon.BugInDaikon;
014import daikon.config.Configuration;
015import daikon.derive.ValueAndModified;
016import daikon.diff.InvMap;
017import daikon.inv.Invariant;
018import java.io.BufferedReader;
019import java.io.BufferedWriter;
020import java.io.Closeable;
021import java.io.EOFException;
022import java.io.File;
023import java.io.IOException;
024import java.io.InputStream;
025import java.io.InputStreamReader;
026import java.io.InvalidClassException;
027import java.io.LineNumberReader;
028import java.io.ObjectInputStream;
029import java.io.PrintWriter;
030import java.io.Reader;
031import java.io.Serializable;
032import java.io.StringWriter;
033import java.io.UncheckedIOException;
034import java.net.ServerSocket;
035import java.net.Socket;
036import java.net.URI;
037import java.net.URL;
038import java.nio.file.Files;
039import java.nio.file.Path;
040import java.text.NumberFormat;
041import java.util.ArrayDeque;
042import java.util.ArrayList;
043import java.util.Arrays;
044import java.util.Collection;
045import java.util.Deque;
046import java.util.EnumSet;
047import java.util.HashMap;
048import java.util.LinkedHashMap;
049import java.util.List;
050import java.util.Locale;
051import java.util.Map;
052import java.util.Scanner;
053import java.util.StringJoiner;
054import java.util.logging.Level;
055import java.util.logging.Logger;
056import java.util.zip.GZIPInputStream;
057import org.checkerframework.checker.calledmethods.qual.EnsuresCalledMethods;
058import org.checkerframework.checker.interning.qual.Interned;
059import org.checkerframework.checker.interning.qual.UsesObjectEquals;
060import org.checkerframework.checker.lock.qual.GuardSatisfied;
061import org.checkerframework.checker.mustcall.qual.MustCall;
062import org.checkerframework.checker.mustcall.qual.Owning;
063import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
064import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
065import org.checkerframework.checker.nullness.qual.KeyFor;
066import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
067import org.checkerframework.checker.nullness.qual.NonNull;
068import org.checkerframework.checker.nullness.qual.Nullable;
069import org.checkerframework.checker.nullness.qual.RequiresNonNull;
070import org.checkerframework.dataflow.qual.Pure;
071import org.checkerframework.dataflow.qual.SideEffectFree;
072import org.plumelib.util.CollectionsPlume;
073import org.plumelib.util.FilesPlume;
074import org.plumelib.util.StringsPlume;
075
076/** File I/O utilities. */
077public final class FileIO {
078
079  /** Nobody should ever instantiate a FileIO. */
080  private FileIO() {
081    throw new Error();
082  }
083
084  // Constants
085
086  /** Introduces a declaration in a declaration file. */
087  static final String declaration_header = "DECLARE";
088
089  // Program point name tags
090  /** String used to append a ppt type to a ppt name. */
091  public static final String ppt_tag_separator = ":::";
092
093  /** String used to identify entry ppt names. */
094  public static final String enter_suffix = "ENTER";
095
096  /** String used to mark entry ppt names. */
097  public static final String enter_tag = ppt_tag_separator + enter_suffix;
098
099  // EXIT does not necessarily appear at the end of the program point name;
100  // a number may follow it.
101  /** String used to identify exit ppt names. */
102  public static final String exit_suffix = "EXIT";
103
104  /** String used to mark exit ppt names. */
105  public static final String exit_tag = ppt_tag_separator + exit_suffix;
106
107  /** To be deleted. */
108  public static final String throws_suffix = "THROWS";
109
110  /** To be deleted. */
111  public static final String throws_tag = ppt_tag_separator + throws_suffix;
112
113  public static final String object_suffix = "OBJECT";
114
115  /** String used to mark object ppt names. */
116  public static final String object_tag = ppt_tag_separator + object_suffix;
117
118  /** String used to identify class ppt names. */
119  public static final String class_static_suffix = "CLASS";
120
121  /** String used to mark class ppt names. */
122  public static final String class_static_tag = ppt_tag_separator + class_static_suffix;
123
124  /** String used to identify global ppt names. */
125  public static final String global_suffix = "GLOBAL";
126
127  /** The line separator. */
128  private static final String lineSep = Global.lineSep;
129
130  // Settings
131
132  // Variables starting with dkconfig_ should only be set via the
133  // daikon.config.Configuration interface.
134
135  /**
136   * When true, just ignore exit ppts that don't have a matching enter ppt rather than exiting with
137   * an error. Unmatched exits can occur if only a portion of a dtrace file is processed.
138   */
139  public static boolean dkconfig_ignore_missing_enter = false;
140
141  /**
142   * Boolean. When false, set modbits to 1 iff the printed representation has changed. When true,
143   * set modbits to 1 if the printed representation has changed; leave other modbits as is.
144   */
145  public static boolean dkconfig_add_changed = true;
146
147  /** Integer. Maximum number of lines to read from the dtrace file. If 0, reads the entire file. */
148  public static int dkconfig_max_line_number = 0;
149
150  /**
151   * Boolean. When false, don't count the number of lines in the dtrace file before reading. This
152   * will disable the percentage progress printout.
153   */
154  public static boolean dkconfig_count_lines = true;
155
156  /**
157   * Boolean. When true, only read the samples, but don't process them. Used to gather timing
158   * information.
159   */
160  public static boolean dkconfig_read_samples_only = false;
161
162  /**
163   * Boolean. When true, don't print a warning about unmatched procedure entries, which are ignored
164   * by Daikon (unless the {@code --nohierarchy} command-line argument is provided).
165   */
166  public static boolean dkconfig_unmatched_procedure_entries_quiet = false;
167
168  /** Boolean. If true, prints the unmatched procedure entries verbosely. */
169  public static boolean dkconfig_verbose_unmatched_procedure_entries = false;
170
171  /**
172   * Boolean. When true, suppress exceptions related to file reading. This permits Daikon to
173   * continue even if there is a malformed trace file. Use this with care: in general, it is better
174   * to fix the problem that caused a bad trace file, rather than to suppress the exception.
175   */
176  public static boolean dkconfig_continue_after_file_exception = false;
177
178  /**
179   * Long integer. If non-zero, this value will be used as the number of lines in (each) dtrace file
180   * input for the purposes of the progress display, and the counting of the lines in the file will
181   * be suppressed.
182   */
183  public static long dkconfig_dtrace_line_count = 0;
184
185  /** True if declaration records are in the new format -- that is, decl-version 2.0. */
186  // Set by read_decl_version; by read_data_trace_record if the file is non-empty;
187  // by read_serialized_pptmap; and by InvMap.readObject.
188  public static @MonotonicNonNull Boolean new_decl_format = null;
189
190  /**
191   * Do not use this routine unless you know what you are doing. This routine breaks the
192   * representation invariant that new_decl_format, once set, is never reset to null. This routine
193   * should be used only if you can guarantee that new_decl_format will be once again set to a
194   * non-null value before any code runs that depends on the fact that new_decl_format is non-null.
195   */
196  @SuppressWarnings("nullness") // reinitialization
197  public static void resetNewDeclFormat() {
198    FileIO.new_decl_format = null;
199  }
200
201  /**
202   * If true, modified all ppt names to remove duplicate routine names within the ppt name. This is
203   * used when a stack trace (of active methods) is used as the ppt name. The routine names must be
204   * separated by vertical bars (|).
205   */
206  public static boolean dkconfig_rm_stack_dups = false;
207
208  // Variables
209
210  // This hashmap maps every program point to an array, which contains the
211  // old values of all variables in scope the last time the program point
212  // was executed. This enables us to determine whether the values have been
213  // modified since this program point was last executed.
214  static HashMap<PptTopLevel, String[]> ppt_to_value_reps = new HashMap<>();
215
216  // For debugging purposes: printing out a modified trace file with
217  // changed modbits.
218  private static boolean to_write_nonce = false;
219  private static final String NONCE_HEADER = "this_invocation_nonce";
220  private static String nonce_value = "no nonce (yet)";
221
222  // (This implementation as a public static variable is a bit unclean.)
223  // Number of ignored declarations.
224  public static int omitted_declarations = 0;
225
226  // Logging Categories
227
228  /**
229   * If true, then print the variable name each time the variable's value is first
230   * missing/nonsensical.
231   */
232  public static boolean debug_missing = false;
233
234  /** Debug tracer for reading. */
235  public static final Logger debugRead = Logger.getLogger("daikon.FileIO.read");
236
237  /** Debug tracer for printing. */
238  public static final Logger debugPrint = Logger.getLogger("daikon.FileIO.printDtrace");
239
240  /** Debug tracer for printing variable values. */
241  public static final Logger debugVars = Logger.getLogger("daikon.FileIO.vars");
242
243  // public static final SimpleLog debug_decl = new SimpleLog(false);
244
245  /** Parents in the ppt/variable hierarchy for a particular program point. */
246  public static final class ParentRelation implements java.io.Serializable {
247    static final long serialVersionUID = 20060622L;
248    public PptRelationType rel_type;
249    public @Interned String parent_ppt_name;
250    public int id;
251
252    public ParentRelation(PptRelationType rel_type, @Interned String parent_ppt_name, int id) {
253      this.rel_type = rel_type;
254      this.parent_ppt_name = parent_ppt_name;
255      this.id = id;
256    }
257
258    @SideEffectFree
259    @Override
260    public String toString(@GuardSatisfied ParentRelation this) {
261      return parent_ppt_name + "[" + id + "] " + rel_type;
262    }
263
264    /**
265     * Intern the ppt name.
266     *
267     * @param in the input stream from which to read the object
268     * @throws IOException if there is a problem reading the stream
269     * @throws ClassNotFoundException if a class cannot be loaded
270     */
271    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
272      in.defaultReadObject();
273      if (parent_ppt_name != null) {
274        parent_ppt_name = parent_ppt_name.intern();
275      }
276    }
277  }
278
279  // Utilities
280  @EnsuresNonNullIf(result = true, expression = "#1")
281  @Pure
282  public static final boolean isComment(@Nullable String s) {
283    return s != null && (s.startsWith("//") || s.startsWith("#"));
284  }
285
286  /**
287   * Returns true if the next line is a comment.
288   *
289   * @param reader the reader whose next line to check
290   * @return true if the next line is a comment
291   */
292  // Nullness-checking of read_data_trace_record(ParseState) works even
293  // without these two lines, since StringJoiner accepts null values.
294  @SuppressWarnings(
295      "nullness:contracts.conditional.postcondition" // readLine() assertion is ensured by call to
296  // reset()
297  )
298  @EnsuresNonNullIf(result = true, expression = "#1.readLine()")
299  public static final boolean nextLineIsComment(BufferedReader reader) {
300    boolean result = false;
301    try {
302      reader.mark(10000);
303      String nextline = reader.readLine();
304      result = isComment(nextline);
305    } catch (IOException e) {
306      result = false;
307    }
308    try {
309      reader.reset();
310    } catch (IOException e) {
311      throw new UncheckedIOException(e);
312    }
313    return result;
314  }
315
316  // ///////////////////////////////////////////////////////////////////////////
317  // Declaration files
318  //
319
320  /**
321   * Returns a new PptMap containing declarations read from the files listed in the argument;
322   * connection information (controlling variables and entry ppts) is set correctly upon return.
323   *
324   * @param files files to be read (java.io.File)
325   * @return a new PptMap containing declarations read from the files listed in the argument
326   */
327  public static PptMap read_declaration_files(Collection<File> files) throws IOException {
328    PptMap all_ppts = new PptMap();
329    // Read all decls, creating PptTopLevels and VarInfos
330    for (File file : files) {
331      Daikon.progress = "Reading " + file;
332      if (!Daikon.dkconfig_quiet) {
333        System.out.print("."); // show progress
334      }
335      read_declaration_file(file, all_ppts);
336    }
337    return all_ppts;
338  }
339
340  /** Read one decls file; add it to all_ppts. */
341  public static void read_declaration_file(File filename, PptMap all_ppts) throws IOException {
342    if (Daikon.using_DaikonSimple) {
343      Processor processor = new DaikonSimple.SimpleProcessor();
344      read_data_trace_file(filename.toString(), all_ppts, processor, true, false);
345    } else {
346      Processor processor = new Processor();
347      read_data_trace_file(filename.toString(), all_ppts, processor, true, true);
348    }
349  }
350
351  // Read a declaration in the Version 2 format.  For Version 1, see
352  // read_declaration.
353  /**
354   * Reads one ppt declaration. The next line should be the ppt record. After completion, the file
355   * pointer will be pointing at the next record (ie, the blank line at the end of the ppt
356   * declaration will have been read in). Returns null if the ppt is excluded/omitted from this
357   * execution of Daikon.
358   */
359  private static @Nullable PptTopLevel read_ppt_decl(ParseState state, String top_line)
360      throws IOException {
361
362    // process the ppt record
363    String line = top_line;
364    Scanner scanner = new Scanner(line);
365    @Interned String record_name = need(state, scanner, "'ppt'");
366    if (record_name != "ppt") { // interned
367      decl_error(state, "found '%s' where 'ppt' expected", record_name);
368    }
369    String ppt_name = need(state, scanner, "ppt name");
370    ppt_name = user_mod_ppt_name(ppt_name);
371
372    // Information that will populate the new program point.
373    Map<String, VarDefinition> varmap = new LinkedHashMap<>();
374    // The VarDefinition we are in the middle of reading, or null if we are not.
375    VarDefinition vardef = null;
376    List<ParentRelation> ppt_parents = new ArrayList<>();
377    EnumSet<PptFlags> ppt_flags = EnumSet.noneOf(PptFlags.class);
378    PptType ppt_type = PptType.POINT;
379
380    try {
381      // Read the records that define this program point
382      while ((line = state.reader.readLine()) != null) {
383        // debug_decl.log("read line %s%n", line);
384        line = line.trim();
385        if (line.length() == 0) {
386          break;
387        }
388
389        scanner = new Scanner(line);
390        @Interned String record = scanner.next().intern();
391        if (vardef == null) {
392          if (record == "parent") { // interned
393            ppt_parents.add(parse_ppt_parent(state, scanner));
394          } else if (record == "flags") { // interned
395            parse_ppt_flags(state, scanner, ppt_flags);
396          } else if (record == "variable") { // interned
397            vardef = new VarDefinition(state, scanner);
398            // There is no need to check "varmap.containsKey(vardef.name)"
399            // because this is the first variable.
400            assert varmap.isEmpty();
401            if (var_included(vardef.name)) {
402              varmap.put(vardef.name, vardef);
403            }
404          } else if (record == "ppt-type") { // interned
405            ppt_type = parse_ppt_type(state, scanner);
406          } else {
407            decl_error(state, "record '%s' found where %s expected", record, "'parent', 'flags'");
408          }
409        } else { // there must be a current variable
410          if (record == "var-kind") { // interned
411            vardef.parse_var_kind(scanner);
412          } else if (record == "enclosing-var") { // interned
413            vardef.parse_enclosing_var_name(scanner);
414          } else if (record == "reference-type") { // interned
415            vardef.parse_reference_type(scanner);
416          } else if (record == "array") { // interned
417            vardef.parse_array(scanner);
418          } else if (record == "function-args") { // interned
419            vardef.parse_function_args(scanner);
420          } else if (record == "rep-type") { // interned
421            vardef.parse_rep_type(scanner);
422          } else if (record == "dec-type") { // interned
423            vardef.parse_dec_type(scanner);
424          } else if (record == "flags") { // interned
425            vardef.parse_flags(scanner);
426          } else if (record == "lang-flags") { // interned
427            vardef.parse_lang_flags(scanner);
428          } else if (record == "parent") { // interned
429            vardef.parse_parent(scanner, ppt_parents);
430          } else if (record == "comparability") { // interned
431            vardef.parse_comparability(scanner);
432          } else if (record == "constant") { // interned
433            vardef.parse_constant(scanner);
434          } else if (record == "variable") { // interned
435            try {
436              vardef.checkRep(); // make sure the previous variable is ok
437            } catch (AssertionError e) {
438              decl_error(state, e);
439            }
440            vardef = new VarDefinition(state, scanner);
441            if (varmap.containsKey(vardef.name)) {
442              decl_error(state, "var %s declared twice", vardef.name);
443            }
444            if (var_included(vardef.name)) {
445              varmap.put(vardef.name, vardef);
446            }
447          } else if (record == "min-value") { // interned
448            vardef.parse_min_value(scanner);
449          } else if (record == "max-value") { // interned
450            vardef.parse_max_value(scanner);
451          } else if (record == "min-length") { // interned
452            vardef.parse_min_length(scanner);
453          } else if (record == "max-length") { // interned
454            vardef.parse_max_length(scanner);
455          } else if (record == "valid-values") { // interned
456            vardef.parse_valid_values(scanner);
457          } else {
458            decl_error(state, "Unexpected variable item '%s' found", record);
459          }
460        }
461      }
462    } catch (Daikon.ParseError pe) {
463      decl_error(state, "%s", pe.getMessage());
464      throw new Error(); // this can't happen
465    }
466    if (vardef != null) {
467      try {
468        vardef.checkRep();
469      } catch (AssertionError e) {
470        decl_error(state, e);
471      }
472    }
473
474    // If we are excluding this ppt, just read the data and throw it away
475    if (!ppt_included(ppt_name)) {
476      omitted_declarations++;
477      return null;
478    }
479
480    // Build the var infos from the var definitions.
481    List<VarInfo> vi_list = new ArrayList<>(varmap.size());
482    for (VarDefinition vd : varmap.values()) {
483      @SuppressWarnings("interning") // about to be used in a new program point
484      @Interned VarInfo vi = new VarInfo(vd);
485      vi_list.add(vi);
486    }
487    VarInfo[] vi_array = vi_list.toArray(new VarInfo[0]);
488
489    // Check to see if the program point is new
490    if (state.all_ppts.containsName(ppt_name)) {
491      @NonNull PptTopLevel existing_ppt = state.all_ppts.get(ppt_name);
492      assert existing_ppt != null : "state.all_ppts.containsName(" + ppt_name + ")";
493      if (state.ppts_may_be_new) {
494        check_decl_match(state, existing_ppt, vi_array);
495      } else { // ppts are already in the map
496        if (VarInfo.assertionsEnabled()) {
497          for (VarInfo vi : vi_array) {
498            vi.checkRep();
499          }
500        }
501        return existing_ppt;
502      }
503    }
504
505    // Build the program point
506    PptTopLevel newppt = new PptTopLevel(ppt_name, ppt_type, ppt_parents, ppt_flags, vi_array);
507
508    return newppt;
509  }
510
511  /** Parses a ppt parent hierarchy record and returns it. */
512  private static ParentRelation parse_ppt_parent(ParseState state, Scanner scanner) {
513
514    PptRelationType rel_type =
515        parse_enum_val(state, scanner, PptRelationType.class, "relation type");
516    String parent_ppt_name = need(state, scanner, "ppt name");
517    int id = Integer.parseInt(need(state, scanner, "relation id"));
518    ParentRelation pr = new ParentRelation(rel_type, parent_ppt_name, id);
519
520    need_eol(state, scanner);
521    return pr;
522  }
523
524  /** Parses a program point flag record. Adds any specified flags to to flags. */
525  private static void parse_ppt_flags(ParseState state, Scanner scanner, EnumSet<PptFlags> flags) {
526
527    flags.add(parse_enum_val(state, scanner, PptFlags.class, "ppt flags"));
528    while (scanner.hasNext()) {
529      flags.add(parse_enum_val(state, scanner, PptFlags.class, "ppt flags"));
530    }
531  }
532
533  /** Parses a ppt-type record and returns the type. */
534  private static PptType parse_ppt_type(ParseState state, Scanner scanner) {
535
536    PptType ppt_type = parse_enum_val(state, scanner, PptType.class, "ppt type");
537    need_eol(state, scanner);
538    return ppt_type;
539  }
540
541  // Read a declaration in the Version 1 format.  For version 2, see
542  // read_ppt_decl.
543  // The "DECLARE" line has already been read.
544  private static @Nullable PptTopLevel read_declaration(ParseState state) throws IOException {
545
546    // We have just read the "DECLARE" line.
547    String ppt_name = state.reader.readLine();
548    if (ppt_name == null) {
549      throw new Daikon.UserError(
550          "File ends with \"DECLARE\" with no following program point name", state);
551    }
552    ppt_name = user_mod_ppt_name(ppt_name);
553    ppt_name = ppt_name.intern();
554    VarInfo[] vi_array = read_VarInfos(state, ppt_name);
555
556    // System.out.printf("Ppt %s with %d variables%n", ppt_name,
557    //                   vi_array.length);
558
559    // This program point name has already been encountered.
560    if (state.all_ppts.containsName(ppt_name)) {
561      @NonNull PptTopLevel existing_ppt = state.all_ppts.get(ppt_name);
562      assert existing_ppt != null : "state.all_ppts.containsName(" + ppt_name + ")";
563      if (state.ppts_may_be_new) {
564        check_decl_match(state, existing_ppt, vi_array);
565      } else { // ppts are already in the map
566        return existing_ppt;
567      }
568    }
569
570    // If we are excluding this ppt, just throw it away
571    if (!ppt_included(ppt_name)) {
572      omitted_declarations++;
573      return null;
574    }
575
576    // taking care of visibility information
577    // the information is needed in the variable hierarchy because private methods
578    // should not be linked under the object program point
579    // the ppt name is truncated before putting it in the pptMap because the visibility
580    // information is only present in the decls file and not the dtrace file
581
582    //    if (ppt_name.startsWith("public")) {
583    //      int position = ppt_name.indexOf("public");
584    //      ppt_name = ppt_name.substring(7);
585    //      PptTopLevel newppt = new PptTopLevel(ppt_name, vi_array);
586    //      newppt.ppt_name.setVisibility("public");
587    //      return newppt;
588    //    }
589    //    if (ppt_name.startsWith("private")) {
590    //      int position = ppt_name.indexOf("private");
591    //      ppt_name = ppt_name.substring(8);
592    //      PptTopLevel newppt = new PptTopLevel(ppt_name, vi_array);
593    //      newppt.ppt_name.setVisibility("private");
594    //      return newppt;
595    //    }
596    //    if (ppt_name.startsWith("protected")) {
597    //      int position = ppt_name.indexOf("protected");
598    //      ppt_name = ppt_name.substring(10);
599    //      PptTopLevel newppt = new PptTopLevel(ppt_name, vi_array);
600    //      newppt.ppt_name.setVisibility("protected");
601    //      return newppt;
602    //    }
603
604    // TODO: add a new config variable to turn this accessibility flag processing on?
605    PptTopLevel newppt = new PptTopLevel(ppt_name, vi_array);
606    // newppt.ppt_name.setVisibility("package-protected");
607    return newppt;
608    // return new PptTopLevel(ppt_name, vi_array);
609  }
610
611  private static VarInfo[] read_VarInfos(ParseState state, String ppt_name) throws IOException {
612
613    // The var_infos that will populate the new program point
614    List<VarInfo> var_infos = new ArrayList<>();
615
616    // Each iteration reads a variable name, type, and comparability.
617    // Possibly abstract this out into a separate function??
618    VarInfo vi;
619    while ((vi = read_VarInfo(state, ppt_name)) != null) {
620      for (VarInfo vi2 : var_infos) {
621        if (vi.name() == vi2.name()) {
622          throw new Daikon.UserError("Duplicate variable name " + vi.name(), state);
623        }
624      }
625      // Can't do this test in read_VarInfo, it seems, because of the test
626      // against null above.
627      if (!var_included(vi.name())) {
628        continue;
629      }
630      var_infos.add(vi);
631    }
632
633    VarInfo[] result = var_infos.toArray(new VarInfo[0]);
634    return result;
635  }
636
637  // So that warning message below is only printed once
638  private static boolean seen_string_rep_type = false;
639
640  /**
641   * Read a variable name, type, and comparability; construct a VarInfo. Return null after reading
642   * the last variable in this program point declaration.
643   *
644   * <p>The resulting VarInfo does not have its ppt field set; the client should arrange to do so.
645   *
646   * @param state the parse state
647   * @param ppt_name the name of the variable's program point; used only for diagnostic messages
648   * @return a new VarInfo read from {@code state.reader}
649   * @throws IOException if there is trouble reading the file
650   */
651  private static @Nullable VarInfo read_VarInfo(ParseState state, String ppt_name)
652      throws IOException {
653    LineNumberReader file = state.reader;
654    int varcomp_format = state.varcomp_format;
655    String filename = state.filename;
656
657    String line = file.readLine();
658    if ((line == null) || line.equals("")) {
659      return null;
660    }
661    String varname = line;
662    String proglang_type_string_and_aux = file.readLine();
663    String file_rep_type_string = file.readLine();
664    String comparability_string = file.readLine();
665    if ( // (varname == null) || // already returned null if varname==null
666    (proglang_type_string_and_aux == null)
667        || (file_rep_type_string == null)
668        || (comparability_string == null))
669      throw new Daikon.UserError(
670          "End of file "
671              + filename
672              + " while reading variable "
673              + varname
674              + " in declaration of program point "
675              + ppt_name);
676    int equals_index = file_rep_type_string.indexOf(" = ");
677    String static_constant_value_string = null;
678    @Interned Object static_constant_value = null;
679    boolean is_static_constant = false;
680    if (equals_index != -1) {
681      is_static_constant = true;
682      static_constant_value_string = file_rep_type_string.substring(equals_index + 3);
683      file_rep_type_string = file_rep_type_string.substring(0, equals_index);
684    }
685    // XXX temporary, for compatibility with older .dtrace files.  12/20/2001
686    if ("String".equals(file_rep_type_string)) {
687      file_rep_type_string = "java.lang.String";
688      if (!seen_string_rep_type) {
689        seen_string_rep_type = true;
690        System.err.println(
691            "Warning: Malformed trace file.  Representation type 'String' should be "
692                + "'java.lang.String' instead on line "
693                + (file.getLineNumber() - 1)
694                + " of "
695                + filename);
696      }
697    }
698    // This is for people who were confused by the above temporary
699    // workaround when it didn't have a warning. But this has never
700    // worked, so it's fatal.
701    else if ("String[]".equals(file_rep_type_string)) {
702      throw new Daikon.UserError(
703          "Representation type 'String[]' should be "
704              + "'java.lang.String[]' instead for variable "
705              + varname,
706          file,
707          filename);
708    }
709    // XXX
710
711    int hash_position = proglang_type_string_and_aux.indexOf('#');
712    String aux_string = "";
713    if (hash_position == -1) {
714      hash_position = proglang_type_string_and_aux.length();
715    } else {
716      aux_string =
717          proglang_type_string_and_aux.substring(
718              hash_position + 1, proglang_type_string_and_aux.length());
719    }
720
721    String proglang_type_string = proglang_type_string_and_aux.substring(0, hash_position).trim();
722
723    ProglangType prog_type;
724    ProglangType file_rep_type;
725    ProglangType rep_type;
726    VarInfoAux aux;
727    try {
728      prog_type = ProglangType.parse(proglang_type_string);
729      file_rep_type = ProglangType.rep_parse(file_rep_type_string);
730      rep_type = file_rep_type.fileTypeToRepType();
731      aux = VarInfoAux.parse(aux_string);
732    } catch (IOException e) {
733      throw new Daikon.UserError(e, file, filename);
734    }
735
736    if (static_constant_value_string != null) {
737      static_constant_value = rep_type.parse_value(static_constant_value_string, file, filename);
738      // Why can't the value be null?
739      assert static_constant_value != null;
740    }
741    VarComparability comparability = null;
742    try {
743      comparability = VarComparability.parse(varcomp_format, comparability_string, prog_type);
744    } catch (Exception e) {
745      throw new Daikon.UserError(
746          String.format(
747              "Error parsing comparability (%s) at line %d in file %s",
748              e, file.getLineNumber(), filename));
749    }
750    if (!VarInfo.legalFileRepType(file_rep_type)) {
751      throw new Daikon.UserError(
752          "Unsupported representation type "
753              + file_rep_type.format()
754              + " (parsed as "
755              + rep_type
756              + ")"
757              + " for variable "
758              + varname,
759          file,
760          filename);
761    }
762    if (!VarInfo.legalRepType(rep_type)) {
763      throw new Daikon.UserError(
764          "Unsupported (converted) representation type "
765              + file_rep_type.format()
766              + " for variable "
767              + varname,
768          file,
769          filename);
770    }
771    // COMPARABILITY TEST
772    if (!(comparability.alwaysComparable()
773        || ((VarComparabilityImplicit) comparability).dimensions == file_rep_type.dimensions())) {
774      System.err.println();
775      throw new Daikon.UserError(
776          "Rep type "
777              + file_rep_type.format()
778              + " has "
779              + file_rep_type.dimensions()
780              + " dimensions,"
781              + " but comparability "
782              + comparability
783              + " has "
784              + ((VarComparabilityImplicit) comparability).dimensions
785              + " dimensions,"
786              + " for variable "
787              + varname,
788          file,
789          filename);
790    }
791
792    @SuppressWarnings("interning")
793    @Interned VarInfo result =
794        new VarInfo(
795            varname,
796            prog_type,
797            file_rep_type,
798            comparability,
799            is_static_constant,
800            static_constant_value,
801            aux);
802    return result;
803  }
804
805  @RequiresNonNull("FileIO.new_decl_format")
806  private static int read_var_comparability(ParseState state, String line) throws IOException {
807
808    // System.out.printf("read_var_comparability, line = '%s' %b%n", line,
809    //                   new_decl_format);
810    String comp_str;
811    if (new_decl_format) {
812      Scanner scanner = new Scanner(line);
813      scanner.next();
814      comp_str = need(state, scanner, "comparability");
815      need_eol(state, scanner);
816    } else { // old format
817      comp_str = state.reader.readLine();
818      if (comp_str == null) {
819        throw new Daikon.UserError("Found end of file, expected comparability", state);
820      }
821    }
822
823    if (comp_str.equals("none")) {
824      return VarComparability.NONE;
825    } else if (comp_str.equals("implicit")) {
826      return VarComparability.IMPLICIT;
827    } else {
828      throw new Daikon.UserError("Unrecognized VarComparability '" + comp_str + "'", state);
829    }
830  }
831
832  private static @Interned String read_input_language(ParseState state, String line)
833      throws IOException {
834
835    Scanner scanner = new Scanner(line);
836    scanner.next();
837    @Interned String input_lang = need(state, scanner, "input language");
838    need_eol(state, scanner);
839    return input_lang;
840  }
841
842  @EnsuresNonNull("FileIO.new_decl_format")
843  private static void read_decl_version(ParseState state, String line) throws IOException {
844    Scanner scanner = new Scanner(line);
845    scanner.next();
846    @Interned String version = need(state, scanner, "declaration version number");
847    need_eol(state, scanner);
848    boolean new_df;
849    if (version == "2.0") { // interned
850      new_df = true;
851    } else if (version == "1.0") { // interned
852      new_df = false;
853    } else {
854      decl_error(state, "'%s' found where 1.0 or 2.0 expected", version);
855      throw new Error("Can't get here"); // help out definite assignment analysis
856    }
857
858    // Make sure that if a format was specified previously, it is the same
859    if ((new_decl_format != null) && (new_df != new_decl_format.booleanValue())) {
860      decl_error(state, "decl format '%s' does not match previous setting", version);
861    }
862
863    // System.out.println("setting new_decl_format = " + new_df);
864    new_decl_format = Boolean.valueOf(new_df);
865  }
866
867  // Each line following is the name (in JVM form) of a class that
868  // implements java.util.List.  All those lines (including interspersed
869  // comments) are returned.
870  private static String read_list_implementors(LineNumberReader reader) throws IOException {
871    StringJoiner result = new StringJoiner(lineSep);
872    for (; ; ) {
873      String line = reader.readLine();
874      if (line == null || line.equals("")) {
875        break;
876      }
877      result.add(line);
878      if (isComment(line)) {
879        continue;
880      }
881      ProglangType.list_implementors.add(line.intern());
882    }
883    return result.toString();
884  }
885
886  // ///////////////////////////////////////////////////////////////////////////
887  // invocation tracking for dtrace files entry/exit grouping
888  //
889
890  /** Represents an instance/invocation of a program point. */
891  static final class Invocation implements Comparable<Invocation> {
892    /** The program point; used in printing and in suppressing duplicates. */
893    PptTopLevel ppt;
894
895    /** The values. This array is used rather than a valuetuple. */
896    @Nullable Object[] vals;
897
898    /** The modbits. */
899    int[] mods;
900
901    static Object canonical_hashcode = new Object();
902
903    Invocation(PptTopLevel ppt, @Nullable Object[] vals, int[] mods) {
904      this.ppt = ppt;
905      this.vals = vals;
906      this.mods = mods;
907    }
908
909    /**
910     * Return a string representation of this. The Invocation is formatted on two lines, indented by
911     * two spaces. The receiver Invocation may be canonicalized or not.
912     *
913     * @return a string representation of this
914     */
915    String format(@GuardSatisfied Invocation this) {
916      return format(true);
917    }
918
919    /**
920     * Return a string representation of this. The Invocation is formatted on two lines, indented by
921     * two spaces. The receiver Invocation may be canonicalized or not.
922     *
923     * @param show_values if true, show values; otherwise, return just the Ppt name
924     * @return a string representation of this
925     */
926    String format(@GuardSatisfied Invocation this, boolean show_values) {
927      if (!show_values) {
928        return "  " + ppt.ppt_name.getNameWithoutPoint();
929      }
930
931      StringWriter sw = new StringWriter();
932      PrintWriter pw = new PrintWriter(sw);
933
934      pw.println("  " + ppt.ppt_name.getNameWithoutPoint());
935      pw.print("    ");
936
937      // [adonovan] is this sound? Let me know if not (sorry).
938      // assert ppt.var_infos.length == vals.length;
939
940      for (int j = 0; j < vals.length; j++) {
941        if (j != 0) {
942          pw.print(", ");
943        }
944
945        pw.print(ppt.var_infos[j].name() + "=");
946
947        Object val = vals[j];
948        if (canonical_hashcode.equals(
949            val)) // succeeds only for canonicalized Invocations.  Can be an == test, but there is
950          // little point.  val can be null, so it cannot be the receiver.
951          pw.print("<hashcode>");
952        else if (val instanceof int[]) {
953          pw.print(Arrays.toString((int[]) val));
954        } else if (val instanceof String) {
955          pw.print(StringsPlume.escapeNonASCII((String) val));
956        } else {
957          pw.print(val);
958        }
959      }
960      pw.println();
961
962      return sw.toString();
963    }
964
965    /** Change uses of hashcodes to canonical_hashcode. */
966    public @Interned Invocation canonicalize() {
967      @Nullable Object[] new_vals = new @Nullable Object[vals.length];
968      System.arraycopy(vals, 0, new_vals, 0, vals.length);
969      VarInfo[] vis = ppt.var_infos;
970      // Warning: abstraction violation!
971      for (VarInfo vi : vis) {
972        if ((vi.value_index != -1) && (vi.file_rep_type == ProglangType.HASHCODE)) {
973          new_vals[vi.value_index] = canonical_hashcode;
974        }
975      }
976      @SuppressWarnings("interning:cast.unsafe.constructor.invocation")
977      @Interned Invocation result = new @Interned Invocation(ppt, new_vals, mods);
978      return result;
979    }
980
981    // Return true if the invocations print the same
982    @EnsuresNonNullIf(result = true, expression = "#1")
983    @Pure
984    @Override
985    public boolean equals(@GuardSatisfied Invocation this, @GuardSatisfied @Nullable Object other) {
986      if (other instanceof FileIO.Invocation) {
987        return this.format().equals(((FileIO.Invocation) other).format());
988      } else {
989        return false;
990      }
991    }
992
993    @Pure
994    @Override
995    public int compareTo(@GuardSatisfied Invocation this, Invocation other) {
996      return ppt.name().compareTo(other.ppt.name());
997    }
998
999    @Pure
1000    @Override
1001    public int hashCode(@GuardSatisfied Invocation this) {
1002      return this.format().hashCode();
1003    }
1004  }
1005
1006  // I could save some Object overhead by using two parallel stacks
1007  // instead of Invocation objects; but that's not worth it.
1008
1009  // Map key is a (global, not per-procedure) nonce.
1010  // The nonce indicates which returns are associated with which entries.
1011  static HashMap<Integer, Invocation> call_hashmap = new HashMap<>();
1012  // call_stack is for procedures without nonces.
1013  static Deque<Invocation> call_stack = new ArrayDeque<Invocation>();
1014
1015  /**
1016   * Reads data from {@code .dtrace} files. For each record in the files, calls the appropriate
1017   * callback in the processor.
1018   *
1019   * @see #read_data_trace_files(Collection,PptMap,Processor,boolean)
1020   * @see #read_data_trace_file(String,PptMap,Processor,boolean,boolean)
1021   */
1022  public static void read_data_trace_files(Collection<String> files, PptMap all_ppts)
1023      throws IOException {
1024
1025    Processor processor = new Processor();
1026    read_data_trace_files(files, all_ppts, processor, true);
1027  }
1028
1029  /**
1030   * Reads data from {@code .dtrace} files. Calls {@link
1031   * #read_data_trace_file(String,PptMap,Processor,boolean,boolean)} for each element of filenames.
1032   *
1033   * @param ppts_may_be_new true if declarations of ppts read from the data trace file are new (and
1034   *     thus are not in all_ppts). false if the ppts may already be there.
1035   * @see #read_data_trace_file(String,PptMap,Processor,boolean,boolean)
1036   */
1037  public static void read_data_trace_files(
1038      Collection<String> files, PptMap all_ppts, Processor processor, boolean ppts_may_be_new)
1039      throws IOException {
1040
1041    for (String filename : files) {
1042      // System.out.printf("processing filename %s%n", filename);
1043      try {
1044        read_data_trace_file(filename, all_ppts, processor, false, ppts_may_be_new);
1045      } catch (Daikon.NormalTermination e) {
1046        throw e;
1047      } catch (Throwable e) {
1048        if (dkconfig_continue_after_file_exception) {
1049          System.out.println();
1050          System.out.println(
1051              "WARNING: Error while processing trace file; remaining records ignored.");
1052          System.out.print("Ignored backtrace:");
1053          e.printStackTrace(System.out);
1054          System.out.println();
1055        } else {
1056          throw e;
1057        }
1058      }
1059    }
1060    if (Daikon.server_dir != null) {
1061      // Yoav: server mode
1062      while (true) {
1063        @SuppressWarnings(
1064            "nullness") // server_dir is a directory; this was checked when the variable was set
1065        String @NonNull [] dir_files = Daikon.server_dir.list();
1066        Arrays.sort(dir_files);
1067        boolean hasEnd = false;
1068        for (String f : dir_files) {
1069          if (f.endsWith(".end")) {
1070            hasEnd = true;
1071          }
1072          if (f.endsWith(".end") || f.endsWith(".start")) {
1073            continue;
1074          }
1075          if (files.contains(f)) {
1076            continue;
1077          }
1078          files.add(f);
1079          System.out.println("Reading " + f);
1080          read_data_trace_file(
1081              new File(Daikon.server_dir, f).toString(),
1082              all_ppts,
1083              processor,
1084              false,
1085              ppts_may_be_new);
1086        }
1087        if (hasEnd) {
1088          break;
1089        }
1090        try {
1091          Thread.sleep(1000);
1092        } catch (java.lang.InterruptedException e) {
1093          // It's not a problem if the sleep is interrupted.
1094        }
1095      }
1096    }
1097
1098    process_unmatched_procedure_entries();
1099
1100    warn_if_hierarchy_mismatch(all_ppts);
1101  }
1102
1103  // Determine if dataflow hierarchy should have been used, and print
1104  // warning if this does not match Daikon.use_dataflow_hierarchy.
1105  // Dataflow hierarchy should be used only when all program points
1106  // correspond to points normally found in traces from a
1107  // programming languages.
1108  private static void warn_if_hierarchy_mismatch(PptMap all_ppts) {
1109
1110    boolean some_program_points = false;
1111    boolean all_program_points = true;
1112
1113    // Go through each top level ppt, and make all_program_points
1114    // false if at least one of them is not a program point normally
1115    // found in traces from programming languages.
1116    for (PptTopLevel ppt_top_level : all_ppts.ppt_all_iterable()) {
1117      boolean is_program_point =
1118          (ppt_top_level.ppt_name.isExitPoint()
1119              || ppt_top_level.ppt_name.isEnterPoint()
1120              || ppt_top_level.ppt_name.isThrowsPoint()
1121              || ppt_top_level.ppt_name.isObjectInstanceSynthetic()
1122              || ppt_top_level.ppt_name.isClassStaticSynthetic()
1123              || ppt_top_level.ppt_name.isGlobalPoint());
1124
1125      all_program_points = all_program_points && is_program_point;
1126      some_program_points = some_program_points || is_program_point;
1127    }
1128
1129    // If all program points correspond to a programming language,
1130    // but the dataflow hierarchy has been turned off, then
1131    // suggest not using the --nohierarchy flag.
1132    //    if (all_program_points && (!Daikon.use_dataflow_hierarchy)) {
1133    //      System.out.println("Warning: data trace appears to be over" +
1134    //                         " a program execution, but dataflow" +
1135    //                         " hierarchy has been turned off," +
1136    //                         " consider running Daikon without the" +
1137    //                         " --nohierarchy flag");
1138    //    }
1139
1140    // if some of the program points do not correspond to a
1141    // points from a programming language, and the dataflow
1142    // hierarchy is being used, suggest using the --nohierarchy flag.
1143    if (Daikon.use_dataflow_hierarchy && !all_program_points && some_program_points) {
1144      System.out.println(
1145          "Warning: Daikon is using a dataflow"
1146              + " hierarchy analysis on a data trace"
1147              + " that does not appear to be over a"
1148              + " program execution.  Consider running"
1149              + " Daikon with the --nohierarchy flag.");
1150    }
1151  }
1152
1153  /**
1154   * Connect to Chicory.
1155   *
1156   * @return the stream that is connected to Chicory
1157   */
1158  private static @Owning InputStream connectToChicory() throws IOException {
1159    Socket chicSocket = null;
1160
1161    // bind to any free port
1162    try (ServerSocket daikonServer = new ServerSocket(0)) {
1163      // tell Chicory what port we have!
1164      System.out.println("DaikonChicoryOnlinePort=" + daikonServer.getLocalPort());
1165      daikonServer.setReceiveBufferSize(64000);
1166      daikonServer.setSoTimeout(5000);
1167      chicSocket = daikonServer.accept();
1168      return chicSocket.getInputStream();
1169    } catch (Exception e) {
1170      if (chicSocket != null) {
1171        try {
1172          chicSocket.close();
1173        } catch (Exception closeException) {
1174          // do nothing
1175        }
1176      }
1177      throw e;
1178    }
1179  }
1180
1181  /**
1182   * A Processor is used to read a dtrace file. A Processor defines callbacks for each record type
1183   * in a dtrace file. As each record is read from a dtrace file, the corresponding callback is
1184   * called.
1185   *
1186   * <p>to use a Processor, pass it to {@link #read_data_trace_files(Collection, PptMap,
1187   * FileIO.Processor, boolean)}. {@code read_data_trace_files} will call {@link
1188   * #process_sample(PptMap,PptTopLevel,ValueTuple,Integer)} once for every sample in the dtrace
1189   * file, and will call other callbacks for other records in the dtrace file.
1190   *
1191   * <p>For an example of how to create and use a Processor, see {@link daikon.tools.ReadTrace}.
1192   *
1193   * @see #read_data_trace_files(Collection, PptMap, FileIO.Processor, boolean)
1194   * @see daikon.tools.ReadTrace
1195   */
1196  public static class Processor {
1197    /**
1198     * Process a data sample record. This default implementation calls {@link
1199     * FileIO#process_sample(PptMap, PptTopLevel, ValueTuple, Integer)}.
1200     *
1201     * @see FileIO#process_sample(PptMap, PptTopLevel, ValueTuple, Integer)
1202     */
1203    @RequiresNonNull("FileIO.data_trace_state")
1204    public void process_sample(
1205        PptMap all_ppts, PptTopLevel ppt, ValueTuple vt, @Nullable Integer nonce) {
1206      FileIO.process_sample(all_ppts, ppt, vt, nonce);
1207    }
1208
1209    /** Process a program point declaration record. */
1210    public void process_decl(PptMap all_ppts, PptTopLevel ppt) {}
1211
1212    /** Process a ppt decl format record. */
1213    public void process_decl_version(String format) {}
1214
1215    /** Process a VarComparability declaration. */
1216    public void process_comparability(String comparability) {}
1217
1218    /** Process a ListImplementors declaration. */
1219    public void process_list_implementors(String implementors) {}
1220
1221    /** Process an input-language declaration. */
1222    public void process_input_language(String language) {}
1223
1224    /** Process a null record (haven't read anything yet). */
1225    public void process_null() {}
1226
1227    /** Process a comment. */
1228    public void process_comment(String comment) {}
1229
1230    /** Process indication of end of file. */
1231    public void process_eof() {}
1232
1233    /** Process indication of exceeding file size limit. */
1234    public void process_truncated() {}
1235
1236    /** Process continuable error. */
1237    public void process_error() {}
1238  }
1239
1240  /**
1241   * Total number of samples passed to process_sample(). Not part of ParseState because it's global
1242   * over all files processed by Daikon.
1243   */
1244  public static int samples_processed = 0;
1245
1246  /** The type of the record that was most recently read. */
1247  public enum RecordType {
1248    SAMPLE, // got a sample
1249
1250    DECL, // got a ppt decl
1251    DECL_VERSION, // got an indication of the ppt decl format
1252    COMPARABILITY, // got a VarComparability declaration
1253    LIST_IMPLEMENTORS, // got a ListImplementors declaration
1254    INPUT_LANGUAGE, // got an input-language declaration
1255
1256    NULL, // haven't read anything yet
1257    COMMENT, // got a comment
1258    EOF, // reached end of file
1259    TRUNCATED, // dkconfig_max_line_number reached (without error)
1260    ERROR, // continuable error; fatal errors thrown as exceptions
1261  };
1262
1263  /**
1264   * ParseState indicates:
1265   *
1266   * <ol>
1267   *   <li>Some global information about the state of the parser while reading a decl or dtrace
1268   *       file.
1269   *   <li>The record that was most recently read; thus, ParseState is essentially a discriminated
1270   *       union whose tag is a RecordType. (TODO: These are poor names that should probably be
1271   *       swapped!) ParseState is what is returned (actually, side-effected) by method
1272   *       read_data_trace_record when it reads a record.
1273   * </ol>
1274   */
1275  @UsesObjectEquals
1276  @MustCall("close") public static class ParseState implements Closeable {
1277
1278    //
1279    // This is the global information about the state of the parser.
1280    //
1281
1282    /** Name of input file. */
1283    public String filename;
1284
1285    /** True if the current file is a declaration file. */
1286    public boolean is_decl_file;
1287
1288    /**
1289     * True if ppts may be new. If a duplicate is seen, it must match a previous point exactly. If
1290     * false, the previous ppt is used without checking for a match.
1291     */
1292    public boolean ppts_may_be_new;
1293
1294    /** All of the ppts seen so far. */
1295    public PptMap all_ppts;
1296
1297    /** Input stream. */
1298    public final @Owning LineNumberReader reader;
1299
1300    /** Total number of lines in the input file. */
1301    public long total_lines;
1302
1303    /** Comparability format, either VarComparability.IMPLICIT or VarComparability.NONE. */
1304    public int varcomp_format;
1305
1306    //
1307    // This is the discriminated-union part of the ParseState.
1308    // (Presumably this design was chosen for efficiency, to avoid creating
1309    // & garbage-collecting these values many times.)
1310    //
1311
1312    public RecordType rtype;
1313
1314    /**
1315     * Current ppt. Used when status=DECL or SAMPLE. Can be null if this declaration was skipped
1316     * because of {@code --ppt-select-pattern} or {@code --ppt-omit-pattern}.
1317     */
1318    public @Nullable PptTopLevel ppt;
1319
1320    /** The current nonce. Used when status=SAMPLE. */
1321    public @Nullable Integer nonce;
1322
1323    /** The current set of values. Used when status=SAMPLE. */
1324    public @Nullable ValueTuple vt;
1325
1326    /** Miscellaneous text in the parsed item. */
1327    public @Nullable Object payload; // used when status=COMMENT
1328
1329    /**
1330     * Start parsing the given file.
1331     *
1332     * @param raw_filename the file name supplied by the user; may be "-" or "+"
1333     * @param decl_file_p true if the file is a declaration file
1334     * @param ppts_may_be_new true if declarations of ppts read from the data trace file are new
1335     *     (and thus are not in all_ppts). false if the ppts may already be there.
1336     * @param ppts the program points
1337     * @throws IOException if there is a problem reading or writing a file
1338     */
1339    @SuppressWarnings("StaticAssignmentInConstructor") // for progress output
1340    public ParseState(
1341        String raw_filename, boolean decl_file_p, boolean ppts_may_be_new, PptMap ppts)
1342        throws IOException {
1343      // Pretty up raw_filename for use in messages
1344      if (raw_filename.equals("-")) {
1345        filename = "standard input";
1346      } else if (raw_filename.equals("+")) {
1347        filename = "chicory socket";
1348      } else {
1349        // Remove directory parts, to make it shorter
1350        filename = raw_filename;
1351      }
1352
1353      is_decl_file = decl_file_p;
1354      this.ppts_may_be_new = ppts_may_be_new;
1355      all_ppts = ppts;
1356
1357      boolean is_url = raw_filename.startsWith("file:") || raw_filename.startsWith("jar:");
1358
1359      // Do we need to count the lines in the file?
1360      total_lines = 0;
1361      boolean count_lines = dkconfig_count_lines;
1362      if (is_decl_file) {
1363        count_lines = false;
1364      } else if (dkconfig_dtrace_line_count != 0) {
1365        total_lines = dkconfig_dtrace_line_count;
1366        count_lines = false;
1367      } else if (filename.equals("-")) {
1368        count_lines = false;
1369      } else if (is_url) {
1370        count_lines = false;
1371      } else if (Daikon.dkconfig_progress_delay == -1) {
1372        count_lines = false;
1373      } else if (new File(raw_filename).length() == 0) {
1374        // Either it's actually empty, or it's something like a pipe.
1375        count_lines = false;
1376      }
1377
1378      if (count_lines) {
1379        Daikon.progress = "Checking size of " + filename;
1380        total_lines = FilesPlume.countLines(raw_filename);
1381      } else {
1382        // System.out.printf("no count %b %d %s %d %d%n", is_decl_file,
1383        //                    dkconfig_dtrace_line_count, filename,
1384        //  Daikon.dkconfig_progress_delay, (new File(raw_filename)).length());
1385      }
1386
1387      // Open the reader stream
1388      if (raw_filename.equals("-")) {
1389        // "-" means read from the standard input stream
1390        Reader file_reader = new InputStreamReader(System.in, "ISO-8859-1");
1391        reader = new LineNumberReader(file_reader);
1392      } else if (raw_filename.equals("+")) { // socket comm with Chicory
1393        InputStream chicoryInput = connectToChicory();
1394        InputStreamReader chicReader = new InputStreamReader(chicoryInput, UTF_8);
1395        reader = new LineNumberReader(chicReader);
1396      } else if (is_url) {
1397        URL url = URI.create(raw_filename).toURL();
1398        InputStream stream = null; // dummy initialization for compiler's definite assignment check
1399        try {
1400          stream = url.openStream();
1401          InputStream gzip_stream =
1402              raw_filename.endsWith(".gz") ? new GZIPInputStream(stream) : stream;
1403          InputStreamReader isr = new InputStreamReader(gzip_stream, UTF_8);
1404          LineNumberReader lnr = new LineNumberReader(isr);
1405          reader = lnr;
1406        } catch (IOException e) {
1407          if (stream != null) {
1408            stream.close();
1409          }
1410          throw e;
1411        }
1412      } else {
1413        reader = FilesPlume.newLineNumberFileReader(raw_filename);
1414      }
1415
1416      varcomp_format = VarComparability.IMPLICIT;
1417      rtype = RecordType.NULL;
1418      ppt = null;
1419    }
1420
1421    /** Releases resources held by this. */
1422    @Override
1423    @EnsuresCalledMethods(value = "reader", methods = "close")
1424    public void close(@GuardSatisfied ParseState this) {
1425      try {
1426        reader.close();
1427      } catch (IOException e) {
1428        throw new BugInDaikon(e);
1429      }
1430    }
1431
1432    /**
1433     * Returns the current line number in the input file, or -1 if not available.
1434     *
1435     * @return the current line number in the input file, or -1 if not available
1436     */
1437    public int get_linenum() {
1438      return reader.getLineNumber();
1439    }
1440
1441    private static NumberFormat pctFmt;
1442
1443    static {
1444      pctFmt = NumberFormat.getPercentInstance();
1445      pctFmt.setMinimumFractionDigits(2);
1446      pctFmt.setMaximumFractionDigits(2);
1447    }
1448
1449    public String reading_message() {
1450      String line;
1451      if (reader == null) {
1452        line = "?";
1453      } else {
1454        long lineNum = reader.getLineNumber();
1455        line = String.valueOf(lineNum);
1456        if (total_lines > 0) {
1457          double frac = lineNum / (double) total_lines;
1458          String percent = pctFmt.format(frac);
1459          line = line + ", " + percent;
1460        }
1461      }
1462      return "Reading " + filename + " (line " + line + ") ...";
1463    }
1464
1465    public String line_file_message() {
1466      return String.format(" at line %d in file %s", reader.getLineNumber(), filename);
1467    }
1468  }
1469
1470  /** Returns the current line number in the input file, or -1 if not available. */
1471  public static int get_linenum() {
1472    if (FileIO.data_trace_state == null) {
1473      return -1;
1474    } else {
1475      return FileIO.data_trace_state.get_linenum();
1476    }
1477  }
1478
1479  /**
1480   * Logically, this is a local variable in static method read_data_trace_file. It is used for
1481   * status output, and to give the line number at which a problem was detected.
1482   */
1483  // The @MonotonicNonNull property is not true globally, but within every
1484  // method it's true, so it is a useful annotation.
1485  public static @MonotonicNonNull ParseState data_trace_state = null;
1486
1487  // The variable is only ever cleared at the end of a routine that set it.
1488  @SuppressWarnings("nullness") // reinitialization
1489  private static void clear_data_trace_state() {
1490    FileIO.data_trace_state = null;
1491  }
1492
1493  /**
1494   * Read only samples from {@code .dtrace} file. Uses the standard data processor which calls
1495   * {@link FileIO#process_sample(PptMap, PptTopLevel, ValueTuple, Integer)} on each record, and
1496   * ignores records other than samples.
1497   */
1498  public static void read_data_trace_file(String filename, PptMap all_ppts) throws IOException {
1499    Processor processor = new Processor();
1500    read_data_trace_file(filename, all_ppts, processor, false, true);
1501  }
1502
1503  /**
1504   * Read declarations AND samples (not just sample data as the name might imply) from {@code
1505   * .dtrace} file. For each record read from the file, passes the record to a method of the
1506   * processor.
1507   */
1508  public static void read_data_trace_file(
1509      String filename,
1510      PptMap all_ppts,
1511      Processor processor,
1512      boolean is_decl_file,
1513      boolean ppts_may_be_new)
1514      throws IOException {
1515
1516    if (debugRead.isLoggable(Level.FINE)) {
1517      debugRead.fine(
1518          "read_data_trace_file "
1519              + filename
1520              + ((Daikon.ppt_regexp != null) ? " " + Daikon.ppt_regexp.pattern() : "")
1521              + ((Daikon.ppt_omit_regexp != null) ? " " + Daikon.ppt_omit_regexp.pattern() : ""));
1522    }
1523
1524    try (ParseState data_trace_state =
1525        new ParseState(filename, is_decl_file, ppts_may_be_new, all_ppts)) {
1526      FileIO.data_trace_state = data_trace_state;
1527
1528      // Used for debugging: write new data trace file.
1529      if (Global.debugPrintDtrace) {
1530        Path p = new File(filename + ".debug").toPath();
1531        BufferedWriter bw = null; // dummy initialization for compiler's definite assignment check
1532        try {
1533          bw = Files.newBufferedWriter(p, UTF_8);
1534          Global.dtraceWriter = new PrintWriter(bw);
1535        } catch (IOException e) {
1536          if (bw != null) {
1537            bw.close();
1538          }
1539          throw e;
1540        }
1541      }
1542
1543      while (true) {
1544        read_data_trace_record(data_trace_state);
1545
1546        if (data_trace_state.rtype == RecordType.SAMPLE) {
1547          assert data_trace_state.ppt != null
1548              : "@AssumeAssertion(nullness): dependent: RecordType.SAMPLE";
1549          assert data_trace_state.vt != null
1550              : "@AssumeAssertion(nullness): dependent: RecordType.SAMPLE";
1551          // Nonce may be null
1552          samples_processed++;
1553          // Add orig and derived variables; pass to inference (add_and_flow)
1554          try {
1555            processor.process_sample(
1556                data_trace_state.all_ppts,
1557                data_trace_state.ppt,
1558                data_trace_state.vt,
1559                data_trace_state.nonce);
1560          } catch (Error e) {
1561            // e.printStackTrace();
1562            if (!dkconfig_continue_after_file_exception) {
1563              throw new Daikon.UserError(e, data_trace_state);
1564            } else {
1565              System.out.println();
1566              System.out.println(
1567                  "WARNING: Error while processing trace file; subsequent records ignored.");
1568              System.out.print("Ignored backtrace:");
1569              e.printStackTrace(System.out);
1570              System.out.println();
1571            }
1572          }
1573        } else if ((data_trace_state.rtype == RecordType.EOF)
1574            || (data_trace_state.rtype == RecordType.TRUNCATED)) {
1575          break;
1576        } else {
1577          // don't need to do anything explicit for other records found
1578        }
1579      }
1580
1581      if (Global.debugPrintDtrace) {
1582        assert Global.dtraceWriter != null
1583            : "@AssumeAssertion(nullness): dependent: set if debugPrintDtrace is true";
1584        Global.dtraceWriter.close();
1585      }
1586
1587      Daikon.progress = "Finished reading " + data_trace_state.filename;
1588
1589      clear_data_trace_state();
1590    }
1591  }
1592
1593  /**
1594   * Like read_data_trace_record, but sets global FileIO.data_trace_state for the duration of the
1595   * call then clears it before returning. Intended for most external callers.
1596   */
1597  public static void read_data_trace_record_setstate(ParseState state) throws IOException {
1598
1599    FileIO.data_trace_state = state;
1600    read_data_trace_record(state);
1601    clear_data_trace_state();
1602  }
1603
1604  /**
1605   * Read a single record of ANY type (sample, declaration, comparability, etc.) from a dtrace file.
1606   * If the record is anything but a sample, also processes it. The record is stored by side effect
1607   * into the state argument.
1608   */
1609  // TODO:  For clarity, this should perhaps return its side-effected argument.
1610  @RequiresNonNull("FileIO.data_trace_state")
1611  // not guaranteed: File might be empty  EnsuresNonNull("FileIO.new_decl_format")
1612  public static void read_data_trace_record(ParseState state) throws IOException {
1613
1614    // Abstract out the test result into a variable because Java doesn't
1615    // permit suppressing warnings on a statement.  Yuck.
1616    boolean stateOK = (state == FileIO.data_trace_state);
1617    assert stateOK;
1618
1619    LineNumberReader reader = state.reader;
1620
1621    for (String line = reader.readLine(); line != null; line = reader.readLine()) {
1622      if (line.equals("")) {
1623        continue;
1624      }
1625
1626      // This cleverness would not be necessary if every comment was followed by
1627      // a blank line.  We can't depend on that, though.
1628      if (isComment(line)) {
1629        StringJoiner commentLines = new StringJoiner(lineSep);
1630        commentLines.add(line);
1631        while (nextLineIsComment(reader)) {
1632          commentLines.add(reader.readLine());
1633        }
1634        state.payload = commentLines.toString();
1635        state.rtype = RecordType.COMMENT;
1636        return;
1637      }
1638
1639      // stop at a specified point in the file
1640      if ((dkconfig_max_line_number > 0) && (reader.getLineNumber() > dkconfig_max_line_number)) {
1641        state.rtype = RecordType.TRUNCATED;
1642        return;
1643      }
1644
1645      // interning bugfix:  no need to intern "line" (after code change to is_declaration_header)
1646
1647      // Check for the file format
1648      if (line.startsWith("decl-version")) {
1649        read_decl_version(state, line);
1650        state.payload = (new_decl_format ? "2.0" : "1.0");
1651        state.payload = (FileIO.new_decl_format ? "2.0" : "1.0");
1652        state.rtype = RecordType.DECL_VERSION;
1653        return;
1654      }
1655
1656      // Check for the input language
1657      if (line.startsWith("input-language")) {
1658        String input_language = read_input_language(state, line);
1659        state.payload = input_language;
1660        state.rtype = RecordType.INPUT_LANGUAGE;
1661        return;
1662      }
1663
1664      // If we have gotten to here and new_decl_format is not set, presume
1665      // it is the old format
1666      if (new_decl_format == null) {
1667        // System.out.printf("setting new_decl_format to false%n");
1668        new_decl_format = Boolean.FALSE;
1669      }
1670
1671      // First look for declarations in the dtrace stream
1672      if (is_declaration_header(line)) {
1673        if (new_decl_format) {
1674          state.ppt = read_ppt_decl(state, line);
1675        } else {
1676          state.ppt = read_declaration(state);
1677        }
1678        // ppt can be null if this declaration was skipped because of
1679        // --ppt-select-pattern or --ppt-omit-pattern.
1680        if (state.ppt != null) {
1681          if (!state.all_ppts.containsName(state.ppt.name())) {
1682            state.all_ppts.add(state.ppt);
1683            assert state.ppt != null : "@AssumeAssertion(nullness)";
1684            try {
1685              Daikon.init_ppt(state.ppt, state.all_ppts);
1686            } catch (Exception e) {
1687              decl_error(state, e);
1688            }
1689          }
1690        }
1691        state.rtype = RecordType.DECL;
1692        return;
1693      }
1694      if (line.equals("VarComparability") || line.startsWith("var-comparability")) {
1695        state.varcomp_format = read_var_comparability(state, line);
1696        state.rtype = RecordType.COMPARABILITY;
1697        return;
1698      }
1699      if (line.equals("ListImplementors")) {
1700        state.payload = read_list_implementors(reader);
1701        state.rtype = RecordType.LIST_IMPLEMENTORS;
1702        return;
1703      }
1704      String ppt_name = line;
1705      if (new_decl_format) {
1706        // interning bugfix: no need to intern
1707        ppt_name = unescape_decl(line);
1708      }
1709      ppt_name = user_mod_ppt_name(ppt_name);
1710      if (!ppt_included(ppt_name)) {
1711        // System.out.printf("skipping ppt %s%n", line);
1712        while ((line != null) && !line.equals("")) line = reader.readLine();
1713        continue;
1714      }
1715      // System.out.printf("Not skipping ppt  %s%n", line);
1716
1717      if (state.is_decl_file) {
1718        if (!new_decl_format && line.startsWith("ppt ")) {
1719          throw new Daikon.UserError(
1720              String.format(
1721                  "Declaration file %s is not version 2.0, but line %d looks like a version 2.0"
1722                      + " declaration: %s%nPerhaps the file is missing a \"decl-version 2.0\""
1723                      + " record at the beginning",
1724                  state.filename, state.reader.getLineNumber(), line));
1725        }
1726        throw new Daikon.UserError(
1727            String.format(
1728                "Declaration files should not contain samples, but file %s does at line %d: %s",
1729                state.filename, state.reader.getLineNumber(), line));
1730      }
1731
1732      // Parse the ppt name
1733      try {
1734        new PptName(ppt_name);
1735      } catch (Throwable t) {
1736        @SuppressWarnings("nullness") // thrown exception always has a detail message
1737        @NonNull String message = t.getMessage();
1738        // Augment the message with line number information.
1739        if (!(t instanceof Daikon.UserError)) {
1740          message = String.format("Illegal program point name '%s' (%s)", ppt_name, message);
1741        }
1742        throw new Daikon.UserError(message, reader, state.filename);
1743      }
1744
1745      if (state.all_ppts.size() == 0) {
1746        throw new Daikon.UserError(
1747            "No declarations were provided before the first sample.  Perhaps you did not supply"
1748                + " the proper .decls file to Daikon.  (Or, there could be a bug in the front end"
1749                + " that created the .dtrace file "
1750                + state.filename
1751                + ".)");
1752      }
1753
1754      PptTopLevel ppt = state.all_ppts.get(ppt_name);
1755      if (ppt == null) {
1756        throw new Daikon.UserError(
1757            "No declaration was provided for program point " + ppt_name, state);
1758      }
1759
1760      // not vis.length, as that includes constants, derived variables, etc.
1761      // Actually, we do want to leave space for _orig vars.
1762      // And for the time being (and possibly forever), for derived variables.
1763      int vals_array_size = ppt.var_infos.length - ppt.num_static_constant_vars;
1764
1765      // Read an invocation nonce if one exists
1766      Integer nonce;
1767
1768      boolean nonce_exists;
1769      {
1770        String nonce_header_peekahead;
1771        // arbitrary number, hopefully big enough; catch exceptions
1772        reader.mark(1000);
1773        try {
1774          nonce_header_peekahead = reader.readLine();
1775        } catch (Exception e) {
1776          nonce_header_peekahead = null;
1777        }
1778        reader.reset();
1779        nonce_exists = NONCE_HEADER.equals(nonce_header_peekahead);
1780      }
1781      if (!nonce_exists) {
1782        nonce = null;
1783      } else {
1784        @SuppressWarnings("nullness") // nonce_exists is true, so readLine() returns non-null
1785        @NonNull String nonce_header = reader.readLine(); // read & discard header
1786        assert NONCE_HEADER.equals(nonce_header);
1787        String nonce_number = reader.readLine();
1788        if (nonce_number == null) {
1789          throw new Daikon.UserError("File ended while trying to read nonce", state);
1790        }
1791        nonce = Integer.valueOf(nonce_number);
1792
1793        if (Global.debugPrintDtrace) {
1794          to_write_nonce = true;
1795          nonce_value = nonce.toString();
1796        }
1797      }
1798
1799      @Nullable Object[] vals = new @Nullable Object[vals_array_size];
1800      int[] mods = new int[vals_array_size];
1801
1802      // Read a single record from the trace file;
1803      // fills up vals and mods arrays by side effect.
1804      try {
1805        read_vals_and_mods_from_trace_file(reader, state.filename, ppt, vals, mods);
1806      } catch (IOException e) {
1807        String nextLine = reader.readLine();
1808        if ((e instanceof EOFException) || (nextLine == null)) {
1809          System.out.println();
1810          System.out.println(
1811              "WARNING: Unexpected EOF while processing "
1812                  + "trace file - last record of trace file ignored");
1813          state.rtype = RecordType.EOF;
1814          return;
1815        } else if (dkconfig_continue_after_file_exception) {
1816          System.out.println();
1817          System.out.println("WARNING: IOException while processing trace file - record ignored");
1818          System.out.print("Ignored backtrace:");
1819          e.printStackTrace(System.out);
1820          System.out.println();
1821          while (nextLine != null && !nextLine.equals("")) {
1822            // System.out.println("Discarded line " + reader.getLineNumber()
1823            //                     + ": " + nextLine);
1824            nextLine = reader.readLine();
1825          }
1826          continue;
1827        } else {
1828          throw e;
1829        }
1830      }
1831
1832      state.ppt = ppt;
1833      state.nonce = nonce;
1834      state.vt = ValueTuple.makeUninterned(vals, mods);
1835      state.rtype = RecordType.SAMPLE;
1836      return;
1837    }
1838
1839    state.rtype = RecordType.EOF;
1840  }
1841
1842  /**
1843   * Add orig() and derived variables to vt (by side effect), then supply it to the program point
1844   * for flowing.
1845   *
1846   * @param vt trace data only; modified by side effect to add derived vars
1847   */
1848  @RequiresNonNull("FileIO.data_trace_state")
1849  public static void process_sample(
1850      PptMap all_ppts, PptTopLevel ppt, ValueTuple vt, @Nullable Integer nonce) {
1851
1852    // Add orig variables.  This must be above the check below because
1853    // it saves away the orig values from enter points for later use
1854    // by exit points.
1855    boolean ignore = compute_orig_variables(ppt, vt.vals, vt.mods, nonce);
1856    if (ignore) {
1857      return;
1858    }
1859
1860    // Only process the leaves of the ppt tree.
1861    // This test assumes that all leaves are numbered exit program points
1862    // -- that is, points of the form foo:::EXIT22 for which isExitPoint()
1863    // is true and isCombinedExitPoint() is false.  "Combined" exit points
1864    // of the form foo:::EXIT are not processed -- they are assumed to be
1865    // non-leaves.
1866    if (Daikon.use_dataflow_hierarchy) {
1867
1868      // Rather than defining leaves as :::EXIT54 (numbered exit)
1869      // program points define them as everything except
1870      // ::EXIT (combined), :::ENTER, :::THROWS, :::OBJECT, ::GLOBAL
1871      //  and :::CLASS program points.  This scheme ensures that arbitrarly
1872      //  named program points such as :::POINT (used by convertcsv.pl)
1873      //  will be treated as leaves.
1874
1875      if (ppt.ppt_name.isEnterPoint()
1876          || ppt.ppt_name.isThrowsPoint()
1877          || ppt.ppt_name.isObjectInstanceSynthetic()
1878          || ppt.ppt_name.isClassStaticSynthetic()
1879          || ppt.ppt_name.isGlobalPoint()) {
1880        return;
1881      }
1882
1883      if (ppt.ppt_name.isExitPoint() && ppt.ppt_name.isCombinedExitPoint()) {
1884        // not Daikon.UserError; caller has more info (e.g., filename)
1885        throw new RuntimeException(
1886            "Bad program point name " + ppt.name + " is a combined exit point name");
1887      }
1888    }
1889
1890    // Add derived variables
1891    compute_derived_variables(ppt, vt.vals, vt.mods);
1892
1893    // Causes interning
1894    vt = new ValueTuple(vt.vals, vt.mods);
1895
1896    if (debugRead.isLoggable(Level.FINE)) {
1897      debugRead.fine("Adding ValueTuple to " + ppt.name());
1898      debugRead.fine("  length is " + vt.vals.length);
1899    }
1900
1901    // If we are only reading the sample, don't process them
1902    if (dkconfig_read_samples_only) {
1903      return;
1904    }
1905
1906    @SuppressWarnings({"UnusedVariable", "nullness:contracts.precondition"})
1907    Object dummy = ppt.add_bottom_up(vt, 1);
1908
1909    if (debugVars.isLoggable(Level.FINE)) {
1910      debugVars.fine(ppt.name() + " vars: " + Debug.int_vars(ppt, vt));
1911    }
1912
1913    if (Global.debugPrintDtrace) {
1914      assert Global.dtraceWriter != null
1915          : "@AssumeAssertion(nullness): dependent: set if debugPrintDtrace is true";
1916      Global.dtraceWriter.close();
1917    }
1918  }
1919
1920  /** Returns true if this procedure has an unmatched entry. */
1921  static boolean has_unmatched_procedure_entry(PptTopLevel ppt) {
1922    for (Invocation invok : call_hashmap.values()) {
1923      if (invok.ppt == ppt) {
1924        return true;
1925      }
1926    }
1927    for (Invocation invok : call_stack) {
1928      if (invok.ppt == ppt) {
1929        return true;
1930      }
1931    }
1932    return false;
1933  }
1934
1935  /** Print each call that does not have a matching exit. */
1936  public static void process_unmatched_procedure_entries() {
1937
1938    if (dkconfig_unmatched_procedure_entries_quiet) {
1939      return;
1940    }
1941
1942    int unmatched_count = call_stack.size() + call_hashmap.size();
1943
1944    if (!call_stack.isEmpty() || !call_hashmap.isEmpty()) {
1945      System.out.println();
1946      System.out.print(
1947          "No return from procedure observed "
1948              + StringsPlume.nplural(unmatched_count, "time")
1949              + ".");
1950      if (Daikon.use_dataflow_hierarchy) {
1951        System.out.print("  Unmatched entries are ignored!");
1952      }
1953      System.out.println();
1954      if (!call_hashmap.isEmpty()) {
1955        // Put the invocations in sorted order for printing.
1956        ArrayList<Invocation> invocations = new ArrayList<>();
1957        for (@KeyFor("call_hashmap") Integer i : CollectionsPlume.sortedKeySet(call_hashmap)) {
1958          Invocation invok = call_hashmap.get(i);
1959          assert invok != null;
1960          invocations.add(invok);
1961        }
1962        System.out.println("Unterminated calls:");
1963        if (dkconfig_verbose_unmatched_procedure_entries) {
1964          print_invocations_verbose(invocations);
1965        } else {
1966          print_invocations_grouped(invocations);
1967        }
1968      }
1969
1970      if (!call_stack.isEmpty()) {
1971        if (dkconfig_verbose_unmatched_procedure_entries) {
1972          System.out.println(
1973              "Remaining "
1974                  + StringsPlume.nplural(unmatched_count, "stack")
1975                  + " call summarized below.");
1976          print_invocations_verbose(call_stack);
1977        } else {
1978          print_invocations_grouped(call_stack);
1979        }
1980      }
1981      System.out.print("End of report for procedures not returned from.");
1982      if (Daikon.use_dataflow_hierarchy) {
1983        System.out.print("  Unmatched entries are ignored!");
1984      }
1985      System.out.println();
1986    }
1987  }
1988
1989  /** Print all the invocations in the collection, in order. */
1990  static void print_invocations_verbose(Collection<Invocation> invocations) {
1991    for (Invocation invok : invocations) {
1992      System.out.println(invok.format());
1993    }
1994  }
1995
1996  /** Print the invocations in the collection, in order, and coalescing duplicates. */
1997  static void print_invocations_grouped(Collection<Invocation> invocations) {
1998    Map<@Interned String, Integer> counter = new LinkedHashMap<>();
1999
2000    for (Invocation invok_noncanonical : invocations) {
2001      @Interned Invocation invok = invok_noncanonical.canonicalize();
2002      String invokString = invok.format(false).intern();
2003      if (counter.containsKey(invokString)) {
2004        Integer oldCount = counter.get(invokString);
2005        Integer newCount = oldCount.intValue() + 1;
2006        counter.put(invokString, newCount);
2007      } else {
2008        counter.put(invokString, 1);
2009      }
2010    }
2011
2012    // Print the invocations in sorted order.
2013    for (Map.Entry<@Interned String, Integer> invokEntry : counter.entrySet()) {
2014      System.out.println(
2015          invokEntry.getKey() + " : " + StringsPlume.nplural(invokEntry.getValue(), "invocation"));
2016    }
2017  }
2018
2019  // This procedure reads a single record from a trace file and
2020  // fills up vals and mods by side effect.  The ppt name and
2021  // invocation nonce (if any) have already been read.
2022  @RequiresNonNull("FileIO.data_trace_state")
2023  private static void read_vals_and_mods_from_trace_file(
2024      LineNumberReader reader,
2025      String filename,
2026      PptTopLevel ppt,
2027      @Nullable Object[] vals,
2028      int[] mods)
2029      throws IOException {
2030    VarInfo[] vis = ppt.var_infos;
2031    int num_tracevars = ppt.num_tracevars;
2032
2033    /*NNC:@Nullable*/ String[] oldvalue_reps = ppt_to_value_reps.get(ppt);
2034    if (oldvalue_reps == null) {
2035      // We've not encountered this program point before.  The nulls in
2036      // this array will compare non-equal to whatever is in the trace
2037      // file, which is the desired behavior.
2038      oldvalue_reps = new /*NNC:@Nullable*/ String[num_tracevars];
2039    }
2040
2041    if (Global.debugPrintDtrace) {
2042      assert Global.dtraceWriter != null
2043          : "@AssumeAssertion(nullness): dependent: set if debugPrintDtrace is true";
2044      Global.dtraceWriter.println(ppt.name());
2045
2046      if (to_write_nonce) {
2047        Global.dtraceWriter.println(NONCE_HEADER);
2048        Global.dtraceWriter.println(nonce_value);
2049        to_write_nonce = false;
2050      }
2051    }
2052
2053    for (int vi_index = 0, val_index = 0; val_index < num_tracevars; vi_index++) {
2054      assert vi_index < vis.length
2055          : "Got to vi_index "
2056              + vi_index
2057              + " after "
2058              + val_index
2059              + " of "
2060              + num_tracevars
2061              + " values";
2062      VarInfo vi = vis[vi_index];
2063      assert !vi.is_static_constant || (vi.value_index == -1)
2064      // : "Bad value_index " + vi.value_index + " when static_constant_value = " +
2065      // vi.static_constant_value + " for " + vi.repr() + " at " + ppt_name
2066      ;
2067      if (vi.is_static_constant) {
2068        continue;
2069      }
2070      assert val_index == vi.value_index
2071      // : "Differing val_index = " + val_index
2072      // + " and vi.value_index = " + vi.value_index
2073      // + " for " + vi.name + lineSep + vi.repr()
2074      ;
2075
2076      // In errors, say "for program point", not "at program point" as the
2077      // latter confuses Emacs goto-error.
2078
2079      String line = reader.readLine();
2080      if (line == null) {
2081        throw new Daikon.UserError(
2082            "Unexpected end of file at "
2083                + data_trace_state.filename
2084                + " line "
2085                + reader.getLineNumber()
2086                + lineSep
2087                + "  Expected variable "
2088                + vi.name()
2089                + ", got "
2090                + "null" // line
2091                + " for program point "
2092                + ppt.name());
2093      }
2094
2095      // Read lines until an included variable is found
2096      while ((line != null) && !line.equals("") && !var_included(line)) {
2097        line = reader.readLine(); // value (discard it)
2098        line = reader.readLine(); // modbit
2099        if (line == null || !(line.equals("0") || line.equals("1") || line.equals("2"))) {
2100          throw new Daikon.UserError("Bad modbit '" + line + "'", data_trace_state);
2101        }
2102        line = reader.readLine(); // next variable name
2103      }
2104      if (line == null) {
2105        throw new Daikon.UserError(
2106            "Unexpected end of file at "
2107                + data_trace_state.filename
2108                + " line "
2109                + reader.getLineNumber()
2110                + lineSep
2111                + "  Expected to find variable name"
2112                + " for program point "
2113                + ppt.name());
2114      }
2115
2116      if (!unescape_decl(line.trim()).equals(vi.str_name())) {
2117        throw new Daikon.UserError(
2118            "Mismatch between declaration and trace.  Expected variable "
2119                + vi.name()
2120                + ", got "
2121                + line
2122                + " for program point "
2123                + ppt.name(),
2124            data_trace_state);
2125      }
2126      line = reader.readLine();
2127      if (line == null) {
2128        throw new Daikon.UserError(
2129            "Unexpected end of file at "
2130                + data_trace_state.filename
2131                + " line "
2132                + reader.getLineNumber()
2133                + lineSep
2134                + "  Expected value for variable "
2135                + vi.name()
2136                + ", got "
2137                + "null" // line
2138                + " for program point "
2139                + ppt.name());
2140      }
2141      String value_rep = line;
2142      line = reader.readLine();
2143      if (line == null) {
2144        throw new Daikon.UserError(
2145            "Unexpected end of file at "
2146                + data_trace_state.filename
2147                + " line "
2148                + reader.getLineNumber()
2149                + lineSep
2150                + "  Expected modbit for variable "
2151                + vi.name()
2152                + ", got "
2153                + "null" // line
2154                + " for program point "
2155                + ppt.name());
2156      }
2157      if (!(line.equals("0") || line.equals("1") || line.equals("2"))) {
2158        throw new Daikon.UserError("Bad modbit `" + line + "'", data_trace_state);
2159      }
2160      int mod = ValueTuple.parseModified(line);
2161
2162      // System.out.println("Mod is " + mod + " at " + data_trace_state.filename + " line " +
2163      // reader.getLineNumber());
2164      // System.out.pringln("  for variable " + vi.name()
2165      //                   + " for program point " + ppt.name());
2166
2167      // MISSING_FLOW is only found during flow algorithm
2168      assert mod != ValueTuple.MISSING_FLOW : "Data trace value can't be missing due to flow";
2169
2170      if (mod != ValueTuple.MISSING_NONSENSICAL) {
2171        // Set the modbit now, depending on whether the value of the variable
2172        // has been changed or not.
2173        if (value_rep.equals(oldvalue_reps[val_index])) {
2174          if (!dkconfig_add_changed) {
2175            mod = ValueTuple.UNMODIFIED;
2176          }
2177        } else {
2178          mod = ValueTuple.MODIFIED;
2179        }
2180      }
2181
2182      mods[val_index] = mod;
2183      oldvalue_reps[val_index] = value_rep;
2184
2185      if (Global.debugPrintDtrace) {
2186        assert Global.dtraceWriter != null
2187            : "@AssumeAssertion(nullness): dependent: set if debugPrintDtrace is true";
2188        Global.dtraceWriter.println(vi.name());
2189        Global.dtraceWriter.println(value_rep);
2190        Global.dtraceWriter.println(mod);
2191      }
2192      Debug dbg = Debug.newDebug(FileIO.class, ppt, Debug.vis(vi));
2193      if (dbg != null) {
2194        dbg.log("Var " + vi.name() + " has value " + value_rep + " mod " + mod);
2195      }
2196
2197      // Both uninit and nonsensical mean missing modbit 2, because
2198      // it doesn't make sense to look at x.y when x is uninitialized.
2199      if (ValueTuple.modIsMissingNonsensical(mod)) {
2200        if (!(value_rep.equals("nonsensical")
2201            // Kvasir still uses "uninit" (it distinguishes between
2202            // uninit and nonsensical), though the Daikon manual does not
2203            // officially permit "uninit" as a value and has not since at
2204            // least 2002.  This is fixed in the Kvasir repository as of
2205            // 5/2009, so the following two lines should be removed at
2206            // some point not too long after that.  Then Daikon should
2207            // print a warning (or even terminate execution) about uses
2208            // of "uninit".
2209            || value_rep.equals("uninit")
2210            || value_rep.equals("missing"))) {
2211          throw new Daikon.UserError(
2212              "Modbit indicates nonsensical value for variable "
2213                  + vi.name()
2214                  + " with value \""
2215                  + value_rep
2216                  + "\";"
2217                  + lineSep
2218                  + "  text of value should be \"nonsensical\"",
2219              data_trace_state);
2220        } else {
2221          if (debug_missing && !vi.canBeMissing) {
2222            System.out.printf(
2223                "Var %s ppt %s at line %d missing%n", vi, ppt.name(), FileIO.get_linenum());
2224            System.out.printf("val_index = %d, mods[val_index] = %d%n", val_index, mods[val_index]);
2225          }
2226          vi.canBeMissing = true;
2227        }
2228        vals[val_index] = null;
2229      } else {
2230        // mod is not MISSING_NONSENSICAL
2231
2232        // System.out.println("Mod is " + mod + " (missing=" +
2233        // ValueTuple.MISSING + "), rep=" + value_rep +
2234        // "(modIsMissing=" + ValueTuple.modIsMissing(mod) + ")");
2235
2236        try {
2237          vals[val_index] = vi.rep_type.parse_value(value_rep, reader, filename);
2238          if (vals[val_index] == null) {
2239            if (debug_missing && !vi.canBeMissing) {
2240              System.out.printf(
2241                  "Var %s ppt %s at line %d is null, and modbit is not missing%n",
2242                  vi, ppt.name(), FileIO.get_linenum());
2243            }
2244            // The value in the trace was null even though the modbit was not
2245            // MISSING_NONSENSICAL.  Set the modbit to MISSING_NONSENSICAL.
2246            // This can happen for a value like [1 nonsensical 2], because
2247            // if any array value is nonsensical, the whole array is
2248            // treated as nonsensical.
2249            mods[val_index] = ValueTuple.MISSING_NONSENSICAL;
2250            vi.canBeMissing = true;
2251          }
2252        } catch (Daikon.UserError e) {
2253          throw e;
2254        } catch (Throwable e) {
2255          // e.printStackTrace(System.err); // for debugging
2256          throw new Daikon.UserError(
2257              e,
2258              "Error while parsing value "
2259                  + value_rep
2260                  + " for variable "
2261                  + vi.name()
2262                  + " of type "
2263                  + vi.rep_type
2264                  + ": "
2265                  + e.getLocalizedMessage(),
2266              reader,
2267              filename);
2268        }
2269      }
2270      val_index++;
2271    }
2272
2273    // Does oldvalue_reps now have no null elements???
2274    oldvalue_reps = castNonNullDeep(oldvalue_reps); // https://tinyurl.com/cfissue/986
2275    ppt_to_value_reps.put(ppt, oldvalue_reps);
2276
2277    if (Global.debugPrintDtrace) {
2278      assert Global.dtraceWriter != null
2279          : "@AssumeAssertion(nullness): dependent: set if debugPrintDtrace is true";
2280      Global.dtraceWriter.println();
2281    }
2282
2283    // Expecting the end of a block of values.
2284    String line = reader.readLine();
2285    // First, we might get some variables that ought to be omitted.
2286    while ((line != null) && !line.equals("") && !var_included(line)) {
2287      line = reader.readLine(); // value
2288      line = reader.readLine(); // modbit
2289      line = reader.readLine(); // next variable name
2290    }
2291    assert (line == null) || line.equals("")
2292        : "Expected blank line in "
2293            + data_trace_state.filename
2294            + " at line "
2295            + reader.getLineNumber()
2296            + ": "
2297            + line;
2298  }
2299
2300  /**
2301   * If this is a function entry ppt, stores the values of all of the variables away for use at the
2302   * exit. If this is an exit, finds the values at enter and adds them as the values of the orig
2303   * variables. Normally returns false. Returns true if this is an exit without a matching enter.
2304   * See dkconfig_ignore_missing_enter for more info. If true is returned, this ppt should be
2305   * ignored by the caller.
2306   */
2307  @RequiresNonNull("FileIO.data_trace_state")
2308  public static boolean compute_orig_variables(
2309      PptTopLevel ppt,
2310      // HashMap cumulative_modbits,
2311      @Nullable Object[] vals,
2312      int[] mods,
2313      @Nullable Integer nonce) {
2314    assert data_trace_state != null;
2315
2316    VarInfo[] vis = ppt.var_infos;
2317    @Interned String fn_name = ppt.ppt_name.getNameWithoutPoint();
2318    String ppt_name = ppt.name();
2319    if (ppt_name.endsWith(enter_tag)) {
2320      Invocation invok = new Invocation(ppt, vals, mods);
2321      if (nonce == null) {
2322        call_stack.push(invok);
2323      } else {
2324        call_hashmap.put(nonce, invok);
2325      }
2326      return false;
2327    }
2328
2329    if (ppt.ppt_name.isExitPoint() || ppt.ppt_name.isThrowsPoint()) {
2330      Invocation invoc;
2331      // Set invoc
2332      {
2333        if (nonce == null) {
2334          if (call_stack.isEmpty()) {
2335            // Not Daikon.UserError:  caller knows context such as
2336            // file name and line number.
2337            throw new Error("Function exit without corresponding entry: " + ppt.name());
2338          }
2339          invoc = call_stack.pop();
2340          while (invoc.ppt.ppt_name.getNameWithoutPoint() != fn_name) {
2341            // Should also mark as a function that made an exceptional exit
2342            // at run time.
2343            System.err.println(
2344                "Exceptional exit from function "
2345                    + fn_name
2346                    + ", expected to first exit from "
2347                    + invoc.ppt.ppt_name.getNameWithoutPoint()
2348                    + ((data_trace_state.filename == null)
2349                        ? ""
2350                        : "; at "
2351                            + data_trace_state.filename
2352                            + " line "
2353                            + data_trace_state.reader.getLineNumber()));
2354            invoc = call_stack.pop();
2355          }
2356        } else {
2357          // nonce != null
2358          if (!call_hashmap.containsKey(nonce)) {
2359            if (dkconfig_ignore_missing_enter) {
2360              // System.out.printf("Didn't find call with nonce %d to match %s" +
2361              //                   " ending at %s line %d%n", nonce, ppt.name(),
2362              //                   data_trace_state.filename,
2363              //                   data_trace_state.reader.getLineNumber());
2364              return true;
2365            } else {
2366              // Not Daikon.UserError:  caller knows context such as
2367              // file name and line number.
2368              throw new Error(
2369                  String.format(
2370                      "Didn't find call with nonce %s to match %s ending at %s line %d",
2371                      nonce,
2372                      ppt.name(),
2373                      data_trace_state.filename,
2374                      data_trace_state.reader.getLineNumber()));
2375            }
2376          }
2377          invoc = call_hashmap.get(nonce);
2378          call_hashmap.remove(nonce);
2379        }
2380      }
2381
2382      // Loop through each orig variable and get its value/mod bits from
2383      // the ENTER point.  vi_index is the index into var_infos at the
2384      // ENTER point.  val_index is the index into vals[] and mods[] at
2385      // ENTER point.  Note that vis[] includes static constants but
2386      // vals[] and mods[] do not.  Also that we don't create orig versions
2387      // of static constants
2388      int vi_index = 0;
2389      for (int val_index = 0; val_index < ppt.num_orig_vars; val_index++) {
2390        VarInfo vi = vis[ppt.num_tracevars + ppt.num_static_constant_vars + val_index];
2391        assert !vi.is_static_constant : "orig constant " + vi;
2392
2393        // Skip over constants in the entry point
2394        while (invoc.ppt.var_infos[vi_index].is_static_constant) {
2395          vi_index++;
2396        }
2397
2398        // Copy the vals and mod bits from entry to exit
2399        vals[ppt.num_tracevars + val_index] = invoc.vals[val_index];
2400        int mod = invoc.mods[val_index];
2401        mods[ppt.num_tracevars + val_index] = mod;
2402
2403        // If the value was missing, mark this variable as can be missing.
2404        // Carefully check that we have orig version of the variable from
2405        // the ENTER point.
2406        if (ValueTuple.modIsMissingNonsensical(mod)) {
2407          if (debug_missing && !vi.canBeMissing) {
2408            System.out.printf("add_orig: var %s missing[%d/%d]%n", vi, val_index, vi_index);
2409          }
2410          vi.canBeMissing = true;
2411          assert invoc.vals[val_index] == null;
2412          assert vi.name() == invoc.ppt.var_infos[vi_index].prestate_name()
2413              : vi.name() + " != " + invoc.ppt.var_infos[vi_index];
2414          assert invoc.ppt.var_infos[vi_index].canBeMissing : invoc.ppt.var_infos[vi_index];
2415        }
2416        vi_index++;
2417      }
2418    }
2419    return false;
2420  }
2421
2422  /** Computes values of derived variables. */
2423  public static void compute_derived_variables(
2424      PptTopLevel ppt, @Nullable Object[] vals, int[] mods) {
2425    // This ValueTuple is temporary:  we're temporarily suppressing interning,
2426    // which we will do after we have all the values available.
2427    ValueTuple partial_vt = ValueTuple.makeUninterned(vals, mods);
2428    int filled_slots = ppt.num_orig_vars + ppt.num_tracevars + ppt.num_static_constant_vars;
2429    for (int i = 0; i < filled_slots; i++) {
2430      assert !ppt.var_infos[i].isDerived();
2431    }
2432    int num_const = ppt.num_static_constant_vars;
2433    for (int i = filled_slots; i < ppt.var_infos.length; i++) {
2434      assert ppt.var_infos[i].derived != null : "variable not derived: " + ppt.var_infos[i].repr();
2435      assert ppt.var_infos[i].derived != null : "@AssumeAssertion(nullness): application invariant";
2436      // Add this derived variable's value
2437      ValueAndModified vm = ppt.var_infos[i].derived.computeValueAndModified(partial_vt);
2438      vals[i - num_const] = vm.value;
2439      mods[i - num_const] = vm.modified;
2440    }
2441  }
2442
2443  // ///////////////////////////////////////////////////////////////////////////
2444  // Serialized PptMap files
2445  //
2446
2447  /**
2448   * Use a special record type. Saving as one object allows for reference-sharing, easier saves and
2449   * loads, and potential for later overriding of SerialFormat.readObject if the save format changes
2450   * (ick).
2451   */
2452  static final class SerialFormat implements Serializable {
2453    // We are Serializable, so we specify a version to allow changes to
2454    // method signatures without breaking serialization.  If you add or
2455    // remove fields, you should change this number to the current date.
2456    static final long serialVersionUID = 20060905L;
2457
2458    @RequiresNonNull("FileIO.new_decl_format")
2459    public SerialFormat(PptMap map, Configuration config) {
2460      this.map = map;
2461      this.config = config;
2462      this.new_decl_format = FileIO.new_decl_format;
2463    }
2464
2465    public PptMap map;
2466    public Configuration config;
2467    public boolean new_decl_format = false;
2468  }
2469
2470  /**
2471   * Write a serialized PptMap to a file.
2472   *
2473   * @param map a PptMap
2474   * @param file the file to which to write
2475   * @throws IOException if there is trouble writing the file
2476   */
2477  public static void write_serialized_pptmap(PptMap map, File file) throws IOException {
2478    SerialFormat record = new SerialFormat(map, Configuration.getInstance());
2479    FilesPlume.writeObject(record, file);
2480  }
2481
2482  /**
2483   * Read either a serialized PptMap or a InvMap and return a PptMap. If an InvMap is specified, it
2484   * is converted to a PptMap.
2485   *
2486   * @param file the input file
2487   * @param use_saved_config flag
2488   * @return a serialized PptMap
2489   * @throws IOException if there is trouble reading the file
2490   */
2491  @EnsuresNonNull("FileIO.new_decl_format")
2492  public static PptMap read_serialized_pptmap(File file, boolean use_saved_config)
2493      throws IOException {
2494
2495    try {
2496      Object obj = FilesPlume.readObject(file);
2497      if (obj instanceof FileIO.SerialFormat) {
2498        SerialFormat record = (SerialFormat) obj;
2499        if (use_saved_config) {
2500          Configuration.getInstance().overlap(record.config);
2501        }
2502        FileIO.new_decl_format = record.new_decl_format;
2503        // System.err.printf("Setting FileIO.new_decl_format to %b%n",
2504        //                   FileIO.new_decl_format);
2505        return record.map;
2506      } else if (obj instanceof InvMap) {
2507        // System.err.printf("Restoring an InvMap%n");
2508        InvMap invs = (InvMap) obj;
2509        PptMap ppts = new PptMap();
2510        for (PptTopLevel ppt : invs.pptIterable()) {
2511          PptTopLevel nppt = new PptTopLevel(ppt.name, ppt.var_infos);
2512          nppt.set_sample_number(ppt.num_samples());
2513          ppts.add(nppt);
2514          List<Invariant> inv_list = invs.get(ppt);
2515          for (Invariant inv : inv_list) {
2516            PptSlice slice = nppt.get_or_instantiate_slice(inv.ppt.var_infos);
2517            inv.ppt = slice;
2518            slice.addInvariant(inv);
2519          }
2520        }
2521        assert FileIO.new_decl_format != null
2522            : "@AssumeAssertion(nullness): InvMap.readObject() sets FileIO.new_decl_format";
2523        return ppts;
2524      } else {
2525        throw new IOException("Unexpected serialized file type: " + obj.getClass());
2526      }
2527    } catch (ClassNotFoundException e) {
2528      throw (IOException) new IOException("Error while loading inv file").initCause(e);
2529    } catch (InvalidClassException e) {
2530      throw new IOException(
2531          "It is likely that the .inv file format has changed, because a Daikon data structure has"
2532              + " been modified, so your old .inv file is no longer readable by Daikon.  Please"
2533              + " regenerate your .inv file."
2534          // + lineSep + e.toString()
2535          );
2536    }
2537    // } catch (StreamCorruptedException e) { // already extends IOException
2538    // } catch (OptionalDataException e) {    // already extends IOException
2539  }
2540
2541  /**
2542   * Returns whether or not the specified ppt name should be included in processing. Ppts can be
2543   * excluded because they match the omit_regexp, don't match ppt_regexp, or are greater than
2544   * ppt_max_name.
2545   */
2546  public static boolean ppt_included(String ppt_name) {
2547
2548    // System.out.println ("ppt_name = '" + ppt_name + "' max name = '"
2549    //                     + Daikon.ppt_max_name + "'");
2550    if (((Daikon.ppt_omit_regexp != null) && Daikon.ppt_omit_regexp.matcher(ppt_name).find())
2551        || ((Daikon.ppt_regexp != null) && !Daikon.ppt_regexp.matcher(ppt_name).find())
2552        || ((Daikon.ppt_max_name != null)
2553            && ((Daikon.ppt_max_name.compareTo(ppt_name) < 0)
2554                && (ppt_name.indexOf(global_suffix) == -1)))) {
2555      return false;
2556    } else {
2557      return true;
2558    }
2559  }
2560
2561  /**
2562   * Returns true if the given variable is included, according to Daikon's {@code
2563   * --var-select-pattern} and {@code --var-omit-pattern} flags.
2564   */
2565  public static boolean var_included(String var_name) {
2566    assert !var_name.equals("");
2567    if (((Daikon.var_omit_regexp != null) && Daikon.var_omit_regexp.matcher(var_name).find())
2568        || ((Daikon.var_regexp != null) && !Daikon.var_regexp.matcher(var_name).find())) {
2569      return false;
2570    } else {
2571      return true;
2572    }
2573  }
2574
2575  /**
2576   * Checks the specified array of variables to see if it matches exactly the variables in the
2577   * existing ppt. Throws an error if there are any differences. Used to ensure that a new ppt with
2578   * the same name as an existing ppt is exactly the same.
2579   */
2580  static void check_decl_match(ParseState state, PptTopLevel existing_ppt, VarInfo[] vi_array) {
2581
2582    VarInfo[] existing_vars = existing_ppt.var_infos;
2583    if (existing_ppt.num_declvars != vi_array.length) {
2584      throw new Daikon.UserError(
2585          "Duplicate declaration of program point \""
2586              + existing_ppt.name()
2587              + "\" with a different number of VarInfo objects: "
2588              + "old VarInfo number="
2589              + existing_ppt.num_declvars
2590              + ", new VarInfo number="
2591              + vi_array.length,
2592          state);
2593    }
2594
2595    for (int i = 0; i < vi_array.length; i++) {
2596      String oldName = existing_vars[i].str_name();
2597      String newName = vi_array[i].str_name();
2598      if (!oldName.equals(newName)) {
2599        throw new Daikon.UserError(
2600            "Duplicate declaration of program point \""
2601                + existing_ppt.name()
2602                + "\" with two different VarInfo: old VarInfo="
2603                + oldName
2604                + ", new VarInfo="
2605                + newName,
2606            state);
2607      }
2608    }
2609  }
2610
2611  /**
2612   * Converts the declaration record version of a name into its correct version. In the declaration
2613   * record, blanks are encoded as \_ and backslashes as \\.
2614   */
2615  private static String unescape_decl(String orig) {
2616    StringBuilder sb = new StringBuilder(orig.length());
2617    // The previous escape character was seen just before this position.
2618    int post_esc = 0;
2619    int this_esc = orig.indexOf('\\');
2620    while (this_esc != -1) {
2621      if (this_esc == orig.length() - 1) {
2622        sb.append(orig.substring(post_esc, this_esc + 1));
2623        post_esc = this_esc + 1;
2624        break;
2625      }
2626      switch (orig.charAt(this_esc + 1)) {
2627        case 'n':
2628          sb.append(orig.substring(post_esc, this_esc));
2629          sb.append('\n'); // not lineSep
2630          post_esc = this_esc + 2;
2631          break;
2632        case 'r':
2633          sb.append(orig.substring(post_esc, this_esc));
2634          sb.append('\r');
2635          post_esc = this_esc + 2;
2636          break;
2637        case '_':
2638          sb.append(orig.substring(post_esc, this_esc));
2639          sb.append(' ');
2640          post_esc = this_esc + 2;
2641          break;
2642        case '\\':
2643          // This is not in the default case because the search would find
2644          // the quoted backslash.  Here we incluce the first backslash in
2645          // the output, but not the first.
2646          sb.append(orig.substring(post_esc, this_esc + 1));
2647          post_esc = this_esc + 2;
2648          break;
2649
2650        default:
2651          // In the default case, retain the character following the
2652          // backslash, but discard the backslash itself.  "\*" is just
2653          // a one-character string.
2654          sb.append(orig.substring(post_esc, this_esc));
2655          post_esc = this_esc + 1;
2656          break;
2657      }
2658      this_esc = orig.indexOf('\\', post_esc);
2659    }
2660    if (post_esc == 0) {
2661      return orig;
2662    }
2663    sb.append(orig.substring(post_esc));
2664    return sb.toString();
2665  }
2666
2667  // The reverse of unescape_decl.  Test them together.
2668  /**
2669   * Converts a name into its declaration record version. In the declaration record, blanks are
2670   * encoded as \_ and backslashes as \\.
2671   *
2672   * @param orig the name of a declaration
2673   * @return the representation of the name in a declaration file
2674   */
2675  @SuppressWarnings("UnusedMethod")
2676  private static String escape_decl(String orig) {
2677    return orig.replace("\\", "\\\\")
2678        .replace(" ", "\\_")
2679        .replace("\n", "\\n") // not lineSep
2680        .replace("\r", "\\r");
2681  }
2682
2683  /**
2684   * Class that holds information from the declaration record (in the file). Once collected, this
2685   * information is used to create a VarInfo. This class is necessary because a VarInfo cannot be
2686   * created until much of this information is present: the constructor requires all the information
2687   * at the time of construction, and some of the fields are final.
2688   *
2689   * <p>In general, each field has a one-to-one relation with the corresponding entry in the
2690   * variable definition block in the trace file. More detailed information about each of the fields
2691   * can be found in the 'Variable declarations' section of the 'File Formats' appendix of the
2692   * Daikon developers manual. Specifics can also be found in the 'parse_[field]' methods of the
2693   * class (eg, parse_var_kind, parse_enclosing_var_name, etc).
2694   */
2695  @SuppressWarnings(
2696      "nullness") // undocumented class needs documentation before annotating with nullness
2697  public static class VarDefinition implements java.io.Serializable, Cloneable {
2698    static final long serialVersionUID = 20060524L;
2699
2700    /** Current information about input file and previously parsed values. */
2701    transient ParseState state;
2702
2703    /** Name of the variable (required). */
2704    public String name;
2705
2706    /** Type of the variable (required). */
2707    public VarKind kind = null;
2708
2709    /** Name of variable that contains this variable (optional) */
2710    // seems non-null for arrays/sequences
2711    public @Nullable String enclosing_var_name;
2712
2713    /** the simple (not fully specified) name of this variable (optional) */
2714    public @Nullable String relative_name = null;
2715
2716    /** Type of reference for structure/class variables. */
2717    public RefType ref_type = RefType.POINTER;
2718
2719    /** Number of array dimensions (0 or 1). */
2720    public int arr_dims = 0;
2721
2722    /**
2723     * Non-null iff (vardef.kind == VarKind.FUNCTION). The arguments that were used to create this
2724     * function application.
2725     */
2726    @SuppressWarnings("serial")
2727    public @Nullable List<String> function_args = null;
2728
2729    /** The type of the variable as stored in the dtrace file (required) */
2730    public ProglangType rep_type = null;
2731
2732    /** Declared type of the variable as an arbitrary string (required) */
2733    public ProglangType declared_type = null;
2734
2735    /** Variable flags (optional) */
2736    public EnumSet<VarFlags> flags = EnumSet.noneOf(VarFlags.class);
2737
2738    /** Language specific variable flags (optional) */
2739    public EnumSet<LangFlags> lang_flags = EnumSet.noneOf(LangFlags.class);
2740
2741    /** Comparability of this variable (required. */
2742    @SuppressWarnings("serial")
2743    public VarComparability comparability = null;
2744
2745    /** Parent program points in ppt hierarchy (optional) */
2746    @SuppressWarnings("serial")
2747    public List<VarParent> parents;
2748
2749    /** Non-null if this 'variable' always has the same value (optional) */
2750    @SuppressWarnings("serial")
2751    public @Nullable @Interned Object static_constant_value = null;
2752
2753    /**
2754     * Non-null if it is statically known that the value of the variable will be always greater than
2755     * or equal to this value.
2756     */
2757    public @Nullable String min_value = null;
2758
2759    /**
2760     * Non-null if it is statically known that the value of the variable will be always less than or
2761     * equal to this value.
2762     */
2763    public @Nullable String max_value = null;
2764
2765    /** Non-null if it is statically known that the array will have at least this many elements. */
2766    public @Nullable Integer min_length = null;
2767
2768    /** Non-null if it is statically known that the array will have up to this many elements. */
2769    public @Nullable Integer max_length = null;
2770
2771    /** Non-null if the set of valid values for the variable is statically known. */
2772    public @Nullable String valid_values = null;
2773
2774    /** Check representation invariants. */
2775    public void checkRep() {
2776
2777      // Basic checking for sensible input
2778      assert name != null;
2779      if (kind == null) {
2780        throw new AssertionError("missing var-kind information for variable " + name);
2781      }
2782      assert (arr_dims == 0) || (arr_dims == 1)
2783          : String.format(
2784              "array dimensions==%s, should be 0 or 1, for variable %s", arr_dims, name);
2785      assert !rep_type.isArray() || arr_dims == 1
2786          : String.format("array dimensions is 0, should be 1, for variable %s", name);
2787      if (rep_type == null) {
2788        throw new AssertionError("missing rep-type information for variable " + name);
2789      }
2790      if (declared_type == null) {
2791        throw new AssertionError("missing dec-type information for variable " + name);
2792      }
2793      if (comparability == null) {
2794        throw new AssertionError("missing comparability information for variable " + name);
2795      }
2796      assert (kind == VarKind.FUNCTION) || (function_args == null)
2797          : String.format(
2798              "incompatible kind=%s and function_args=%s for VarDefinition %s",
2799              kind, function_args, name);
2800      if ((kind == VarKind.FIELD || kind == VarKind.ARRAY) && enclosing_var_name == null) {
2801        throw new AssertionError("enclosing-var not specified for variable " + name);
2802      }
2803    }
2804
2805    /** Initialize from the 'variable <em>name</em>' record. Scanner should be pointing at name. */
2806    public VarDefinition(ParseState state, Scanner scanner) {
2807      this.state = state;
2808      this.parents = new ArrayList<VarParent>();
2809      name = need(scanner, "name");
2810      need_eol(scanner);
2811      if (state.varcomp_format == VarComparability.IMPLICIT) {
2812        comparability = VarComparabilityImplicit.unknown;
2813      } else {
2814        comparability = VarComparabilityNone.it;
2815      }
2816    }
2817
2818    public VarDefinition(String name, VarKind kind, ProglangType type) {
2819      this.state = null;
2820      this.parents = new ArrayList<VarParent>();
2821      this.name = name;
2822      this.kind = kind;
2823      this.rep_type = type;
2824      this.declared_type = type;
2825      comparability = VarComparabilityNone.it;
2826    }
2827
2828    @SideEffectFree
2829    @Override
2830    public VarDefinition clone(@GuardSatisfied VarDefinition this) {
2831      try {
2832        return (VarDefinition) super.clone();
2833      } catch (CloneNotSupportedException e) {
2834        throw new Error("This can't happen: ", e);
2835      }
2836    }
2837
2838    public VarDefinition copy() {
2839      try {
2840        VarDefinition copy = this.clone();
2841        copy.flags = flags.clone();
2842        copy.lang_flags = lang_flags.clone();
2843        return copy;
2844      } catch (Throwable t) {
2845        throw new RuntimeException(t);
2846      }
2847    }
2848
2849    /** Restore interned strings. */
2850    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
2851      in.defaultReadObject();
2852      name = name.intern();
2853      if (enclosing_var_name != null) {
2854        enclosing_var_name = enclosing_var_name.intern();
2855      }
2856      if (relative_name != null) {
2857        relative_name = relative_name.intern();
2858      }
2859      for (VarParent parent : parents) {
2860        parent.parent_ppt = parent.parent_ppt.intern();
2861        if (parent.parent_variable != null) {
2862          parent.parent_variable = parent.parent_variable.intern();
2863        }
2864      }
2865    }
2866
2867    /** Clears the parent relations, if any existed. */
2868    public void clear_parent_relation() {
2869      parents.clear();
2870    }
2871
2872    /** Parse a var-kind record. Scanner should be pointing at the variable kind. */
2873    public void parse_var_kind(Scanner scanner) {
2874      VarKind kind_local = parse_enum_val(scanner, VarKind.class, "variable kind");
2875      kind = kind_local;
2876
2877      if ((kind == VarKind.FIELD) || (kind == VarKind.FUNCTION)) {
2878        relative_name = need(scanner, "relative name");
2879      }
2880      need_eol(scanner);
2881    }
2882
2883    /** Parses the enclosing-var record. */
2884    public void parse_enclosing_var_name(Scanner scanner) {
2885      enclosing_var_name = need(scanner, "enclosing variable name");
2886      need_eol(scanner);
2887    }
2888
2889    /** Parses the reference-type record. */
2890    public void parse_reference_type(Scanner scanner) {
2891      RefType ref_type_local = parse_enum_val(scanner, RefType.class, "reference type");
2892      ref_type = ref_type_local;
2893      need_eol(scanner);
2894    }
2895
2896    /** Parses the array record. */
2897    public void parse_array(Scanner scanner) {
2898      @Interned String arr_str = need(scanner, "array dimensions");
2899      if (arr_str == "0") { // interned
2900        arr_dims = 0;
2901      } else if (arr_str == "1") { // interned
2902        arr_dims = 1;
2903      } else {
2904        decl_error(state, "%s found where 0 or 1 expected", arr_str);
2905      }
2906    }
2907
2908    /** Parses the function-args record. */
2909    public void parse_function_args(Scanner scanner) {
2910
2911      function_args = new ArrayList<String>();
2912      while (scanner.hasNext()) {
2913        function_args.add(unescape_decl(scanner.next()).intern());
2914      }
2915    }
2916
2917    public void parse_rep_type(Scanner scanner) {
2918      @Interned String rep_type_str = need(scanner, "rep type");
2919      need_eol(scanner);
2920      rep_type = ProglangType.rep_parse(rep_type_str);
2921    }
2922
2923    public void parse_dec_type(Scanner scanner) {
2924      @Interned String declared_type_str = need(scanner, "declaration type");
2925      need_eol(scanner);
2926      declared_type = ProglangType.parse(declared_type_str);
2927    }
2928
2929    /** Parse the flags record. Multiple flags can be specified. */
2930    public void parse_flags(Scanner scanner) {
2931
2932      flags.add(parse_enum_val(scanner, VarFlags.class, "Flag"));
2933      while (scanner.hasNext()) flags.add(parse_enum_val(scanner, VarFlags.class, "Flag"));
2934      // System.out.printf("flags for %s are %s%n", name, flags);
2935    }
2936
2937    /** Parse the langauge specific flags record. Multiple flags can be specified. */
2938    public void parse_lang_flags(Scanner scanner) {
2939
2940      lang_flags.add(parse_enum_val(scanner, LangFlags.class, "Language Specific Flag"));
2941      while (scanner.hasNext()) {
2942        lang_flags.add(parse_enum_val(scanner, LangFlags.class, "Language Specific Flag"));
2943      }
2944    }
2945
2946    /** Parses a comparability record. */
2947    public void parse_comparability(Scanner scanner) {
2948      @Interned String comparability_str = need(scanner, "comparability");
2949      need_eol(scanner);
2950      comparability =
2951          VarComparability.parse(state.varcomp_format, comparability_str, declared_type);
2952    }
2953
2954    /** Parse a parent ppt record. */
2955    public void parse_parent(Scanner scanner, List<ParentRelation> ppt_parents)
2956        throws Daikon.ParseError {
2957
2958      String parent_ppt = need(scanner, "parent ppt");
2959      String parent_relation_id_string = need(scanner, "parent id");
2960      int parent_relation_id;
2961      try {
2962        parent_relation_id = Integer.parseInt(parent_relation_id_string);
2963      } catch (NumberFormatException nfe) {
2964        throw new Daikon.ParseError("Expected a number, found: " + parent_relation_id_string);
2965      }
2966      String parent_variable = null;
2967
2968      boolean found = false;
2969      for (ParentRelation pr : ppt_parents) {
2970        if ((pr.parent_ppt_name == parent_ppt) && (pr.id == parent_relation_id)) {
2971          found = true;
2972          break;
2973        }
2974      }
2975      if (!found) {
2976        decl_error(
2977            state,
2978            "specified parent ppt '%s[%d]' for variable '%s' is not a parent to this ppt",
2979            parent_ppt,
2980            parent_relation_id,
2981            name);
2982      }
2983      if (scanner.hasNext()) {
2984        parent_variable = need(scanner, "parent variable");
2985      }
2986
2987      parents.add(new VarParent(parent_ppt, parent_relation_id, parent_variable));
2988
2989      need_eol(scanner);
2990    }
2991
2992    /** Parse a constant record. */
2993    public void parse_constant(Scanner scanner) {
2994      @Interned String constant_str = need(scanner, "constant value");
2995      need_eol(scanner);
2996      try {
2997        static_constant_value = rep_type.parse_value(constant_str, null, "parse_constant");
2998      } catch (Error e) {
2999        decl_error(state, e);
3000      }
3001    }
3002
3003    /** Parse a minimum value record. */
3004    public void parse_min_value(Scanner scanner) {
3005      this.min_value = need(scanner, "minimum value");
3006      need_eol(scanner);
3007    }
3008
3009    /** Parse a maximum value record. */
3010    public void parse_max_value(Scanner scanner) {
3011      this.max_value = need(scanner, "maximum value");
3012      need_eol(scanner);
3013    }
3014
3015    /** Parse a minimum length record. */
3016    public void parse_min_length(Scanner scanner) {
3017      this.min_length = Integer.parseInt(need(scanner, "minimum length"));
3018      need_eol(scanner);
3019    }
3020
3021    /** Parse a maximum length record. */
3022    public void parse_max_length(Scanner scanner) {
3023      this.max_length = Integer.parseInt(need(scanner, "maximum length"));
3024      need_eol(scanner);
3025    }
3026
3027    /** Parse a valid values record. */
3028    public void parse_valid_values(Scanner scanner) {
3029      this.valid_values = scanner.nextLine();
3030    }
3031
3032    /**
3033     * Helper function, returns the next string token unescaped and interned. Throw Daikon.UserError
3034     * if there is no next token.
3035     */
3036    public @Interned String need(Scanner scanner, String description) {
3037      return FileIO.need(state, scanner, description);
3038    }
3039
3040    /** Throws Daikon.UserError if the scanner is not at end of line */
3041    public void need_eol(Scanner scanner) {
3042      FileIO.need_eol(state, scanner);
3043    }
3044
3045    /**
3046     * Looks up the next token as a member of enum_class. Throws Daikon.UserError if there is no
3047     * token or if it is not valid member of the class. Enums are presumed to be in in upper case.
3048     */
3049    public <E extends Enum<E>> E parse_enum_val(
3050        Scanner scanner, Class<E> enum_class, String descr) {
3051      return FileIO.parse_enum_val(state, scanner, enum_class, descr);
3052    }
3053  }
3054
3055  /**
3056   * Helper function, returns the next string token unescaped and interned. Throws Daikon.UserError
3057   * if there is no next token.
3058   */
3059  public static @Interned String need(ParseState state, Scanner scanner, String description) {
3060    if (!scanner.hasNext()) {
3061      decl_error(state, "end-of-line found where %s expected", description);
3062    }
3063    return unescape_decl(scanner.next()).intern();
3064  }
3065
3066  /** Throws a Daikon.UserError if the scanner is not at end of line */
3067  public static void need_eol(ParseState state, Scanner scanner) {
3068    if (scanner.hasNext()) {
3069      decl_error(state, "'%s' found where end-of-line expected", scanner.next());
3070    }
3071  }
3072
3073  /**
3074   * Looks up the next token as a member of enum_class. Throws Daikon.UserError if there is no token
3075   * or if it is not valid member of the class. Enums are presumed to be in in upper case.
3076   */
3077  public static <E extends Enum<E>> E parse_enum_val(
3078      ParseState state, Scanner scanner, Class<E> enum_class, String descr) {
3079
3080    @Interned String str = need(state, scanner, descr);
3081    try {
3082      E e = Enum.valueOf(enum_class, str.toUpperCase(Locale.ENGLISH));
3083      return e;
3084    } catch (Exception exception) {
3085      @SuppressWarnings(
3086          "nullness") // getEnumConstants returns non-null because enum_class is an enum class
3087      E @NonNull [] all = enum_class.getEnumConstants();
3088      StringJoiner msg = new StringJoiner(", ");
3089      for (E e : all) {
3090        msg.add(String.format("'%s'", e.name().toLowerCase(Locale.ENGLISH)));
3091      }
3092      decl_error(state, "'%s' found where %s expected", str, msg);
3093      throw new Error("execution cannot get to here, previous line threw an error");
3094    }
3095  }
3096
3097  /**
3098   * Call this to indicate a malformed declaration.
3099   *
3100   * @param state the current parse state
3101   * @param format a format string, for the error message
3102   * @param args arguments for the format string
3103   */
3104  private static void decl_error(ParseState state, String format, @Nullable Object... args) {
3105    @SuppressWarnings({
3106      "formatter:format.string" // https://tinyurl.com/cfissue/2584
3107    })
3108    String msg = String.format(format, args) + state.line_file_message();
3109    throw new Daikon.UserError(msg);
3110  }
3111
3112  /** Call this to indicate a malformed declaration. */
3113  private static void decl_error(ParseState state, Throwable cause) {
3114    String msg = cause.getMessage() + state.line_file_message();
3115    if (msg.startsWith("null at")) {
3116      msg = msg.substring(5);
3117    }
3118    throw new Daikon.UserError(cause, msg);
3119  }
3120
3121  /** Returns whether the line is the start of a ppt declaration. */
3122  @RequiresNonNull("FileIO.new_decl_format")
3123  @Pure
3124  private static boolean is_declaration_header(String line) {
3125    if (new_decl_format) {
3126      return line.startsWith("ppt ");
3127    } else {
3128      return line.equals(declaration_header);
3129    }
3130  }
3131
3132  /**
3133   * Handle any possible modifications to the ppt name. For now, just support the Applications
3134   * Communities specific modification to remove duplicate stack entries. But a more generic
3135   * technique could be implemented in the future.
3136   *
3137   * @param ppt_name a program point
3138   * @return modified ppt name
3139   */
3140  public static String user_mod_ppt_name(String ppt_name) {
3141
3142    if (!dkconfig_rm_stack_dups) {
3143      return ppt_name;
3144    }
3145
3146    // System.out.printf("removing stack dups (%b)in fileio%n",
3147    //                    dkconfig_rm_stack_dups);
3148
3149    String[] stack = ppt_name.split("[|]");
3150    List<String> nd_stack = new ArrayList<>();
3151    for (String si : stack) {
3152      if (nd_stack.contains(si)) {
3153        continue;
3154      }
3155      nd_stack.add(si);
3156    }
3157    return String.join("|", nd_stack).intern();
3158  }
3159}