001package daikon.tools.runtimechecker;
002
003import daikon.PptMap;
004import daikon.PptTopLevel;
005import daikon.inv.Invariant;
006import daikon.inv.OutputFormat;
007import daikon.inv.ternary.threeScalar.FunctionBinary;
008import daikon.tools.jtb.Ast;
009import daikon.tools.jtb.PptNameMatcher;
010import java.io.StringWriter;
011import java.lang.reflect.Constructor;
012import java.lang.reflect.Method;
013import java.util.ArrayList;
014import java.util.HashMap;
015import java.util.HashSet;
016import java.util.List;
017import java.util.Map;
018import java.util.Set;
019import java.util.StringJoiner;
020import java.util.Vector;
021import jtb.syntaxtree.*;
022import jtb.visitor.DepthFirstVisitor;
023import jtb.visitor.TreeDumper;
024import jtb.visitor.TreeFormatter;
025import org.checkerframework.checker.nullness.qual.KeyFor;
026import org.checkerframework.checker.nullness.qual.NonNull;
027import org.plumelib.util.StringsPlume;
028
029/**
030 * Visitor that instruments a Java source file (i.e. adds code at certain places) to check invariant
031 * violations at run time.
032 */
033@SuppressWarnings("rawtypes")
034public class InstrumentVisitor extends DepthFirstVisitor {
035
036  // If false, all invariants will be output. If true, only
037  // invariants with high confidence will be output.
038  public static boolean outputOnlyHighConfInvariants = false;
039
040  // Properties under this threshold will be considered minor; at or
041  // above threshold will be considered major.
042  // See daikon.tools.runtimechecker.Property.calculateConfidence()
043  public static double confidenceThreshold = 0.5;
044
045  // If true, the instrumenter will make all fields of the class
046  // visible. The reason for doing this is so that invariants over
047  // potentially inaccessible object fields can be evaluated.
048  public static boolean makeAllFieldsPublic = false;
049
050  // The map containing the invariants.
051  private final PptMap pptmap;
052
053  // The AST of the class FooPropertyChecks, where Foo is the class
054  // that we're instrumenting.
055  public CheckerClasses checkerClasses;
056
057  // The methods and constructors that were visited (in other words,
058  // those explicitly declared in the source).
059  public List<Method> visitedMethods = new ArrayList<>();
060  public List<Constructor<?>> visitedConstructors = new ArrayList<>();
061
062  // [[ TODO: I'm using xmlString() because it will definitely give
063  // different values for different Properties. But if Properties
064  // are in fact unique and immutable, and if I ensure that hashcode
065  // returns unique values for unique Properties, then I should be
066  // able to use hashcode. So: make sure that the statements above
067  // are all correct, and then use hashcode. ]]
068  private final Map<String, String> xmlStringToIndex = new HashMap<>();
069
070  private int varNumCounter = 0;
071
072  private final PptNameMatcher pptMatcher;
073
074  /** Create a visitor that will insert code to check the invariants contained in pptmap. */
075  public InstrumentVisitor(PptMap pptmap, TypeDeclaration root) {
076
077    this.checkerClasses = new CheckerClasses();
078
079    this.pptmap = pptmap;
080    this.pptMatcher = new PptNameMatcher(root);
081
082    for (PptTopLevel ppt : pptmap.pptIterable()) {
083
084      if (ppt.ppt_name.isExitPoint() && !ppt.ppt_name.isCombinedExitPoint()) {
085        continue;
086      }
087
088      List<Invariant> invList = filterInvariants(daikon.tools.jtb.Ast.getInvariants(ppt, pptmap));
089      for (Invariant inv : invList) {
090        xmlStringToIndex.put(toProperty(inv).xmlString(), Integer.toString(varNumCounter));
091        varNumCounter++;
092      }
093    }
094  }
095
096  /** If makeAllFieldsPublic == true, then it makes this field declaration public. */
097  @SuppressWarnings("JdkObsolete") // JTB uses Vector
098  @Override
099  public void visit(FieldDeclaration fd) {
100
101    // Fix any line/col inconsistencies first
102    fd.accept(new TreeFormatter());
103
104    super.visit(fd);
105    // Grammar production for ClassOrInterfaceBodyDeclaration:
106    // f0 -> Initializer()
107    //       | Modifiers() ( ClassOrInterfaceDeclaration(modifiers) | EnumDeclaration(modifiers)
108    //                       | ConstructorDeclaration() | FieldDeclaration(modifiers)
109    //                       | MethodDeclaration(modifiers) )
110    //       | ";"
111
112    if (makeAllFieldsPublic) {
113      NodeSequence seq = (NodeSequence) fd.getParent().getParent();
114      Modifiers modifiers = (Modifiers) seq.elementAt(0);
115      List<Node> modifierList = modifiers.f0.nodes;
116
117      List<Node> newModifiers = new ArrayList<>();
118      for (Node modifier : modifierList) {
119        NodeChoice nc = (NodeChoice) modifier;
120        NodeToken token = (NodeToken) nc.choice;
121        if (!token.tokenImage.equals("public")
122            && !token.tokenImage.equals("protected")
123            && !token.tokenImage.equals("private")) {
124          newModifiers.add(token);
125        }
126      }
127
128      newModifiers.add(new NodeToken("public"));
129      modifiers.f0.nodes = new Vector<Node>(newModifiers);
130    }
131  }
132
133  /**
134   * Adds the following new methods:
135   *
136   * <p>checkClassInvariantsInstrument(daikon.tools.runtimechecker.Violation.Time time) Checks the
137   * class invariants.
138   *
139   * <p>checkObjectInvariants_instrument(daikon.tools.runtimechecker.Violation.Time time) Check the
140   * object invariants
141   *
142   * <p>isDaikonInstrumented() returns true (you can imagine calling this method to see if the class
143   * has been instrumented).
144   *
145   * <p>getDaikonInvariants() Returns th array of properties being checked.
146   *
147   * <p>Adds the following field:
148   *
149   * <p>daikon.tools.runtimechecker.Property[] daikonProperties The properties being checked.
150   *
151   * <p>Adds code that initializes the properties array.
152   */
153  @Override
154  public void visit(ClassOrInterfaceBody clazz) {
155
156    checkerClasses.addCheckerClass(clazz);
157
158    // Fix any line/col inconsistencies first
159    clazz.accept(new TreeFormatter());
160
161    super.visit(clazz);
162
163    if (Ast.isInterface(clazz)) {
164      return;
165    }
166
167    // add method to check object and class invariants.
168
169    // just skip anonymous classes for now, hard to get a name for them without a decl node...
170    if ((clazz.getParent() instanceof ClassOrInterfaceDeclaration)) {
171      ClassOrInterfaceDeclaration ucd = (ClassOrInterfaceDeclaration) clazz.getParent();
172      String classname = Ast.getClassName(ucd);
173      ClassOrInterfaceBodyDeclaration objInvDecl =
174          checkObjectInvariants_instrumentDeclaration(classname);
175      Ast.addDeclaration(clazz, objInvDecl);
176
177      checkerClasses.addDeclaration(
178          clazz,
179          checkObjectInvariants_instrumentDeclaration_checker(
180              classname, false /* check minor properties */));
181      checkerClasses.addDeclaration(
182          clazz,
183          checkObjectInvariants_instrumentDeclaration_checker(
184              classname, true /* check major properties */));
185
186      if (!Ast.isInner(ucd) || Ast.isStatic(ucd)) {
187        ClassOrInterfaceBodyDeclaration classInvDecl =
188            checkClassInvariantsInstrumentDeclaration(classname);
189        checkerClasses.addDeclaration(
190            clazz,
191            checkClassInvariantsInstrumentDeclaration_checker(
192                classname, false /* check minor properties */));
193        checkerClasses.addDeclaration(
194            clazz,
195            checkClassInvariantsInstrumentDeclaration_checker(
196                classname, true /* check minor properties */));
197        Ast.addDeclaration(clazz, classInvDecl);
198        Ast.addDeclaration(clazz, getInvariantsDecl());
199        Ast.addDeclaration(clazz, isInstrumentedDecl());
200        Ast.addDeclaration(clazz, staticPropertyDecl());
201        Ast.addDeclaration(clazz, staticPropertyInit());
202      }
203    }
204  }
205
206  /**
207   * Adds code to check class invariants and preconditions on entry (but not object invariants,
208   * because there's no object yet!).
209   *
210   * <p>Adds code to check postcontiions, class and object invariants on exit.
211   */
212  // ConstructorDeclaration:
213  // f0 -> [ TypeParameters() ]
214  // f1 -> <IDENTIFIER>
215  // f2 -> FormalParameters()
216  // f3 -> [ "throws" NameList() ]
217  // f4 -> "{"
218  // f5 -> [ ExplicitConstructorInvocation() ]
219  // f6 -> ( BlockStatement() )*
220  @Override
221  public void visit(ConstructorDeclaration ctor) {
222
223    // Fix any line/col inconsistencies first
224    ctor.accept(new TreeFormatter());
225
226    visitedConstructors.add(Ast.getConstructor(ctor));
227
228    super.visit(ctor);
229
230    // Find declared throwables.
231    // List<String> declaredThrowables = getDeclaredThrowables(ctor.f3);
232
233    // System.out.println(Ast.formatEntireTree(ctor));
234
235    List<PptTopLevel> matching_ppts = pptMatcher.getMatches(pptmap, ctor);
236
237    StringBuilder code = new StringBuilder();
238
239    NodeListOptional ctorBody = ctor.f6;
240
241    // Create the code for a new BlockStatement that will be the new
242    // body of the constructor.
243    code.append("{");
244
245    // Count this program point entry.
246    code.append("daikon.tools.runtimechecker.Runtime.numPptEntries++;");
247
248    // Check class invariants.
249    code.append(
250        "checkClassInvariantsInstrument(daikon.tools.runtimechecker.Violation.Time.onEntry);");
251
252    String name = Ast.getName(ctor);
253    List<String> typesAndParameters = new ArrayList<>();
254    for (FormalParameter param : Ast.getParametersNoImplicit(ctor)) {
255      typesAndParameters.add(Ast.format(param));
256    }
257
258    checkerClasses.addDeclaration(
259        ctor,
260        checkPreconditions_checker_constructor(
261            matching_ppts, pptmap, name, typesAndParameters, false /* check minor properties*/));
262    checkerClasses.addDeclaration(
263        ctor,
264        checkPreconditions_checker_constructor(
265            matching_ppts, pptmap, name, typesAndParameters, true /* check major properties*/));
266
267    checkerClasses.addDeclaration(
268        ctor,
269        checkPostconditions_checker_constructor(
270            matching_ppts, pptmap, name, typesAndParameters, false /* check minor properties*/));
271
272    checkerClasses.addDeclaration(
273        ctor,
274        checkPostconditions_checker_constructor(
275            matching_ppts, pptmap, name, typesAndParameters, true /* check major properties*/));
276
277    // Check preconditions.
278    checkPreconditions(code, matching_ppts, pptmap);
279
280    // Call original constructor code, wrapped in a try clause so that
281    // we can evaluate object/class invariants before exiting, even
282    // if an exception is thrown.
283    code.append("boolean methodThrewSomething_instrument = false;");
284
285    // code.append("try {");
286
287    // Insert original constructor code.
288    // [[ TODO: should I use the daikon dumper that Mike found? ]]
289    StringWriter stringWriter = new StringWriter();
290    TreeDumper dumper = new TreeDumper(stringWriter);
291    dumper.visit(ctorBody);
292    code.append(stringWriter.toString());
293
294    // Handle any exceptions, check postconditions, object and class
295    // invariants.
296    exitChecks(
297        code,
298        matching_ppts,
299        pptmap,
300        // declaredThrowables,
301        false);
302
303    code.append("}");
304
305    // Replace constructor body with instrumented code.
306    BlockStatement newCtorBody = (BlockStatement) Ast.create("BlockStatement", code.toString());
307    // newCtorBody.accept(new TreeFormatter(2, 0));
308    ctor.f6 = new NodeListOptional(newCtorBody);
309  }
310
311  // Methods that we have created
312  private final Set<MethodDeclaration> generated_methods = new HashSet<>();
313
314  /** */
315  // MethodDeclaration:
316  // f0 -> [ TypeParameters() ]
317  // f1 -> ResultType()
318  // f2 -> MethodDeclarator()
319  // f3 -> [ "throws" NameList() ]
320  // f4 -> ( Block() | ";" )
321  @Override
322  public void visit(MethodDeclaration method) {
323
324    // Fix any line/col inconsistencies first
325    method.accept(new TreeFormatter());
326
327    visitedMethods.add(Ast.getMethod(method));
328
329    super.visit(method);
330
331    @SuppressWarnings(
332        "nullness") // application invariant: method node is always in a class or interface
333    @NonNull ClassOrInterfaceDeclaration clsdecl =
334        (ClassOrInterfaceDeclaration) Ast.getParent(ClassOrInterfaceDeclaration.class, method);
335
336    // skip anonymous nested classes for now, hard to refer to the right this object there...
337    if (Ast.isInterface(clsdecl) || Ast.isInAnonymousClass(method)) {
338      return;
339    }
340
341    // skip abstract methods
342    if (Ast.modifierPresent(Ast.getModifiers(method), "abstract")) {
343      return;
344    }
345
346    method.accept(new TreeFormatter());
347
348    //         System.out.println("@@@0");
349    //         method.accept(new TreeDumper());
350    //         System.out.println("@@@1");
351
352    if (generated_methods.contains(method)) {
353      return;
354    }
355
356    // Determine if method is static.
357    boolean isStatic = Ast.isStatic(method);
358
359    // Find declared throwables.
360    // List<String> declaredThrowables = getDeclaredThrowables(method.f3);
361
362    List<PptTopLevel> matching_ppts = pptMatcher.getMatches(pptmap, method);
363
364    String name = Ast.getName(method);
365    String returnType = Ast.getReturnType(method);
366    List<String> typesAndParameters = new ArrayList<>();
367    List<String> parameters = new ArrayList<>();
368    for (FormalParameter param : Ast.getParameters(method)) {
369      parameters.add(Ast.getName(param));
370      typesAndParameters.add(Ast.format(param));
371    }
372
373    InstrumentHandler.debug.fine("Method: " + name);
374
375    StringBuilder code = new StringBuilder();
376
377    code.append("{");
378
379    // Count this program point entry.
380    code.append("daikon.tools.runtimechecker.Runtime.numPptEntries++;");
381
382    // Check object invariants.
383    if (!isStatic) {
384      code.append(
385          "checkObjectInvariants_instrument(daikon.tools.runtimechecker.Violation.Time.onEntry);");
386    }
387
388    // Check class invariants.
389    code.append(
390        "checkClassInvariantsInstrument(daikon.tools.runtimechecker.Violation.Time.onEntry);");
391
392    checkerClasses.addDeclaration(
393        method,
394        checkPreconditions_checker_method(
395            matching_ppts, pptmap, name, typesAndParameters, false /* check minor properties */));
396    checkerClasses.addDeclaration(
397        method,
398        checkPreconditions_checker_method(
399            matching_ppts, pptmap, name, typesAndParameters, true /* check major properties */));
400
401    checkerClasses.addDeclaration(
402        method,
403        checkPostconditions_checker_method(
404            matching_ppts,
405            pptmap,
406            name,
407            returnType,
408            typesAndParameters,
409            false /* check minor properties */));
410
411    checkerClasses.addDeclaration(
412        method,
413        checkPostconditions_checker_method(
414            matching_ppts,
415            pptmap,
416            name,
417            returnType,
418            typesAndParameters,
419            true /* check major properties */));
420
421    checkPreconditions(code, matching_ppts, pptmap);
422
423    // Call original method, wrapped in a try clause so that we
424    // can evaluate object/class invariants before exiting.
425    code.append("boolean methodThrewSomething_instrument = false;");
426    if (!returnType.equals("void")) {
427      code.append(returnType + " retval_instrument = ");
428
429      // Assign some initial value to retval_instrument, otherwise
430      // compiler issues a "might not have been initialized" error.
431      if (returnType.equals("boolean")) {
432        code.append("false");
433      } else if (returnType.equals("char")) {
434        code.append("'a'");
435      } else if (returnType.equals("byte")
436          || returnType.equals("double")
437          || returnType.equals("float")
438          || returnType.equals("int")
439          || returnType.equals("long")
440          || returnType.equals("short")) {
441        code.append("0");
442      } else {
443        code.append("null");
444      }
445
446      code.append(";");
447    }
448
449    // code.append("try {");
450
451    if (!returnType.equals("void")) {
452      code.append("retval_instrument = ");
453    }
454
455    code.append("internal$" + name + "(" + String.join(", ", parameters) + ");");
456
457    exitChecks(
458        code,
459        matching_ppts,
460        pptmap,
461        // declaredThrowables,
462        isStatic);
463
464    // Return value.
465    if (!returnType.equals("void")) {
466      code.append("return retval_instrument;");
467    }
468
469    code.append("}");
470
471    // Add method to AST.
472    String new_method = code.toString();
473
474    MethodDeclaration wrapper =
475        (MethodDeclaration)
476            Ast.create(
477                "MethodDeclaration",
478                new Class[] {Integer.TYPE},
479                new Object[] {0}, // modifiers = 0
480                Ast.format(method));
481
482    Block block = (Block) Ast.create("Block", new_method);
483
484    wrapper.f4.choice = block;
485    wrapper.accept(new TreeFormatter());
486
487    Modifiers modifiersOrig = Ast.getModifiers(method);
488    Modifiers modifiers = (Modifiers) Ast.create("Modifiers", Ast.format(modifiersOrig));
489    modifiers.accept(new TreeFormatter());
490
491    @SuppressWarnings(
492        "nullness") // application invariant: method node is always in a class or interface
493    @NonNull ClassOrInterfaceBody c =
494        (ClassOrInterfaceBody) Ast.getParent(ClassOrInterfaceBody.class, method);
495
496    StringBuilder modifiers_declaration_stringbuffer = new StringBuilder();
497    modifiers_declaration_stringbuffer.append(Ast.format(modifiers));
498    modifiers_declaration_stringbuffer.append(" ");
499    modifiers_declaration_stringbuffer.append(Ast.format(wrapper));
500
501    ClassOrInterfaceBodyDeclaration d =
502        (ClassOrInterfaceBodyDeclaration)
503            Ast.create(
504                "ClassOrInterfaceBodyDeclaration",
505                new Class[] {Boolean.TYPE},
506                new Object[] {Boolean.FALSE}, // isInterface == false
507                modifiers_declaration_stringbuffer.toString());
508    Ast.addDeclaration(c, d);
509    NodeSequence ns = (NodeSequence) d.f0.choice;
510    NodeChoice nc = (NodeChoice) ns.elementAt(1);
511    MethodDeclaration generated_method = (MethodDeclaration) nc.choice;
512    generated_methods.add(generated_method);
513
514    // Rename the original method, and make it private.
515    Ast.setName(method, "internal$" + name);
516    Ast.setAccess(method, "private");
517    Ast.removeMethodDeclAnnotations(method);
518  }
519
520  // vioTime can be the name of a variable (which should be in scope)
521  // or a string, but if it's a string, then you should write it as
522  // something like: \"<ENTER>\"
523  private void appendInvariantChecks(List<Invariant> invs, StringBuilder code, String vioTime) {
524    for (Invariant inv : invs) {
525
526      InstrumentHandler.debug.fine("inv type: " + inv.getClass().getName());
527
528      String javarep = inv.format_using(OutputFormat.JAVA);
529
530      InstrumentHandler.debug.fine("javarep: " + javarep);
531
532      Property property = toProperty(inv);
533
534      String xmlString = property.xmlString();
535
536      InstrumentHandler.debug.fine("xmlString: " + xmlString);
537
538      // [[ TODO : explain this. ]]
539      // [[ TODO : move this to filterInvariants method. ]]
540      if (xmlString.indexOf("orig(") != -1) {
541        continue;
542      }
543
544      if (javarep.indexOf("unimplemented") != -1) {
545        // [[ TODO: print a warning that some invariants were
546        // unimplemented. ]]
547        continue;
548      }
549
550      if (javarep.indexOf("\\result") != -1) {
551        javarep = javarep.replaceAll("\\\\result", "retval_instrument");
552      }
553
554      InstrumentHandler.debug.fine("xmlStringToIndex: " + xmlStringToIndex.get(xmlString));
555
556      String addViolationToListCode =
557          "daikon.tools.runtimechecker.Runtime.violationsAdd"
558              + "(daikon.tools.runtimechecker.Violation.get(daikonProperties["
559              + xmlStringToIndex.get(xmlString)
560              + "], "
561              + vioTime
562              + "));";
563
564      code.append("try {" + daikon.Global.lineSep + "");
565      code.append("daikon.tools.runtimechecker.Runtime.numEvaluations++;");
566      code.append("if (!(" + daikon.Global.lineSep + "");
567      code.append(javarep);
568      code.append(")) {");
569      code.append(addViolationToListCode);
570      code.append("}");
571      code.append("} catch (ThreadDeath t_instrument) {" + daikon.Global.lineSep + "");
572      code.append("throw t_instrument;");
573      code.append("} catch (Throwable t_instrument) {" + daikon.Global.lineSep + "");
574      // don't catch anything. The assumption is that invariant-checking code
575      // never leads to an exception.
576      code.append("}");
577      //             code.append(addViolationToListCode);
578      //             code.append("} catch (Error t_instrument) {" + daikon.Global.lineSep + "");
579      //             code.append(addViolationToListCode);
580      //             code.append("}" + daikon.Global.lineSep + "");
581    }
582  }
583
584  private void appendInvariantChecks_checker(List<InvProp> ips, StringBuilder code) {
585
586    for (InvProp ip : ips) {
587
588      Invariant inv = ip.invariant;
589      Property property = ip.property;
590
591      assert property.xmlString().equals(toProperty(inv).xmlString());
592
593      String javarep = inv.format_using(OutputFormat.JAVA);
594
595      String daikonrep = inv.format_using(OutputFormat.DAIKON);
596
597      String xmlString = property.xmlString();
598
599      // [[ TODO : explain this. ]]
600      // [[ TODO : move this to filterInvariants method. ]]
601      if (xmlString.indexOf("orig(") != -1) {
602        continue;
603      }
604
605      if (javarep.indexOf("unimplemented") != -1) {
606        // [[ TODO: print a warning that some invariants were
607        // unimplemented. ]]
608        continue;
609      }
610
611      if (javarep.indexOf("\\result") != -1) {
612        javarep = javarep.replaceAll("\\\\result", "checker_returnval");
613      }
614
615      code.append("// Check: " + daikonrep + daikon.Global.lineSep);
616      code.append("assert ");
617      code.append(fixForExternalUse(javarep));
618      code.append(";");
619    }
620  }
621
622  private static String fixForExternalUse(String inv) {
623    inv = inv.replace("this", "thiz");
624    return inv;
625  }
626
627  private ClassOrInterfaceBodyDeclaration isInstrumentedDecl() {
628    return (ClassOrInterfaceBodyDeclaration)
629        Ast.create(
630            "ClassOrInterfaceBodyDeclaration",
631            new Class[] {Boolean.TYPE},
632            new Object[] {Boolean.FALSE}, // isInterface == false
633            "public static boolean isDaikonInstrumented() { return true; }");
634  }
635
636  private ClassOrInterfaceBodyDeclaration getInvariantsDecl() {
637    StringBuilder code = new StringBuilder();
638    code.append("public static java.util.Set getDaikonInvariants() {");
639    code.append("  return new java.util.HashSet(java.util.Arrays.asList(daikonProperties));");
640    code.append("}");
641    return (ClassOrInterfaceBodyDeclaration)
642        Ast.create(
643            "ClassOrInterfaceBodyDeclaration",
644            new Class[] {Boolean.TYPE},
645            new Object[] {Boolean.FALSE}, // isInterface == false
646            code.toString());
647  }
648
649  private ClassOrInterfaceBodyDeclaration staticPropertyDecl() {
650    StringBuilder code = new StringBuilder();
651
652    code.append("private static daikon.tools.runtimechecker.Property[] daikonProperties;");
653    return (ClassOrInterfaceBodyDeclaration)
654        Ast.create(
655            "ClassOrInterfaceBodyDeclaration",
656            new Class[] {Boolean.TYPE},
657            new Object[] {Boolean.FALSE}, // isInterface == false
658            code.toString());
659  }
660
661  /**
662   * Returns an AST for initializng the {@code daikonProperties} variable.
663   *
664   * @return an AST for initializng the {@code daikonProperties} variable
665   */
666  private ClassOrInterfaceBodyDeclaration staticPropertyInit() {
667    StringJoiner code = new StringJoiner(System.lineSeparator());
668
669    code.add("static {");
670    code.add("try {");
671    code.add("daikonProperties = new daikon.tools.runtimechecker.Property[" + varNumCounter + "];");
672
673    for (Map.Entry<@KeyFor("xmlStringToIndex") String, String> e : xmlStringToIndex.entrySet()) {
674      code.add("daikonProperties[" + e.getValue() + "] = ");
675      code.add("    daikon.tools.runtimechecker.Property.get(\"" + e.getKey() + "\");");
676    }
677
678    code.add("} catch (Exception e) {");
679    code.add(
680        " System.err.println(\"malformed invariant. This is probably a bug in the"
681            + " daikon.tools.runtimechecker tool; please submit a bug report.\");");
682    code.add(" e.printStackTrace();");
683    code.add(" System.exit(1);");
684    code.add("}");
685
686    code.add("} // end static");
687
688    return (ClassOrInterfaceBodyDeclaration)
689        Ast.create(
690            "ClassOrInterfaceBodyDeclaration",
691            new Class[] {Boolean.TYPE},
692            new Object[] {Boolean.FALSE}, // isInterface == false
693            code.toString());
694  }
695
696  private ClassOrInterfaceBodyDeclaration checkObjectInvariants_instrumentDeclaration(
697      String classname) {
698
699    StringBuilder code = new StringBuilder();
700    code.append(
701        "private void checkObjectInvariants_instrument"
702            + "(daikon.tools.runtimechecker.Violation.Time time) {");
703    String objectPptname = classname + ":::OBJECT";
704    PptTopLevel objectPpt = pptmap.get(objectPptname);
705    if (objectPpt != null) {
706      List<Invariant> objectInvariants = filterInvariants(Ast.getInvariants(objectPpt, pptmap));
707      appendInvariantChecks(objectInvariants, code, "time");
708    }
709    code.append("}" + daikon.Global.lineSep + "");
710    return (ClassOrInterfaceBodyDeclaration)
711        Ast.create(
712            "ClassOrInterfaceBodyDeclaration",
713            new Class[] {Boolean.TYPE},
714            new Object[] {Boolean.FALSE}, // isInterface == false
715            code.toString());
716  }
717
718  private ClassOrInterfaceBodyDeclaration checkClassInvariantsInstrumentDeclaration(
719      String classname) {
720    StringBuilder code = new StringBuilder();
721    code.append(
722        "private static void checkClassInvariantsInstrument"
723            + "(daikon.tools.runtimechecker.Violation.Time time) {");
724    String classPptname = classname + ":::CLASS";
725    PptTopLevel classPpt = pptmap.get(classPptname);
726    if (classPpt != null) {
727      List<Invariant> classInvariants = filterInvariants(Ast.getInvariants(classPpt, pptmap));
728      appendInvariantChecks(classInvariants, code, "time");
729    }
730    code.append("}" + daikon.Global.lineSep + "");
731    return (ClassOrInterfaceBodyDeclaration)
732        Ast.create(
733            "ClassOrInterfaceBodyDeclaration",
734            new Class[] {Boolean.TYPE},
735            new Object[] {Boolean.FALSE}, // isInterface == false
736            code.toString());
737  }
738
739  private StringBuilder checkObjectInvariants_instrumentDeclaration_checker(
740      String classname, boolean majorProperties) {
741    StringBuilder code = new StringBuilder();
742    code.append(
743        "public static void check"
744            + (majorProperties ? "Major" : "Minor")
745            + "ObjectInvariants(Object thiz) {");
746    String objectPptname = classname + ":::OBJECT";
747    PptTopLevel objectPpt = pptmap.get(objectPptname);
748    if (objectPpt != null) {
749      List<Invariant> objectInvariants = filterInvariants(Ast.getInvariants(objectPpt, pptmap));
750      List<InvProp> finalList;
751      if (majorProperties) {
752        finalList = getMajor(objectInvariants);
753      } else {
754        finalList = getMinor(objectInvariants);
755      }
756      appendInvariantChecks_checker(finalList, code);
757    }
758    code.append("}" + daikon.Global.lineSep);
759    return code;
760  }
761
762  private StringBuilder checkClassInvariantsInstrumentDeclaration_checker(
763      String classname, boolean majorProperties) {
764    StringBuilder code = new StringBuilder();
765    code.append(
766        "public static void check" + (majorProperties ? "Major" : "Minor") + "ClassInvariants() {");
767    String classPptname = classname + ":::CLASS";
768    PptTopLevel classPpt = pptmap.get(classPptname);
769    if (classPpt != null) {
770      List<Invariant> classInvariants = filterInvariants(Ast.getInvariants(classPpt, pptmap));
771      List<InvProp> finalList;
772      if (majorProperties) {
773        finalList = getMajor(classInvariants);
774      } else {
775        finalList = getMinor(classInvariants);
776      }
777      appendInvariantChecks_checker(finalList, code);
778    }
779    code.append("}" + daikon.Global.lineSep + "");
780    return code;
781  }
782
783  /**
784   * Return a subset of the argument list, removing invariants that do not have a properly
785   * implemented Java format.
786   */
787  private static List<Invariant> filterInvariants(List<Invariant> invariants) {
788    List<Invariant> survivors = new ArrayList<>();
789    for (Invariant inv : invariants) {
790
791      if (!inv.isValidExpression(OutputFormat.JAVA)) {
792        continue;
793      }
794      // Left and right shifts are formatted properly by Daikon,
795      // but as of 2/6/2005, a JTB bug prevents them from being
796      // inserted into code.
797      if (inv instanceof FunctionBinary) {
798        FunctionBinary fb = (FunctionBinary) inv;
799        if (fb.isLshift() || fb.isRshiftSigned() || fb.isRshiftUnsigned()) {
800          // System.err.println("Warning: shift operation skipped: " +
801          // inv.format_using(OutputFormat.JAVA));
802          continue;
803        }
804      }
805
806      if (outputOnlyHighConfInvariants) {
807        if (toProperty(inv).calculateConfidence() < 0.5) {
808          continue;
809        }
810      }
811
812      survivors.add(inv);
813    }
814    return survivors;
815  }
816
817  // private static List<String> getDeclaredThrowables(NodeOptional nodeOpt) {
818  //   List<String> declaredThrowables = new ArrayList<>();
819  //   if (nodeOpt.present()) {
820  //     NodeSequence seq = (NodeSequence) nodeOpt.node;
821  //     // There should only be two elements: "throws" and NameList
822  //     assert seq.size() == 2;
823  //     NameList nameList = (NameList) seq.elementAt(1);
824  //
825  //     StringWriter stringWriter = new StringWriter();
826  //     TreeDumper dumper = new TreeDumper(stringWriter);
827  //     dumper.visit(nameList);
828  //
829  //     String[] declaredThrowablesArray = stringWriter.toString().trim().split(",");
830  //     for (int i = 0; i < declaredThrowablesArray.length; i++) {
831  //       declaredThrowables.add(declaredThrowablesArray[i].trim());
832  //     }
833  //     //       System.out.println("@@@");
834  //     //       for (int i = 0 ; i < declaredThrowables.size() ; i++) {
835  //     //         System.out.println("xxx" + declaredThrowables.get(i) + "xxx");
836  //     //       }
837  //   }
838  //   return declaredThrowables;
839  // }
840
841  // [[ TODO: This method can return the wrong sequence of catch clauses,
842  //    because it has no concept of throwable subclasses, and it just
843  //    orders catch clauses in the order in which they appear in
844  //    declaredThrowable. This can cause compilation to fail. ]]
845  private void exitChecks(
846      StringBuilder code,
847      List<PptTopLevel> matching_ppts,
848      PptMap pptmap,
849      // List<String> declaredThrowables,
850      boolean isStatic) {
851
852    //      List<String> declaredThrowablesLocal = new ArrayList<>(declaredThrowables);
853    //      declaredThrowablesLocal.remove("java.lang.RuntimeException");
854    //      declaredThrowablesLocal.remove("RuntimeException");
855    //      declaredThrowablesLocal.remove("java.lang.Error");
856    //      declaredThrowablesLocal.remove("Error");
857
858    // Count this program point exit.
859    code.append("daikon.tools.runtimechecker.Runtime.numNormalPptExits++;");
860
861    //         // [[ TODO: Figure out what could go wrong here (e.g. what if
862    //         // method declaration says "throws Throwable") and prepare for
863    //         // it. ]]
864    //         for (String declaredThrowable : declaredThrowablesLocal) {
865    //             code.append("} catch (" + declaredThrowable + " t_instrument) {");
866    //             // Count this program point exit.
867    //             code.append("daikon.tools.runtimechecker.Runtime.numExceptionalPptExits++;");
868    //             code.append("  methodThrewSomething_instrument = true;");
869    //             code.append("  throw t_instrument;");
870    //         }
871
872    //      code.append("} catch (java.lang.RuntimeException t_instrument) {");
873    //      code.append("  methodThrewSomething_instrument = true;");
874    //         // Count this program point exit.
875    //         code.append("daikon.tools.runtimechecker.Runtime.numExceptionalPptExits++;");
876    //      code.append("  throw t_instrument;");
877    //      code.append("} catch (java.lang.Error t_instrument) {");
878    //         // Count this program point exit.
879    //         code.append("daikon.tools.runtimechecker.Runtime.numExceptionalPptExits++;");
880    //      code.append("  methodThrewSomething_instrument = true;");
881    //      code.append("  throw t_instrument;");
882
883    //         code.append("} finally {");
884
885    // If method didn't throw an exception, it completed
886    // normally--check method postconditions (If the method didn't
887    // complete normally, it makes no sense to check postconditions,
888    // because Daikon only reports normal-exit postconditions.)
889
890    //         code.append(" if (!methodThrewSomething_instrument) {");
891
892    // Check postconditions.
893    for (PptTopLevel ppt : matching_ppts) {
894      if (ppt.ppt_name.isExitPoint() && ppt.ppt_name.isCombinedExitPoint()) {
895        List<Invariant> postconditions = filterInvariants(Ast.getInvariants(ppt, pptmap));
896        appendInvariantChecks(
897            postconditions, code, "daikon.tools.runtimechecker.Violation.Time.onExit");
898      }
899    }
900    //         code.append("}");
901
902    // Check object invariants.
903    if (!isStatic) {
904      code.append(
905          "checkObjectInvariants_instrument(daikon.tools.runtimechecker.Violation.Time.onExit);");
906    }
907    // Check class invariants.
908    code.append(
909        "checkClassInvariantsInstrument(daikon.tools.runtimechecker.Violation.Time.onExit);");
910
911    //         code.append("}"); // this closes the finally clause
912  }
913
914  private void checkPreconditions(
915      StringBuilder code, List<PptTopLevel> matching_ppts, PptMap pptmap) {
916    for (PptTopLevel ppt : matching_ppts) {
917      if (ppt.ppt_name.isEnterPoint()) {
918        List<Invariant> preconditions = filterInvariants(Ast.getInvariants(ppt, pptmap));
919        appendInvariantChecks(
920            preconditions, code, "daikon.tools.runtimechecker.Violation.Time.onEntry");
921      }
922    }
923  }
924
925  private StringBuilder checkPreconditions_checker_method(
926      List<PptTopLevel> matching_ppts,
927      PptMap pptmap,
928      String methodName,
929      List<String> parameters,
930      boolean majorProperties) {
931
932    StringBuilder code = new StringBuilder();
933    code.append(
934        "public static void check"
935            + (majorProperties ? "Major" : "Minor")
936            + "Preconditions_"
937            + methodName
938            + "("
939            + "Object thiz"
940            + (parameters.size() > 0 ? ", " : "")
941            + String.join(", ", parameters)
942            + ") {");
943
944    for (PptTopLevel ppt : matching_ppts) {
945      if (ppt.ppt_name.isEnterPoint()) {
946        List<Invariant> preconditions = filterInvariants(Ast.getInvariants(ppt, pptmap));
947        List<InvProp> finalList;
948        if (majorProperties) {
949          finalList = getMajor(preconditions);
950        } else {
951          finalList = getMinor(preconditions);
952        }
953        appendInvariantChecks_checker(finalList, code);
954      }
955    }
956
957    code.append("}");
958    return code;
959  }
960
961  private StringBuilder checkPostconditions_checker_method(
962      List<PptTopLevel> matching_ppts,
963      PptMap pptmap,
964      String methodName,
965      String returnType,
966      List<String> parameters,
967      boolean majorProperties) {
968
969    StringBuilder code = new StringBuilder();
970    code.append(
971        "public static void check"
972            + (majorProperties ? "Major" : "Minor")
973            + "Postconditions_"
974            + methodName
975            + "("
976            + "Object thiz "
977            + (returnType.equals("void") ? "" : ", " + returnType + " checker_returnval")
978            + (parameters.size() > 0 ? ", " : "")
979            + String.join(", ", parameters)
980            + ") {");
981
982    for (PptTopLevel ppt : matching_ppts) {
983      if (ppt.ppt_name.isExitPoint() && ppt.ppt_name.isCombinedExitPoint()) {
984        List<Invariant> postconditions = filterInvariants(Ast.getInvariants(ppt, pptmap));
985        List<InvProp> finalList;
986        if (majorProperties) {
987          finalList = getMajor(postconditions);
988        } else {
989          finalList = getMinor(postconditions);
990        }
991        appendInvariantChecks_checker(finalList, code);
992      }
993    }
994
995    code.append("}");
996    return code;
997  }
998
999  private StringBuilder checkPreconditions_checker_constructor(
1000      List<PptTopLevel> matching_ppts,
1001      PptMap pptmap,
1002      String methodName,
1003      List<String> parameters,
1004      boolean majorProperties) {
1005
1006    StringBuilder code = new StringBuilder();
1007    code.append(
1008        "public static void check"
1009            + (majorProperties ? "Major" : "Minor")
1010            + "Preconditions_"
1011            + methodName
1012            + "("
1013            + String.join(", ", parameters)
1014            + ") {");
1015
1016    for (PptTopLevel ppt : matching_ppts) {
1017      if (ppt.ppt_name.isEnterPoint()) {
1018        List<Invariant> preconditions = filterInvariants(Ast.getInvariants(ppt, pptmap));
1019        List<InvProp> finalList;
1020        if (majorProperties) {
1021          finalList = getMajor(preconditions);
1022        } else {
1023          finalList = getMinor(preconditions);
1024        }
1025        appendInvariantChecks_checker(finalList, code);
1026      }
1027    }
1028
1029    code.append("}");
1030    return code;
1031  }
1032
1033  private StringBuilder checkPostconditions_checker_constructor(
1034      List<PptTopLevel> matching_ppts,
1035      PptMap pptmap,
1036      String methodName,
1037      List<String> parameters,
1038      boolean majorProperties) {
1039
1040    StringBuilder code = new StringBuilder();
1041    code.append(
1042        "public static void check"
1043            + (majorProperties ? "Major" : "Minor")
1044            + "Postconditions_"
1045            + methodName
1046            + "("
1047            + "Object thiz "
1048            + (parameters.size() > 0 ? ", " : "")
1049            + String.join(", ", parameters)
1050            + ") {");
1051
1052    for (PptTopLevel ppt : matching_ppts) {
1053      if (ppt.ppt_name.isExitPoint() && ppt.ppt_name.isCombinedExitPoint()) {
1054        List<Invariant> postconditions = filterInvariants(Ast.getInvariants(ppt, pptmap));
1055        List<InvProp> finalList;
1056        if (majorProperties) {
1057          finalList = getMajor(postconditions);
1058        } else {
1059          finalList = getMinor(postconditions);
1060        }
1061        appendInvariantChecks_checker(finalList, code);
1062      }
1063    }
1064
1065    code.append("}");
1066    return code;
1067  }
1068
1069  private static Property toProperty(Invariant inv) {
1070    StringBuilder code = new StringBuilder();
1071
1072    String daikonrep = inv.format_using(OutputFormat.DAIKON);
1073
1074    String javarep = inv.format_using(OutputFormat.JAVA);
1075
1076    if (daikonrep.indexOf("\"") != -1 || daikonrep.indexOf("\\") != -1) {
1077      // Now comes some real ugliness: [[ ... ]] It's easier to do
1078      // this transformation on a character list than by pattern
1079      // matching against a String.
1080      char[] chars = daikonrep.toCharArray();
1081      List<Character> charList = new ArrayList<>();
1082      for (int j = 0; j < chars.length; j++) {
1083        char c = chars[j];
1084        if ((c == '\"') || (c == '\\')) {
1085          charList.add('\\');
1086        }
1087        charList.add(c);
1088      }
1089      char[] backslashedChars = new char[charList.size()];
1090      for (int j = 0; j < charList.size(); j++) {
1091        backslashedChars[j] = charList.get(j).charValue();
1092      }
1093      daikonrep = new String(backslashedChars);
1094    }
1095
1096    code.append("<INVINFO>");
1097    code.append("<" + inv.ppt.parent.ppt_name.getPoint() + ">");
1098    code.append("<DAIKON>" + daikonrep + "</DAIKON>");
1099    code.append("<INV>" + StringsPlume.escapeJava(javarep) + "</INV>");
1100    code.append("<DAIKONCLASS>" + inv.getClass().toString() + "</DAIKONCLASS>");
1101    code.append("<METHOD>" + inv.ppt.parent.ppt_name.getSignature() + "</METHOD>");
1102    code.append("</INVINFO>");
1103
1104    try {
1105      return Property.get(code.toString());
1106    } catch (MalformedPropertyException e) {
1107      throw new Error(e);
1108    }
1109  }
1110
1111  /** A pair consisting of an Invariant and its corresponding Property. */
1112  private static class InvProp {
1113    public InvProp(Invariant inv, Property p) {
1114      this.invariant = inv;
1115      this.property = p;
1116    }
1117
1118    public Invariant invariant;
1119    public Property property;
1120  }
1121
1122  /**
1123   * Returns the invariants (and their properties) that have confidence values at or above
1124   * confidenceThreshold.
1125   */
1126  List<InvProp> getMajor(List<Invariant> invs) {
1127
1128    List<InvProp> ret = new ArrayList<>();
1129    for (Invariant i : invs) {
1130      Property p = toProperty(i);
1131      if (p.confidence >= confidenceThreshold) {
1132        ret.add(new InvProp(i, p));
1133      }
1134    }
1135    return ret;
1136  }
1137
1138  /**
1139   * Returns the invariants (and their properties) that have confidence values below
1140   * confidenceThreshold.
1141   */
1142  List<InvProp> getMinor(List<Invariant> invs) {
1143
1144    List<InvProp> ret = new ArrayList<>();
1145    for (Invariant i : invs) {
1146      Property p = toProperty(i);
1147      if (p.confidence < confidenceThreshold) {
1148        ret.add(new InvProp(i, p));
1149      }
1150    }
1151    return ret;
1152  }
1153
1154  /**
1155   * Add checker methods with empty bodies for all public methods and constuctors not explicitly
1156   * declared.
1157   */
1158  public void add_checkers_for_nondeclared_members() {
1159
1160    for (CheckerClass cc : checkerClasses.classes) {
1161
1162      Class<?> c = Ast.getClass(cc.fclassbody);
1163
1164      // Check that all declared methods were in fact visited.
1165      for (Method m : c.getDeclaredMethods()) {
1166        if (!visitedMethods.contains(m)) {
1167          throw new Error("m=" + m + ", visitedMethods=" + visitedMethods);
1168        }
1169      }
1170
1171      // FIXME consider implicit constructors when doing check.
1172      // Check that all declared constructors were in fact visited.
1173      //             for (Constructor cons : c.getDeclaredConstructors()) {
1174      //                 if (!visitedConstructors.contains(cons)) {
1175      //                     assert cons.equals(getDefaultConstructor(c))
1176      //                        : "cons=" + cons + ", visitedConstructors=" + visitedConstructors);
1177      //                 }
1178      //             }
1179
1180      for (Method m : c.getMethods()) {
1181        if (!visitedMethods.contains(m)) {
1182          cc.addDeclaration(createEmptyDeclaration(m));
1183        }
1184      }
1185
1186      for (Constructor<?> cons : c.getConstructors()) {
1187        if (!visitedConstructors.contains(cons)) {
1188          cc.addDeclaration(createEmptyDeclaration(cons));
1189        }
1190      }
1191    }
1192  }
1193
1194  private StringBuilder createEmptyDeclaration(Method m) {
1195
1196    List<String> parameters = new ArrayList<>();
1197    int paramCounter = 0;
1198    for (Class<?> c : m.getParameterTypes()) {
1199      parameters.add(Ast.classnameForSourceOutput(c) + " param" + paramCounter++);
1200    }
1201
1202    StringBuilder code = new StringBuilder();
1203
1204    for (String s : new String[] {"Major", "Minor"}) {
1205      code.append(
1206          "public static void check"
1207              + s
1208              + "Preconditions_"
1209              + m.getName()
1210              + "("
1211              + "Object thiz"
1212              + (parameters.size() > 0 ? ", " : "")
1213              + String.join(", ", parameters)
1214              + ") { /* no properties for this member */ }");
1215
1216      code.append(
1217          "public static void check"
1218              + s
1219              + "Postconditions_"
1220              + m.getName()
1221              + "("
1222              + "Object thiz "
1223              + (m.getReturnType().equals(Void.TYPE)
1224                  ? ""
1225                  : (", " + Ast.classnameForSourceOutput(m.getReturnType()) + " checker_returnval"))
1226              + (parameters.size() > 0 ? ", " : "")
1227              + String.join(", ", parameters)
1228              + ") { /* no properties for this member */ }");
1229    }
1230
1231    return code;
1232  }
1233
1234  private StringBuilder createEmptyDeclaration(Constructor<?> c) {
1235
1236    List<String> parameters = new ArrayList<>();
1237    int paramCounter = 0;
1238    for (Class<?> cls : c.getParameterTypes()) {
1239      parameters.add(Ast.classnameForSourceOutput(cls) + " param" + paramCounter++);
1240    }
1241
1242    StringBuilder code = new StringBuilder();
1243
1244    Package pacg = c.getDeclaringClass().getPackage();
1245    String packageName = (pacg == null ? "" : pacg.getName());
1246    int packageNameLength =
1247        (packageName.equals("") ? 0 : packageName.length() + 1 /* account for ending dot */);
1248    String className = c.getDeclaringClass().getName();
1249    assert className.startsWith(packageName);
1250    String baseClassName = className.substring(packageNameLength);
1251
1252    for (String s : new String[] {"Major", "Minor"}) {
1253
1254      code.append(
1255          "public static void check"
1256              + s
1257              + "Preconditions_"
1258              + baseClassName
1259              + "("
1260              + String.join(", ", parameters)
1261              + ") { /* no properties for this member */ }");
1262
1263      code.append(
1264          "public static void check"
1265              + s
1266              + "Postconditions_"
1267              + baseClassName
1268              + "("
1269              + "Object thiz "
1270              + (parameters.size() > 0 ? ", " : "")
1271              + String.join(", ", parameters)
1272              + ") { /* no properties for this member */ }");
1273    }
1274
1275    return code;
1276  }
1277}