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}