001// See Annotate for documentation of behavior. 002 003package daikon.tools.jtb; 004 005import daikon.*; 006import daikon.chicory.DaikonVariableInfo; 007import daikon.inv.Invariant; 008import daikon.inv.OutputFormat; 009import daikon.inv.unary.sequence.EltNonZero; 010import daikon.inv.unary.stringsequence.EltOneOfString; 011import daikon.inv.unary.stringsequence.OneOfStringSequence; 012import java.io.IOException; 013import java.io.PrintWriter; 014import java.io.StringWriter; 015import java.io.UncheckedIOException; 016import java.util.ArrayDeque; 017import java.util.ArrayList; 018import java.util.Deque; 019import java.util.Enumeration; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.logging.Level; 024import jtb.syntaxtree.*; 025import jtb.visitor.*; 026import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; 027import org.checkerframework.checker.nullness.qual.NonNull; 028import org.checkerframework.checker.nullness.qual.Nullable; 029import org.checkerframework.dataflow.qual.Pure; 030import org.plumelib.util.EntryReader; 031import org.plumelib.util.StringsPlume; 032 033// For each class: (UnmodifiedClassDeclaration) 034// * insert object invariants 035// * insert owner assertions for fields 036// For each field: 037// * add spec_public 038// For each method: 039// * add preconditions 040// * add postconditions (including exsures) 041// * add "modifies" 042// * all of these may be prefixed by "also_" 043// For each field assignment: 044// * add owner annotations 045 046// The overall strategy is to read the Java file into a list of strings 047// (javaFileLines) and then to manipulate that list directly. It doesn't 048// insert the comments into the JTB tree. 049 050// TODO: Unlike InsertCommentFormatter, this does not keep track of column 051// shifts. (It does handle line shifts.) This needs to be fixed. 052 053public class AnnotateVisitor extends DepthFirstVisitor { 054 055 private static final boolean debug = false; 056 057 private static final String lineSep = System.lineSeparator(); 058 059 public static final String JML_START_COMMENT = "/*@" + lineSep; 060 public static final String JML_END_COMMENT = "@*/" + lineSep; 061 062 public List<String> javaFileLines; 063 064 public PptMap ppts; 065 066 /** if true, use "//" comments; if false, use "/*" comments. */ 067 public boolean slashslash; 068 069 /** If true, insert annotations not supported by ESC. */ 070 public boolean insert_inexpressible; 071 072 /** If false, use full JML specs; if true, use lightweight ESC specs. */ 073 public boolean lightweight; 074 075 /** 076 * Whether to use reflection when trying to figure out if a method overrides/implements another 077 * method. If this variable is set to false, then Annotate will not try to determine if a method 078 * overrides/implements another method, which means that it will not try to add "also" tags to its 079 * output. 080 */ 081 public boolean useReflection; 082 083 /** 084 * If the --max_invariants_pp option is given, this variable is set to the maximum number of 085 * invariants output by annotate per program point. 086 */ 087 public int maxInvariantsPP; 088 089 public List<NodeToken> addedComments = new ArrayList<>(); 090 091 private Deque<ClassFieldInfo> cfis = new ArrayDeque<ClassFieldInfo>(); 092 093 private PptNameMatcher pptMatcher; 094 095 public AnnotateVisitor( 096 String javafilename, 097 Node root, 098 PptMap ppts, 099 boolean slashslash, 100 boolean insert_inexpressible, 101 boolean lightweight, 102 boolean useReflection, 103 int maxInvariantsPP) { 104 105 this.pptMatcher = new PptNameMatcher(root); 106 107 // Read the Java file into a list of Strings. 108 this.javaFileLines = new ArrayList<String>(); 109 try (EntryReader er = new EntryReader(javafilename)) { 110 for (String line : er) { 111 this.javaFileLines.add(line); 112 } 113 } catch (IOException e) { 114 throw new UncheckedIOException("problem reading " + javafilename, e); 115 } 116 117 this.ppts = ppts; 118 this.slashslash = slashslash; 119 this.insert_inexpressible = insert_inexpressible; 120 this.lightweight = lightweight; 121 this.useReflection = useReflection; 122 this.maxInvariantsPP = maxInvariantsPP; 123 } 124 125 // Like Ast.addComment, but also keeps a list of what comments were added. 126 void addComment(Node n, String comment, boolean first) { 127 NodeToken nt = new NodeToken(comment); 128 Ast.findLineAndCol(n, nt, first); 129 addedComments.add(nt); 130 if (debug) { 131 System.out.printf("addComment at %d:%d : %s%n", nt.beginLine, nt.beginColumn, comment.trim()); 132 } 133 int linenumber = nt.beginLine - 1; /* because in jtb lines start at 1 */ 134 String lineString = javaFileLines.get(linenumber); 135 int column = 136 getTabbedIndex(nt.beginColumn - 1, /* because in jtb cols start at 1 */ lineString); 137 StringBuilder sb = new StringBuilder(); 138 sb.append(lineString.substring(0, column)); 139 sb.append(comment); 140 if (comment.endsWith("\n")) { 141 // Insert the whitespace preceding the line 142 sb.append(precedingWhitespace(lineString.substring(0, column))); 143 } 144 sb.append(lineString.substring(column)); 145 javaFileLines.set(linenumber, sb.toString()); 146 if (debug) { 147 System.out.printf("addComment result: <<<%s>>>%n", sb.toString()); 148 } 149 } 150 151 // Like Ast.addComment, but also keeps a list of what comments were added. 152 void addComment(Node n, String comment) { 153 addComment(n, comment, false); 154 } 155 156 // Like Ast.addComment, but also keeps a list of what comments were added. 157 void addCommentAfter(Node n, String comment) { 158 addComment(Ast.nodeTokenAfter(n), comment, true); 159 } 160 161 @Pure 162 private boolean isOwned(String fieldname) { 163 for (ClassFieldInfo cfi : cfis) { 164 if (cfi.ownedFieldNames.contains(fieldname)) { 165 return true; 166 } 167 } 168 return false; 169 } 170 171 @Pure 172 private boolean isNotContainsNull(String fieldname) { 173 for (ClassFieldInfo cfi : cfis) { 174 if (cfi.notContainsNullFieldNames.contains(fieldname)) { 175 return true; 176 } 177 } 178 return false; 179 } 180 181 @Pure 182 private boolean isElementType(String fieldname) { 183 for (ClassFieldInfo cfi : cfis) { 184 if (cfi.elementTypeFieldNames.containsKey(fieldname)) { 185 return true; 186 } 187 } 188 return false; 189 } 190 191 private String elementType(String fieldname) { 192 for (ClassFieldInfo cfi : cfis) { 193 String result = cfi.elementTypeFieldNames.get(fieldname); 194 if (result != null) { 195 return result; 196 } 197 } 198 throw new Error("Didn't find ClassFieldInfo for " + fieldname); 199 } 200 201 static class ClassFieldInfo { 202 // List<FieldDeclaration> fieldDecls; 203 // List<String> allFieldNames; 204 List<String> ownedFieldNames; 205 List<String> finalFieldNames; 206 List<String> notContainsNullFieldNames; 207 Map<String, String> elementTypeFieldNames; 208 209 ClassFieldInfo( 210 List<String> ownedFieldNames, 211 List<String> finalFieldNames, 212 List<String> notContainsNullFieldNames, 213 Map<String, String> elementTypeFieldNames) { 214 this.ownedFieldNames = ownedFieldNames; 215 this.finalFieldNames = finalFieldNames; 216 this.notContainsNullFieldNames = notContainsNullFieldNames; 217 this.elementTypeFieldNames = elementTypeFieldNames; 218 } 219 } 220 221 // Insert object invariants for this class. 222 // Insert owner assertions for fields. 223 // Grammar production: 224 // f0 -> ( "class" | "interface" ) 225 // f1 -> <IDENTIFIER> 226 // f2 -> [ TypeParameters() ] 227 // f3 -> [ ExtendsList(isInterface) ] 228 // f4 -> [ ImplementsList(isInterface) ] 229 // f5 -> ClassOrInterfaceBody(isInterface) 230 @Override 231 public void visit(ClassOrInterfaceDeclaration n) { 232 String classname = Ast.getClassName(n); 233 String pptname = classname + ":::OBJECT"; 234 PptTopLevel object_ppt = ppts.get(pptname); 235 if (object_ppt == null) { 236 pptname = classname + ":::CLASS"; 237 object_ppt = ppts.get(pptname); // might *still* be null; we'll check later 238 } 239 240 ClassFieldInfo cfi; 241 { // set fieldNames slots 242 CollectFieldsVisitor cfv = new CollectFieldsVisitor(n, false); 243 if (object_ppt == null) { 244 cfi = 245 new ClassFieldInfo( 246 cfv.ownedFieldNames(), 247 cfv.finalFieldNames(), 248 new ArrayList<String>(), 249 new HashMap<>()); 250 } else { 251 cfi = 252 new ClassFieldInfo( 253 cfv.ownedFieldNames(), 254 cfv.finalFieldNames(), 255 not_contains_null_fields(object_ppt, cfv.allFieldNames()), 256 element_type_fields(object_ppt, cfv.allFieldNames())); 257 } 258 } 259 cfis.push(cfi); 260 261 super.visit(n); // call "accept(this)" on each field 262 263 if (lightweight && (Daikon.output_format != OutputFormat.DBCJAVA)) { 264 for (int i = cfi.ownedFieldNames.size() - 1; i >= 0; i--) { 265 addComment( 266 n.f5.f1, 267 javaLineComment("@ invariant " + cfi.ownedFieldNames.get(i) + ".owner == this;"), 268 true); 269 } 270 } 271 if (object_ppt == null) { 272 if (debug) { 273 System.out.println("No object program point found for " + classname); 274 } 275 } else { 276 InvariantsAndModifiedVars obj_invs = invariants_for(object_ppt, ppts); 277 String inv_tag = (Daikon.output_format == OutputFormat.DBCJAVA ? "@invariant" : "invariant"); 278 insertInvariants(n.f5.f1, inv_tag, obj_invs); 279 } 280 281 cfis.pop(); 282 } 283 284 // Given that this method works not on FieldDeclaration, but its grandparent, 285 // it should likely be moved to a method with a different formal parameter. 286 @Override 287 public void visit(FieldDeclaration n) { 288 super.visit(n); // call "accept(this)" on each field 289 290 // Nothing to do for Jtest DBCJAVA format 291 if (Daikon.output_format == OutputFormat.DBCJAVA) { 292 return; 293 } 294 295 if (!Ast.contains(n.getParent().getParent(), "public")) { 296 297 // n.accept(new TreeFormatter()); 298 // String nString = Ast.format(n); 299 // System.out.println("@@@"); 300 // Node n2 = n.getParent().getParent(); n2.accept(new TreeFormatter()); 301 // System.out.println("@@@" + Ast.format(n2)); 302 addComment(n.getParent().getParent(), "/*@ spec_public */ "); 303 } 304 } 305 306 // Node n is a MethodDeclaration or a ConstructorDeclaration 307 @Nullable InvariantsAndModifiedVars[] get_requires_and_ensures(PptMap ppts, Node n) { 308 InvariantsAndModifiedVars requires_invs = null; 309 InvariantsAndModifiedVars ensures_invs = null; 310 311 List<PptTopLevel> matching_ppts = null; 312 if (n instanceof MethodDeclaration) { 313 matching_ppts = pptMatcher.getMatches(ppts, (MethodDeclaration) n); 314 } else if (n instanceof ConstructorDeclaration) { 315 ConstructorDeclaration cd = (ConstructorDeclaration) n; 316 matching_ppts = pptMatcher.getMatches(ppts, cd); 317 } else { 318 throw new Error("Node must be MethodDeclaration or ConstructorDeclaration"); 319 } 320 321 for (PptTopLevel ppt : matching_ppts) { 322 if (ppt.ppt_name.isEnterPoint()) { 323 requires_invs = invariants_for(ppt, ppts); 324 } else if (ppt.ppt_name.isExitPoint()) { 325 if (!ppt.ppt_name.isCombinedExitPoint()) { 326 continue; 327 } 328 ensures_invs = invariants_for(ppt, ppts); 329 } 330 } 331 332 // if (debug) { 333 // System.out.printf("get_requires_and_ensures(%s):%n => requires=%s%n => ensures=%s%n", n, 334 // requires_invs, ensures_invs); 335 // } 336 337 return new @Nullable InvariantsAndModifiedVars[] {requires_invs, ensures_invs}; 338 } 339 340 public void insertAlso(Node n) { 341 addComment(n, "@ also" + lineSep, true); 342 } 343 344 // n must be a MethodDeclaration or ConstructorDeclaration 345 public void insertBehavior(Node n) { 346 class InsertBehaviorVisitor extends DepthFirstVisitor { 347 Node n; 348 boolean behaviorInserted; 349 350 public InsertBehaviorVisitor(Node n) { 351 super(); 352 this.n = n; 353 behaviorInserted = false; 354 } 355 356 private String getBehaviorString() { 357 return "normal_behavior"; 358 } 359 360 @Override 361 public void visit(NodeChoice nc) { 362 // Since we know we are in a Modifiers() parse tree, the only 363 // thing a NodeChoice can hold is a NodeToken for the modifer. 364 Annotate.debug.fine("InsertBehavior visitor visiting a NodeChoice"); 365 String modifier = (nc != null && nc.choice != null ? nc.choice.toString() : ""); 366 Annotate.debug.fine("A node choice here: " + modifier); 367 368 if (Ast.isAccessModifier(modifier)) { 369 Annotate.debug.fine("addComment from nc"); 370 addComment( 371 n.getParent().getParent(), 372 "@ " 373 + modifier 374 + " " 375 + getBehaviorString() 376 + (Daikon.output_format == OutputFormat.JML ? " // Generated by Daikon" : "") 377 + lineSep, 378 true); 379 Annotate.debug.fine("addComment complete"); 380 behaviorInserted = true; 381 } 382 } 383 384 @Override 385 @SuppressWarnings("JdkObsolete") // JTB uses Vector and Enumeration 386 public void visit(NodeListOptional nlo) { 387 Annotate.debug.fine("InsertBehavior visitor visiting a NodeListOptional"); 388 Annotate.debug.fine("With " + nlo.nodes.size() + " nodes"); 389 390 if (nlo.present()) { 391 for (Enumeration<Node> e = nlo.elements(); e.hasMoreElements(); ) { 392 e.nextElement().accept(this); 393 } 394 } 395 396 if (!behaviorInserted) { 397 Annotate.debug.fine("addComment from nlo"); 398 addComment( 399 n.getParent().getParent(), 400 "@ " 401 + "private " 402 + getBehaviorString() 403 + (Daikon.output_format == OutputFormat.JML ? " // Generated by Daikon" : "") 404 + lineSep, 405 true); 406 Annotate.debug.fine("addComment complete"); 407 } 408 } 409 } 410 411 InsertBehaviorVisitor v = new InsertBehaviorVisitor(n); 412 413 // We are going to move back up the parse tree so we can look 414 // at the Modifiers for this method (or constructor). 415 416 NodeChoice nc = (NodeChoice) n.getParent(); 417 NodeSequence ns = (NodeSequence) nc.getParent(); 418 Modifiers m = (Modifiers) ns.elementAt(0); 419 if (Annotate.debug.isLoggable(Level.FINE)) { 420 System.out.println(Ast.formatEntireTree(m)); 421 } 422 m.accept(v); 423 Annotate.debug.fine("InsertBehavior visitor complete"); 424 } 425 426 @Override 427 public void visit(MethodDeclaration n) { 428 429 // Grammar production for ClassOrInterfaceBodyDeclaration: 430 // f0 -> Initializer() 431 // | Modifiers() ( ClassOrInterfaceDeclaration(modifiers) | EnumDeclaration(modifiers) | 432 // ConstructorDeclaration() | FieldDeclaration(modifiers) | MethodDeclaration(modifiers) ) 433 // | ";" 434 435 super.visit(n); // call "accept(this)" on each field 436 437 @Nullable InvariantsAndModifiedVars[] requires_and_ensures = get_requires_and_ensures(ppts, n); 438 439 InvariantsAndModifiedVars requires_invs = requires_and_ensures[0]; 440 InvariantsAndModifiedVars ensures_invs = requires_and_ensures[1]; 441 442 String ensures_tag = Daikon.output_format.ensures_tag(); 443 String requires_tag = Daikon.output_format.requires_tag(); 444 445 boolean isOverride = false; 446 boolean isImplementation = false; 447 if (useReflection) { 448 isOverride = Ast.isOverride(n); // of a superclass 449 isImplementation = Ast.isImplementation(n); // of an interface 450 } 451 452 if (lightweight) { 453 if (isImplementation) { 454 requires_tag = "also_" + requires_tag; 455 ensures_tag = "also_" + ensures_tag; 456 } 457 if (isOverride) { 458 requires_invs = null; // no requires permitted on overridden methods 459 ensures_tag = "also_" + ensures_tag; 460 } 461 } 462 463 if (!lightweight) { 464 addComment( 465 n.getParent().getParent() /* see ClassOrInterfaceBodyDeclaration */, 466 JML_END_COMMENT, 467 true); 468 } 469 470 boolean invariantInserted = 471 insertInvariants( 472 n.getParent().getParent() /* see ClassOrInterfaceBodyDeclaration */, 473 ensures_tag, 474 ensures_invs, 475 lightweight); 476 477 if (ensures_invs != null) { 478 insertModifies( 479 n.getParent().getParent() /* see ClassOrInterfaceBodyDeclaration */, 480 ensures_invs.modifiedVars, 481 ensures_tag, 482 lightweight); 483 } 484 485 invariantInserted = 486 insertInvariants( 487 n.getParent().getParent() /* see ClassOrInterfaceBodyDeclaration */, 488 requires_tag, 489 requires_invs, 490 lightweight) 491 || invariantInserted; 492 493 if (!lightweight) { 494 if (!invariantInserted) { 495 insertJMLWorkaround(n.getParent().getParent() /* see ClassOrInterfaceBodyDeclaration */); 496 } 497 insertBehavior(n); 498 if (isImplementation 499 || isOverride 500 // temporary fix: not processed correctly by Ast.java 501 || n.f2.f0.toString().equals("clone")) { 502 insertAlso(n.getParent().getParent()); 503 } 504 addComment( 505 n.getParent().getParent() /* see ClassOrInterfaceBodyDeclaration */, 506 JML_START_COMMENT, 507 true); 508 } 509 } 510 511 // Grammar production: 512 // f0 -> [ TypeParameters() ] 513 // f1 -> <IDENTIFIER> 514 // f2 -> FormalParameters() 515 // f3 -> [ "throws" NameList() ] 516 // f4 -> "{" 517 // f5 -> [ ExplicitConstructorInvocation() ] 518 // f6 -> ( BlockStatement() )* 519 // f7 -> "}" 520 @Override 521 public void visit(ConstructorDeclaration n) { 522 Annotate.debug.fine("ConstructorDeclaration: " + n.f1); 523 524 super.visit(n); // call "accept(this)" on each field 525 526 @Nullable InvariantsAndModifiedVars[] requires_and_ensures = get_requires_and_ensures(ppts, n); 527 InvariantsAndModifiedVars requires_invs = requires_and_ensures[0]; 528 InvariantsAndModifiedVars ensures_invs = requires_and_ensures[1]; 529 530 String ensures_tag = Daikon.output_format.ensures_tag(); 531 String requires_tag = Daikon.output_format.requires_tag(); 532 533 if (!lightweight) { 534 addComment( 535 n.getParent().getParent() /* see ClassOrInterfaceBodyDeclaration */, 536 JML_END_COMMENT, 537 true); 538 } 539 540 boolean invariantInserted = 541 insertInvariants( 542 n.getParent().getParent() /* see ClassOrInterfaceBodyDeclaration */, 543 ensures_tag, 544 ensures_invs, 545 lightweight); 546 547 if (ensures_invs != null) { 548 insertModifies( 549 n.getParent().getParent() /* see ClassOrInterfaceBodyDeclaration */, 550 ensures_invs.modifiedVars, 551 ensures_tag, 552 lightweight); 553 } 554 555 invariantInserted = 556 insertInvariants( 557 n.getParent().getParent() /* see ClassOrInterfaceBodyDeclaration */, 558 requires_tag, 559 requires_invs, 560 lightweight) 561 || invariantInserted; 562 563 if (!lightweight) { 564 if (!invariantInserted) { 565 insertJMLWorkaround(n.getParent().getParent() /* see ClassOrInterfaceBodyDeclaration */); 566 } 567 insertBehavior(n); 568 addComment( 569 n.getParent().getParent() /* see ClassOrInterfaceBodyDeclaration */, 570 JML_START_COMMENT, 571 true); 572 } 573 } 574 575 // This is a hack that indicates whether an "assignable \everything" 576 // clause would be legal for the given method. 577 public boolean pureInJML(Node n) { 578 if (n instanceof MethodDeclaration) { 579 String name = ((MethodDeclaration) n).f2.f0.toString(); 580 if (name.equals("clone") 581 || name.equals("equals") 582 || name.equals("hashCode") 583 || name.equals("hasMoreElements") 584 || name.equals("hasNext")) { 585 return true; 586 } 587 } 588 return false; 589 } 590 591 // This seems to get inserted when nothing else does. I guess it works 592 // around some problem with JML output. 593 public void insertJMLWorkaround(Node n) { 594 addComment(n, "@ requires true;" + lineSep, true); 595 } 596 597 public boolean insertInvariants(Node n, String prefix, InvariantsAndModifiedVars invs) { 598 return insertInvariants(n, prefix, invs, true); 599 } 600 601 public boolean insertModifies( 602 Node n, String modifiesString, String prefix, boolean useJavaComment) { 603 604 modifiesString = modifiesString.trim(); 605 606 if (modifiesString.equals("modifies") || modifiesString.equals("assignable")) { 607 // No variables found. 608 return false; 609 } 610 611 if (!(modifiesString.startsWith("modifies") || modifiesString.startsWith("assignable"))) { 612 // Doesn't look ilke a modifies clause. 613 return false; 614 } 615 616 String inv = null; 617 boolean doInsert = true; 618 619 if (Daikon.output_format == OutputFormat.JML) { 620 // Currently, AnnotateVisitor currently does not produce sensible 621 // "assignable" clauses for JML. 622 // * One possible default is "assignable \everything". However, that 623 // is incorrect when the method is pure (that is, it overrides a 624 // method that is annotated as pure, such as hashCode). 625 // * Another possible default is to omit the assignable clause 626 // entirely. Since "assignable \everything" is the JML default, this 627 // omission strategy works both when the method is pure and when it 628 // is not. Thus, we use this strategy. Thanks to Christoph 629 // Csnallner for this idea. 630 // In the future, AnnotateVisitor should produce "assignable" clauses. 631 doInsert = false; 632 } else if (Daikon.output_format == OutputFormat.DBCJAVA) { 633 // Modifies/assignable has no translation in Jtest DBC 634 doInsert = false; 635 } else if (lightweight && prefix.startsWith("also_")) { 636 inv = "also_" + modifiesString; 637 } else { 638 inv = modifiesString; 639 } 640 641 if (!doInsert) { 642 return false; 643 } 644 645 String commentContents = "@ " + inv + ";"; 646 if (useJavaComment) { 647 commentContents = javaLineComment(commentContents); 648 } else { 649 commentContents += lineSep; 650 } 651 addComment(n, commentContents, true); 652 return true; 653 } 654 655 // The "invs" argument may be null, in which case no work is done. 656 @EnsuresNonNullIf(result = true, expression = "#3") 657 public boolean insertInvariants( 658 Node n, String prefix, @Nullable InvariantsAndModifiedVars invs, boolean useJavaComment) { 659 if (invs == null) { 660 return false; 661 } 662 663 boolean invariantInserted = false; 664 665 int maxIndex = invs.invariants.size(); 666 if (maxInvariantsPP > 0) { 667 maxIndex = Math.min(maxIndex, maxInvariantsPP); 668 } 669 670 for (int i = maxIndex - 1; i >= 0; i--) { 671 Invariant inv = invs.invariants.get(i); 672 if (!inv.isValidExpression(Daikon.output_format)) { 673 // inexpressible invariant 674 if (insert_inexpressible) { 675 addComment(n, javaLineComment("! " + inv + ";"), true); 676 } 677 continue; 678 } else { 679 String commentContents = 680 (Daikon.output_format == OutputFormat.DBCJAVA ? " " : "@ ") 681 + prefix 682 + " " 683 + inv.format_using(Daikon.output_format) 684 + (Daikon.output_format == OutputFormat.DBCJAVA ? " " : ";"); 685 if (useJavaComment) { 686 commentContents = javaLineComment(commentContents); 687 } else { 688 commentContents += lineSep; 689 } 690 691 addComment(n, commentContents, true); 692 invariantInserted = true; 693 } 694 } 695 696 return invariantInserted; 697 } 698 699 // Set .owner and/or .containsnull for ++, --, etc expressions within a 700 // statement. 701 // f0 -> PreIncrementExpression() 702 // | PreDecrementExpression() 703 // | PrimaryExpression() [ "++" | "--" | AssignmentOperator() Expression() ] 704 @Override 705 public void visit(StatementExpression n) { 706 super.visit(n); // call "accept(this)" on each field 707 708 Annotate.debug.fine("Found a statement expression: " + n.f0.choice); 709 710 // Nothing to do for Jtest DBC format 711 if (Daikon.output_format == OutputFormat.DBCJAVA) { 712 return; 713 } 714 715 if (n.f0.choice instanceof NodeSequence) { 716 NodeSequence ns = (NodeSequence) n.f0.choice; 717 PrimaryExpression pe = (PrimaryExpression) ns.elementAt(0); 718 // System.out.println("pe:" + Ast.format(pe)); 719 // for (int i = 0; i<ns.size(); i++) { 720 // System.out.println("ns #" + i + ": " + ns.get(i)); 721 // } 722 if (ns.size() == 2) { 723 NodeOptional no = (NodeOptional) ns.elementAt(1); 724 NodeChoice nc = (NodeChoice) no.node; 725 if ((nc != null) && (nc.choice instanceof NodeSequence)) { 726 // It's an assignment. 727 728 // Don't take action unless the PrimaryExpression is a simple 729 // Name (that's effectively checked below) and has no 730 // PrimarySuffix or else its prefix is "this" (check that here). 731 String fieldname = null; 732 // System.out.println("pe.f1.size:" + pe.f1.size()); 733 if (pe.f1.size() == 0) { 734 fieldname = Ast.fieldName(pe); 735 } else if (pe.f1.size() == 1) { 736 if (pe.f0.f0.which == 1) { // prefix is "this" 737 PrimarySuffix ps = (PrimarySuffix) pe.f1.elementAt(0); 738 if (ps.f0.which == 3) { // suffix is an identifier 739 NodeSequence ns2 = (NodeSequence) ps.f0.choice; 740 fieldname = ((NodeToken) ns2.elementAt(1)).tokenImage; 741 } 742 } 743 } 744 745 // System.out.printf("In statement, fieldname = %s%n", fieldname); 746 if ((fieldname != null) 747 && (isOwned(fieldname) || isNotContainsNull(fieldname) || isElementType(fieldname))) { 748 ConstructorDeclaration cd = 749 (ConstructorDeclaration) Ast.getParent(ConstructorDeclaration.class, n); 750 MethodDeclaration md = (MethodDeclaration) Ast.getParent(MethodDeclaration.class, n); 751 if ((cd != null) || ((md != null) && !Ast.contains(md.f0, "static"))) { 752 @SuppressWarnings("nullness") 753 @NonNull Node parent = Ast.getParent(Statement.class, n); 754 // If parent isn't in a block (eg, if parent 755 // is sole element in then or else clause), then this is wrong. 756 // It's safe, however. But does it cause syntax errors if an 757 // else clause follows a then clause without braces? 758 if (isOwned(fieldname)) { 759 if (lightweight) { 760 addCommentAfter(parent, javaLineComment("@ set " + fieldname + ".owner = this;")); 761 } 762 } 763 if (isNotContainsNull(fieldname)) { 764 addCommentAfter( 765 parent, javaLineComment("@ set " + fieldname + ".containsNull = false;")); 766 } 767 if (isElementType(fieldname)) { 768 addCommentAfter( 769 parent, 770 javaLineComment( 771 "@ set " + fieldname + ".elementType = " + elementType(fieldname) + ";")); 772 } 773 } 774 } 775 } 776 } 777 } 778 } 779 780 // This is an assignment exactly if field f1 is present. 781 // f0 -> ConditionalExpression() 782 // f1 -> [ AssignmentOperator() Expression() ] 783 @Override 784 public void visit(Expression n) { 785 super.visit(n); // call "accept(this)" on each field 786 787 if (n.f1.present()) { 788 Annotate.debug.fine("found an assignment expression: " + n); 789 PrimaryExpression pe = Ast.assignment2primaryexpression(n); 790 String fieldname = Ast.fieldName(pe); 791 Annotate.debug.fine("In expression, fieldname = " + fieldname); 792 @SuppressWarnings("nullness") // every expression is within a statement 793 @NonNull Node stmt = Ast.getParent(Statement.class, n); 794 if ((fieldname != null) && isOwned(fieldname)) { 795 if (lightweight) { 796 addCommentAfter(stmt, javaLineComment("@ set " + fieldname + ".owner = this;")); 797 } 798 } 799 } 800 } 801 802 /////////////////////////////////////////////////////////////////////////// 803 /// Subroutines 804 /// 805 806 /** The argument should already contain "@" or any other leading characters. */ 807 String javaLineComment(String comment) { 808 if (slashslash) { 809 return "//" + comment + lineSep; 810 } else { 811 return "/*" + comment + " */" + lineSep; 812 } 813 } 814 815 /** The argument should already contain "@" or any other leading characters. */ 816 String javaComment(String comment) { 817 return "/*" + comment + "*/"; 818 } 819 820 /** 821 * Find a Daikon variable for the given field. The variable is either "this.field" (for instance 822 * variables) or "ClassName.field" (for static variables). 823 */ 824 private VarInfo findVar(String field, PptTopLevel ppt) { 825 String varname = "this." + field; 826 VarInfo vi = ppt.find_var_by_name(varname); 827 if (vi == null) { 828 // No "this.field" variable, so try (static) "ClassName.field". 829 varname = ppt.ppt_name.getFullClassName() + "." + field; 830 vi = ppt.find_var_by_name(varname); 831 } 832 if (vi == null) { 833 // We found a variable in the source code that is not computed by 834 // Daikon. (This can happen if Daikon was run omitting the 835 // variable, for instance due to --std-visibility. But I want to 836 // throw an error instead, since I expect bugs to be more common 837 // than such problems.) 838 debug_field_problem(field, ppt); 839 throw new Error( 840 "Warning: Annotate: Daikon knows nothing about variable " + varname + " at " + ppt); 841 } 842 return vi; 843 } 844 845 private void debug_field_problem(String field, PptTopLevel ppt) { 846 System.out.println( 847 "Warning: Annotate: Daikon knows nothing about field " + field + " at " + ppt); 848 System.out.println(" ppt.var_infos:"); 849 for (VarInfo pptvi : ppt.var_infos) { 850 System.out.println(" " + pptvi.name()); 851 } 852 } 853 854 // Returns a list of fields with ".containsNull == false" invariants. 855 // ppt is an :::OBJECT or :::CLASS program point. 856 List<String> not_contains_null_fields(PptTopLevel ppt, List<String> allFieldNames) { 857 // System.out.println("not_contains_null_fields(" + ppt + ")"); 858 List<String> result = new ArrayList<>(); 859 for (String field : allFieldNames) { 860 // System.out.println("field: " + field); 861 VarInfo vi = findVar(field, ppt); 862 PptSlice1 slice = ppt.findSlice(vi); 863 if (slice == null) { 864 // There might be no slice if there are no invariants over this var. 865 continue; 866 } 867 EltNonZero enz = EltNonZero.find(slice); 868 if (enz != null) { 869 String enz_format = format((Invariant) enz); 870 if (enz_format.endsWith(".containsNull == false")) { 871 result.add(field); 872 } 873 } 874 } 875 return result; 876 } 877 878 // Returns a HashMap for fields with ".elementType == \type(...)" invariants, 879 // mapping the field to the type. 880 // ppt is an :::OBJECT or :::CLASS program point. 881 HashMap<String, String> element_type_fields(PptTopLevel ppt, List<String> allFieldNames) { 882 // System.out.println("element_type_fields(" + ppt + ")"); 883 HashMap<String, String> result = new HashMap<>(); 884 // FieldDeclaration[] fdecls = cfv.fieldDeclarations(); 885 // System.out.println("fields: " + Arrays.toString(fields)); 886 for (String field : allFieldNames) { 887 // System.out.printf("element_type_fields (%s) field: %s%n", ppt, field); 888 VarInfo vi = findVar(field, ppt); 889 if (!vi.is_array()) { 890 continue; 891 } 892 if (vi.type.elementType().isPrimitive()) { 893 continue; 894 } 895 String varname = vi.name(); 896 String elt_varname = varname + "[]"; 897 VarInfo elt_vi = ppt.find_var_by_name(elt_varname); 898 if (elt_vi == null) { 899 debug_field_problem(elt_varname, ppt); 900 throw new Daikon.UserError( 901 "Annotate: Daikon knows nothing about variable " + elt_varname + " at " + ppt); 902 } 903 // et_varname variable represents the types of the elements. 904 String et_varname = elt_varname + DaikonVariableInfo.class_suffix; 905 // System.out.printf("Found %s, seeking %s%n", elt_varname, et_varname); 906 // We found variable "a[]". Now find "a[].getClass().getName()". 907 // (Why might it not be present?) 908 // It might not be present if DaikonVariableInfo.shouldAddRuntimeClass would return false. 909 assert elt_vi.rep_type.isArray(); 910 if (elt_vi.rep_type.dimensions() == 1 && elt_vi.rep_type.baseIsPrimitive()) { 911 continue; 912 } 913 VarInfo et_vi = ppt.find_var_by_name(et_varname); 914 if (et_vi == null) { 915 debug_field_problem(et_varname, ppt); 916 throw new Daikon.UserError( 917 String.format( 918 "Annotate: Daikon knows nothing about variable %s at %s%n with allFieldNames = %s", 919 et_varname, ppt, allFieldNames)); 920 } 921 922 // et_vi != null 923 PptSlice1 slice = ppt.findSlice(et_vi); 924 if (slice == null) { 925 // There might be no slice if there are no invariants over this var. 926 continue; 927 } 928 // System.out.println("Found slice for " + et_vi.name.name()); 929 { 930 EltOneOfString eoos = EltOneOfString.find(slice); 931 // System.out.println("eoos: " + (eoos == null ? "null" : format((Invariant)eoos))); 932 if ((eoos != null) && (eoos.num_elts() == 1)) { 933 String eoos_format = format((Invariant) eoos); 934 int et_pos = eoos_format.indexOf(".elementType == \\type("); 935 if (et_pos != -1) { 936 String type = eoos_format.substring(et_pos + ".elementType == ".length()); 937 result.put(field, type); 938 } 939 } 940 } 941 { 942 OneOfStringSequence eooss = OneOfStringSequence.find(slice); 943 // System.out.println("eooss: " + (eooss == null ? "null" : format((Invariant)eooss))); 944 if (eooss != null) { 945 String eooss_format = format((Invariant) eooss); 946 int et_pos = eooss_format.indexOf(".elementType == \\type("); 947 if (et_pos != -1) { 948 String type = eooss_format.substring(et_pos + ".elementType == ".length()); 949 result.put(field, type); 950 } 951 } 952 } 953 } 954 return result; 955 } 956 957 public String format(Invariant inv) { 958 String inv_string; 959 if (lightweight) { 960 inv_string = inv.format_using(OutputFormat.ESCJAVA); 961 } else { 962 inv_string = inv.format_using(OutputFormat.JML); 963 } 964 // Debugging 965 // if (true) { 966 // inv_string = inv_string + " REPR: " + inv.repr(); 967 // } 968 return inv_string; 969 } 970 971 public static InvariantsAndModifiedVars invariants_for(PptTopLevel ppt, PptMap pptmap) { 972 973 StringWriter sw = new StringWriter(); 974 PrintWriter pw = new PrintWriter(sw); 975 PrintInvariants.print_modified_vars(ppt, pw); 976 977 InvariantsAndModifiedVars retval = 978 new InvariantsAndModifiedVars(Ast.getInvariants(ppt, pptmap), sw.toString()); 979 980 // PrintInvariants.print_modified_vars(ppt, pw) returns possibly 981 // several lines. In such a case, we're only interested in the second 982 // one, which contains the "modified" or "assignable" clause. 983 String[] splitModVars = StringsPlume.splitLines(retval.modifiedVars); 984 if (splitModVars.length > 1) { 985 for (int i = 0; i < splitModVars.length; i++) { 986 if (splitModVars[i].startsWith("modifies ") || splitModVars[i].startsWith("assignable ")) { 987 retval.modifiedVars = splitModVars[i]; 988 break; 989 } 990 } 991 } 992 993 return retval; 994 } 995 996 private static class InvariantsAndModifiedVars { 997 public List<Invariant> invariants; 998 public String modifiedVars; 999 1000 public InvariantsAndModifiedVars(List<Invariant> invariants, String modifiedVars) { 1001 this.invariants = invariants; 1002 this.modifiedVars = modifiedVars; 1003 } 1004 } 1005 1006 // Consider a line L1 of text that contains some tabs. 1007 // 1008 // untabbedIndex represents an index into a line L2, where L2 is L1 1009 // with all tabs substituted by their corresponding number of blank 1010 // spaces, where a tab's width is 8 characters. 1011 // 1012 // This method returns the index into L1 that corresponds to the same 1013 // position in L2. 1014 // 1015 // I assume that untabbedIndex will not be an index into one of the 1016 // blank spaces that replaced some tab. 1017 public static int getTabbedIndex(int untabbedIndex, String L1) { 1018 if (untabbedIndex == 0) { 1019 return 0; 1020 } 1021 int expanded = 0; 1022 int index = 0; 1023 for (int i = 0; i < L1.length(); i++) { 1024 1025 if (expanded > untabbedIndex) { 1026 throw new RuntimeException( 1027 String.join( 1028 System.lineSeparator(), 1029 "expanded:" + expanded, 1030 "untabbedIndex:" + untabbedIndex, 1031 " L1:" + L1)); 1032 } else if (expanded == untabbedIndex) { 1033 index = i; 1034 break; 1035 } 1036 1037 char c = L1.charAt(i); 1038 if (c == '\t') { 1039 expanded += 8 - (expanded % 8); 1040 } else { 1041 expanded += 1; 1042 } 1043 } 1044 1045 assert expanded == untabbedIndex 1046 : String.join( 1047 System.lineSeparator(), 1048 "expanded:" + expanded, 1049 "untabbedIndex:" + untabbedIndex, 1050 "L1: " + L1); 1051 return index; 1052 } 1053 1054 /** Return the whitespace at the front of the string. */ 1055 public static String precedingWhitespace(String s) { 1056 for (int i = 0; i < s.length(); i++) { 1057 if (!Character.isWhitespace(s.charAt(i))) { 1058 return s.substring(0, i); 1059 } 1060 } 1061 return s; 1062 } 1063}