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}