001package daikon.chicory;
002
003import daikon.Chicory;
004import daikon.FileIO;
005import daikon.PptTopLevel.PptType;
006import daikon.plumelib.bcelutil.SimpleLog;
007import java.io.PrintWriter;
008import java.lang.reflect.Member;
009import java.time.LocalDateTime;
010import java.time.ZoneId;
011import java.util.ArrayList;
012import java.util.EnumSet;
013import java.util.HashSet;
014import java.util.List;
015import java.util.Set;
016import java.util.regex.Pattern;
017import org.checkerframework.checker.lock.qual.GuardSatisfied;
018import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
019import org.checkerframework.checker.nullness.qual.Nullable;
020import org.checkerframework.dataflow.qual.Pure;
021import org.checkerframework.dataflow.qual.SideEffectFree;
022
023/**
024 * DeclWriter writes the {@code .decls} file to a stream. It uses traversal pattern trees (see
025 * {@link DaikonVariableInfo}) for each program point. These are also used by the {@link
026 * DTraceWriter}.
027 */
028@SuppressWarnings("nullness") // to do
029public class DeclWriter extends DaikonWriter implements ComparabilityProvider {
030  // Notes:
031  //
032  //  Class.getName() returns JVM names (eg, [Ljava.lang.String;)
033
034  /** Debug flag set from Chicory.debug_decl_print. */
035  public boolean debug = false;
036
037  // If the --comparability-file option is active, there might be
038  // variables for which DynComp saw no interactions and did not
039  // generate a comparability value. In order to reduce the number
040  // of useless invariants (when running Daikon on the output of
041  // Chicory), we generate a unique, dummy comparability value in
042  // these cases.  We start at the maximum integer and decrement
043  // for each use.  At the start of each method, object or class
044  // we reset the value back - though it's hard to imagine a case
045  // where there would be a collision with DynComp's values which
046  // increment up from an initial value of 1.
047  private static int initial_compare_value = Integer.MAX_VALUE;
048  private static int unique_compare_value;
049
050  /** Stream to write to. */
051  private PrintWriter outFile;
052
053  /**
054   * Enable parent relations other than methods to their class objects. Turned off for now to match
055   * previous behavior.
056   */
057  private static boolean enable_object_user = false;
058
059  /**
060   * Constructs a DeclWriter, preparing it to receive messages.
061   *
062   * @param writer stream to write to
063   */
064  public DeclWriter(PrintWriter writer) {
065    super();
066    outFile = writer;
067    debug = Chicory.debug_decl_print;
068  }
069
070  /**
071   * Prints header information to the decls file. Should be called once before emitting any other
072   * declarations.
073   *
074   * @param className name of the top-level class (used only for printing comments)
075   */
076  public void printHeaderInfo(String className) {
077    outFile.println("// Declarations for " + className);
078    outFile.println(
079        "// Declarations written by Chicory " + LocalDateTime.now(ZoneId.systemDefault()));
080    outFile.println();
081
082    // Determine comparability string
083    String comparability = "none";
084    if (Runtime.comp_info != null) {
085      comparability = "implicit";
086    }
087    outFile.printf("decl-version 2.0%n");
088    outFile.printf("var-comparability %s%n%n", comparability);
089  }
090
091  /**
092   * Returns the correctly formulated ":::OBJECT" name of the class (i.e., the program point name)
093   *
094   * @param type the ClassType type
095   * @return the correctly formulated String
096   */
097  public static String classObjectName(Class<?> type) {
098    return (type.getName() + ":::OBJECT");
099  }
100
101  /**
102   * Prints declarations for all the methods in the indicated class. This method is called at run
103   * time to print decls info for a class.
104   *
105   * @param cinfo class whose declarations should be printed
106   * @param comp_info comparability information
107   */
108  public void printDeclClass(ClassInfo cinfo, @Nullable DeclReader comp_info) {
109
110    if (debug) {
111      System.out.println("Enter printDeclClass: " + cinfo);
112    }
113
114    // Print all methods and constructors
115    for (MethodInfo mi : cinfo.get_method_infos()) {
116
117      Member member = mi.member;
118
119      // Don't want to instrument these types of methods
120      if (!shouldInstrumentMethod(member)) {
121        continue;
122      }
123
124      // Gset the root of the method's traversal pattern
125      RootInfo enterRoot = mi.traversalEnter;
126      assert enterRoot != null : "Traversal pattern not initialized at method " + mi.method_name;
127
128      String entryName =
129          (member != null
130              ? methodEntryName(member)
131              : mi.class_info.class_name + ".<clinit>" + FileIO.enter_tag);
132      print_method(mi, enterRoot, entryName, PptType.ENTER, comp_info);
133
134      // Print exit program point for EACH exit location in the method
135      // Note that there may not be any exits.  They may get filtered out,
136      // or some methods don't have an exit (only a throw)
137      Set<Integer> theExits = new HashSet<>(mi.exit_locations);
138      for (Integer exitLoc : theExits) {
139        // Get the root of the method's traversal pattern
140        RootInfo exitRoot = mi.traversalExit;
141        assert enterRoot != null : "Traversal pattern not initialized at method " + mi.method_name;
142
143        String exitName =
144            (member != null
145                ? methodExitName(member, exitLoc)
146                : mi.class_info.class_name + ".<clinit>" + FileIO.exit_tag + exitLoc);
147        print_method(mi, exitRoot, exitName, PptType.SUBEXIT, comp_info);
148      }
149    }
150
151    print_class_ppt(cinfo, cinfo.class_name + ":::CLASS", comp_info);
152    print_object_ppt(cinfo, classObjectName(cinfo.clazz), comp_info);
153
154    if (debug) {
155      System.out.println("Exit printDeclClass");
156    }
157  }
158
159  /**
160   * Prints a method's program point. This includes the ppt declaration, all of the ppt records, and
161   * records for each variable.
162   *
163   * <p>This method uses variable information from the traversal tree.
164   *
165   * @param mi the method information for the method
166   * @param root the root of the traversal tree
167   * @param name the program point name
168   * @param ppt_type the type of the program point (enter, exit, etc)
169   * @param comp_info comparability information
170   */
171  private void print_method(
172      MethodInfo mi, RootInfo root, String name, PptType ppt_type, @Nullable DeclReader comp_info) {
173
174    if (debug) {
175      System.out.println("Enter print_method: " + name);
176    }
177
178    // reset dummy comparability value
179    unique_compare_value = initial_compare_value;
180
181    outFile.println("ppt " + escape(name));
182
183    outFile.println("ppt-type " + ppt_type.name().toLowerCase());
184
185    // Look for and print any hierarchy relations
186    List<VarRelation> relations = new ArrayList<>();
187    for (DaikonVariableInfo child : root) {
188      find_relations(null, mi.is_static(), null, child, relations);
189    }
190    for (VarRelation relation : relations) {
191      outFile.println("parent parent " + relation.parent_ppt_name + " " + relation.id);
192    }
193
194    // Print each variable
195    for (DaikonVariableInfo childOfRoot : root) {
196      if (debug) {
197        System.out.println("method var: " + childOfRoot.getName());
198      }
199      traverse_decl(
200          null,
201          mi.is_static(),
202          null,
203          childOfRoot,
204          null,
205          relations,
206          ((comp_info == null) ? null : comp_info.find_ppt(name)));
207    }
208
209    outFile.println();
210
211    if (debug) {
212      System.out.println("Exit print_method ");
213    }
214  }
215
216  /**
217   * Prints the class program point. This contains only the static variables. If there are no static
218   * variables to print, this method does nothing.
219   *
220   * @param cinfo class whose static declarations should be printed
221   * @param name the program point name
222   * @param comp_info comparability information
223   */
224  private void print_class_ppt(ClassInfo cinfo, String name, DeclReader comp_info) {
225
226    if (debug) {
227      System.out.println("Enter print_class_ppt: " + cinfo);
228    }
229
230    // reset dummy comparability value
231    unique_compare_value = initial_compare_value;
232
233    if (num_class_vars(cinfo) > 0) {
234
235      outFile.println("ppt " + escape(name));
236      outFile.println("ppt-type class");
237
238      // Print out the static fields
239      for (DaikonVariableInfo childOfRoot : RootInfo.getClassPpt(cinfo, Runtime.nesting_depth)) {
240        if (debug) {
241          System.out.println("class var: " + childOfRoot.getName());
242        }
243        traverse_decl(
244            null,
245            false,
246            null,
247            childOfRoot,
248            null,
249            null,
250            ((comp_info == null) ? null : comp_info.find_ppt(name)));
251      }
252
253      outFile.println();
254    }
255
256    if (debug) {
257      System.out.println("Exit print_class_ppt");
258    }
259  }
260
261  /**
262   * Prints the object program point. This contains the "this" object and the class' fields.
263   *
264   * @param cinfo class whose object program point should be printed
265   * @param name the program point name
266   * @param comp_info comparability information
267   */
268  private void print_object_ppt(ClassInfo cinfo, String name, DeclReader comp_info) {
269
270    if (debug) {
271      System.out.println("Enter print_object_ppt: " + cinfo);
272    }
273
274    // reset dummy comparability value
275    unique_compare_value = initial_compare_value;
276
277    outFile.println("ppt " + escape(name));
278    outFile.println("ppt-type object");
279    RootInfo root = RootInfo.getObjectPpt(cinfo, Runtime.nesting_depth);
280
281    // If there are any static variables, add the relation to
282    // the class ppt
283    List<VarRelation> relations = new ArrayList<>();
284    if (num_class_vars(cinfo) > 0) {
285      VarRelation relation = new VarRelation(cinfo.class_name + ":::CLASS", "parent");
286      relation.id = 1;
287      relations.add(relation);
288    }
289
290    // Look for and print any object-user relations
291    for (DaikonVariableInfo child : root) {
292      find_relations(cinfo, false, null, child, relations);
293    }
294    for (VarRelation relation : relations) {
295      outFile.println(
296          "parent " + relation.type + " " + relation.parent_ppt_name + " " + relation.id);
297    }
298
299    // Write out the variables
300    for (DaikonVariableInfo childOfRoot : root) {
301      if (debug) {
302        System.out.println("object var: " + childOfRoot.getName());
303      }
304      traverse_decl(
305          cinfo,
306          false,
307          null,
308          childOfRoot,
309          null,
310          relations,
311          ((comp_info == null) ? null : comp_info.find_ppt(name)));
312    }
313
314    outFile.println();
315
316    if (debug) {
317      System.out.println("Exit print_object_ppt");
318    }
319  }
320
321  /**
322   * Object program points are constructed for invariants about an object. We define an object
323   * invariant as one that is true at the entrance and exit of each public method and also each time
324   * an instance of the object is available to another method (eg, when it is passed as a parameter
325   * or available as a static). Daikon implements object invariants by merging the invariants from
326   * each public method and each user of the object. We refer to the relationship between variables
327   * at these program points as a Program point / variable hierarchy. This relationship must be
328   * defined in the declaration record. The VarRelation class tracks one relation.
329   */
330  private static class VarRelation {
331    /** Name of the program point for the parent. */
332    String parent_ppt_name;
333
334    /** Prefix of the variable name that is not part of the parent name. */
335    String local_prefix;
336
337    /** Prefix of the parent that replaces the local prefix. Normally 'this'. */
338    String parent_prefix;
339
340    /** Top level variable for the relation. */
341    String local_variable;
342
343    /** Type of the relation (parent, user, etc) */
344    String type;
345
346    /** Number that identifies this relation within this ppt. */
347    int id;
348
349    static SimpleLog debug = new SimpleLog(false);
350
351    /** Create a VarRelation. */
352    public VarRelation(
353        String parent_ppt_name,
354        String type,
355        String local_prefix,
356        String parent_prefix,
357        String local_variable) {
358      this.parent_ppt_name = parent_ppt_name;
359      this.type = type;
360      this.local_prefix = local_prefix;
361      this.parent_prefix = parent_prefix;
362      this.local_variable = local_variable;
363      debug.log("Created %s", this);
364    }
365
366    /** Create a var relation with the matching names. */
367    public VarRelation(String parent_ppt_name, String type) {
368      this(parent_ppt_name, type, null, null, null);
369    }
370
371    @SideEffectFree
372    @Override
373    public String toString(@GuardSatisfied VarRelation this) {
374      return String.format(
375          "VarRelation %s (%s->%s) %s [%s]",
376          parent_ppt_name, local_prefix, parent_prefix, local_variable, type);
377    }
378
379    /**
380     * Returns whether or not this relation is from a static variable in an object ppt to its
381     * matching variable at the class level.
382     */
383    @Pure
384    public boolean is_class_relation() {
385      return parent_ppt_name.endsWith(":::CLASS");
386    }
387
388    /**
389     * Returns the string defining the relation for the specified variable The format is
390     * parent-ppt-name id parent-variable-name. If the variable is static, it always has the same
391     * name in the parent (since fully specified names are used for static variables).
392     */
393    public String relation_str(DaikonVariableInfo var) {
394      String out = parent_ppt_name + " " + id;
395      if (!var.isStatic() && (local_prefix != null) && !local_prefix.equals(parent_prefix)) {
396        out += " " + var.getName().replaceFirst(Pattern.quote(local_prefix), parent_prefix);
397      }
398      return out;
399    }
400
401    /** Two VarRelations are equal if the refer to the same program point and local variable. */
402    @Override
403    @EnsuresNonNullIf(result = true, expression = "#1")
404    @Pure
405    public boolean equals(@GuardSatisfied VarRelation this, @GuardSatisfied @Nullable Object o) {
406      if (!(o instanceof VarRelation) || (o == null)) {
407        return false;
408      }
409      VarRelation vr = (VarRelation) o;
410      return (vr.parent_ppt_name.equals(parent_ppt_name)
411          && (((vr.local_variable == null) && local_variable == null)
412              || ((vr.local_variable != null) && vr.local_variable.equals(local_variable))));
413    }
414
415    @Override
416    @Pure
417    public int hashCode(@GuardSatisfied VarRelation this) {
418      return (parent_ppt_name.hashCode()
419          + ((local_variable == null) ? 0 : local_variable.hashCode()));
420    }
421  }
422
423  /**
424   * Prints the .decls information for a single DaikonVariableInfo object, and recurses on its
425   * children. If the current variable has comparability defined in compare_ppt, that comparability
426   * is used. Otherwise -1 is used if there is comparability information available and the
427   * information in the variable is used if it is not.
428   */
429  private void traverse_decl(
430      ClassInfo cinfo,
431      boolean is_static_method,
432      DaikonVariableInfo parent,
433      DaikonVariableInfo var,
434      VarRelation relation,
435      List<VarRelation> relations,
436      DeclReader.DeclPpt compare_ppt) {
437
438    if (debug) {
439      System.out.println("Enter traverse_decl: " + cinfo + ", " + var + ", " + parent);
440    }
441
442    if (!var.declShouldPrint()) {
443      // don't do anything
444    } else if (!(var instanceof StaticObjInfo)) {
445
446      printDecl(parent, var, compare_ppt, Runtime.decl_writer);
447
448      // Determine if there is a ppt for variables of this type
449      // If found this should match one of the previously found relations
450      // for this ppt.
451      // Once a relation has been found, we don't look for recursive
452      // relationships
453      if ((relation == null) && (relations != null)) {
454        relation = find_relation(cinfo, is_static_method, parent, var);
455        if (relation != null) {
456          // System.out.printf("Found relation %s, variable %s%n", relation,
457          //                   var);
458          int index = relations.indexOf(relation);
459          assert (index != -1) : "Relation " + relation + " not found in " + relations;
460          relation = relations.get(index);
461        }
462      }
463
464      // Put out the variable relation (if one exists).
465      if (relation != null) {
466        outFile.println("  parent " + relation.relation_str(var));
467      }
468
469    } else { // this is the dummy root for class statics
470      if ((relations != null) && (relations.size() > 0)) {
471        relation = find_relation(cinfo, true, parent, var);
472        if (relation != null) {
473          int index = relations.indexOf(relation);
474          assert (index != -1) : "Relation " + relation + " not found in " + relations;
475          relation = relations.get(index);
476          // System.out.printf("Found class relation %s for cinfo %s%n",
477          //                   relation, cinfo);
478        } else {
479          System.out.printf("No class relation found for cinfo %s%n", cinfo);
480        }
481      }
482    }
483
484    // Go through all of the current node's children
485    // and recurse
486    for (DaikonVariableInfo child : var) {
487      if (debug) {
488        System.out.println("traverse var: " + child.getName());
489      }
490      traverse_decl(cinfo, is_static_method, var, child, relation, relations, compare_ppt);
491    }
492
493    if (debug) {
494      System.out.println("Exit traverse_decl");
495    }
496  }
497
498  /**
499   * Returns the string to write to the output file for the specified enum. Currently this is just
500   * the name of the enum in lower case.
501   */
502  private String out_name(Enum<?> e) {
503    return e.name().toLowerCase();
504  }
505
506  /**
507   * Output most of the decl file information for a single variable. This includes the variable,
508   * var-kind, enclosing-var, array, dec-type, rep-type, constant, function-args, flags, and
509   * comparability records. Most notably, it does not output the parent record. The records are
510   * output via the PrinterWriter passed as an argument to the DeclWriter constructor.
511   *
512   * @param parent parent of var in the variable tree
513   * @param var variable whose values are to be output
514   * @param compare_ppt ppt with compare value if comparability-file present, null otherwise
515   * @param comparabilityProvider object on which {@link ComparabilityProvider#getComparability} is
516   *     called. It might be this DeclWriter itself.
517   */
518  public void printDecl(
519      DaikonVariableInfo parent,
520      DaikonVariableInfo var,
521      DeclReader.DeclPpt compare_ppt,
522      ComparabilityProvider comparabilityProvider) {
523
524    // Write out the variable and its name
525    outFile.println("variable " + escape(var.getName()));
526
527    // Write out the kind of variable and its relative name
528    VarKind kind = var.get_var_kind();
529    String relative_name = var.get_relative_name();
530    outFile.print("  var-kind " + out_name(kind));
531    if (relative_name != null) {
532      outFile.print(" " + relative_name);
533    }
534    outFile.println();
535
536    // Write out the enclosing variable.
537    // If we are in an inner class, we need to special case the
538    // 'hidden' field that holds the outer class 'this' pointer.
539    // If the field name ends with ".this", it can only be this
540    // special case and we need to not output the enclosing-var.
541    if ((parent != null)
542        && !var.isStatic()
543        && !((relative_name != null) && relative_name.endsWith(".this"))) {
544      if (debug) {
545        System.out.println("traverse var parent: " + parent.getName());
546      }
547      outFile.println("  enclosing-var " + escape(parent.getName()));
548    }
549
550    // If this variable has multiple value, indicate it is an array
551    if (var.isArray()) {
552      outFile.println("  array 1");
553    }
554
555    // Write out the declared and representation types
556    outFile.println("  dec-type " + escape(var.getTypeNameOnly()));
557    outFile.println("  rep-type " + escape(var.getRepTypeNameOnly()));
558
559    // Write out the constant value (if present)
560    String const_val = var.get_const_val();
561    if (const_val != null) {
562      outFile.println("  constant " + const_val);
563    }
564
565    // Write out the arguments used to create (if present) the variable if it is a function
566    String function_args = var.get_function_args();
567    if (function_args != null) {
568      outFile.println("  function-args " + function_args);
569    }
570
571    // Write out the variable flags if any are set
572    EnumSet<VarFlags> var_flags = var.get_var_flags();
573    if (var_flags.size() > 0) {
574      outFile.print("  flags");
575      for (Enum<?> e : var_flags) {
576        outFile.print(" " + out_name(e));
577      }
578      outFile.println();
579    }
580
581    // Determine comparability and write it out
582    String comp_str = comparabilityProvider.getComparability(var, compare_ppt);
583    outFile.println("  comparability " + comp_str);
584  }
585
586  /**
587   * Get the caparability value for a varaible.
588   *
589   * @param var variable whose value is desired
590   * @param compare_ppt ppt with compare value if comparability-file present, null otherwise
591   * @return String containing the comparability value
592   */
593  @Override
594  public String getComparability(DaikonVariableInfo var, DeclReader.DeclPpt compare_ppt) {
595    // Currently, the value returned by getCompareString() is always 22.
596    String comp_str = var.getCompareString();
597    if (compare_ppt != null) {
598      comp_str = "-1";
599      DeclReader.DeclVarInfo varinfo = compare_ppt.find_var(var.getName());
600      if (varinfo != null) {
601        comp_str = varinfo.get_comparability();
602      }
603    } else {
604      // Check to see if DynComp data is present.
605      if (Runtime.comp_info != null) {
606        // There is no comparability value for this variable as DynComp
607        // saw no interactions. In order to reduce the number of useless
608        // invariants, we will generate a unique, dummy comparability value.
609        comp_str = Integer.toString(unique_compare_value--);
610        if (var.isArray()) {
611          // Should output n index values to match number of dimensions.
612          // However, that value is hard to obtain at this point, so just
613          // always do one.  May cause some multi-dimension variables to
614          // be put into incorrect comparability set.  This is certainly
615          // no worse than previous algorithm. (markro)
616          comp_str = comp_str + "[" + Integer.toString(unique_compare_value--) + "]";
617        }
618      }
619    }
620    return comp_str;
621  }
622
623  /**
624   * Looks to see if there is a class that we are instrumenting that matches the type of this
625   * variable. If so, returns a VarRelation that describes the hierarchy relationship between this
626   * variable (and its field) and the variables within the object ppt for the class. If this is an
627   * object ppt (ci != null), then each top level static variable has a relation to the class ppt.
628   *
629   * @param cinfo class of the object ppt. Null if this is not an object ppt.
630   * @param is_static_method true if this ppt is a static method enter
631   * @param parent parent of var in the variable tree
632   * @param var variable whose relation is desired
633   */
634  private @Nullable VarRelation find_relation(
635      @Nullable ClassInfo cinfo,
636      boolean is_static_method,
637      DaikonVariableInfo parent,
638      DaikonVariableInfo var) {
639
640    // Look for object->class static relationship.  This starts on each
641    // static variable under 'this' (the static variables of a class are
642    // placed in the CLASS ppt).
643    if (cinfo != null && var.isStatic() && (parent instanceof ThisObjInfo)) {
644      return new VarRelation(cinfo.class_name + ":::CLASS", "parent");
645    }
646
647    // Only hashcodes have object ppts
648    if (!var.getRepTypeNameOnly().equals("hashcode")) {
649      return null;
650    }
651
652    // Get the type (class) of this variable
653    String decl_type = var.getTypeNameOnly();
654    // System.out.printf("Looking for hierarchy type %s%n", decl_type);
655
656    // If this ppt is the object ppt for this type, don't create a relation
657    // to it.
658    if ((cinfo != null) && cinfo.class_name.equals(decl_type)) {
659      return null;
660    }
661
662    // Look to see if we are instrumenting this class.  If we are, then
663    // there should be an object ppt for this class.  If this is a static
664    // method and the relation is over the dummy static variable, it
665    // relates directly to the class ppt, otherwise to the object
666    // ppt.  Note that a relation to the class ppt is returned only if there
667    // are static variables.
668    for (ClassInfo ci : SharedData.all_classes) {
669      if (ci.class_name.equals(decl_type)) {
670        // System.out.printf("*Found match for %s : %s%n", decl_type, ci);
671        String ppt_marker = ":::OBJECT";
672        if (is_static_method && (var instanceof StaticObjInfo)) {
673          // System.out.printf("num_class_vars for classinfo %s%n", ci);
674          if (num_class_vars(ci) == 0) {
675            return null;
676          }
677          ppt_marker = ":::CLASS";
678        }
679        if (!enable_object_user && !var.getName().equals("this")) {
680          return null;
681        }
682        return new VarRelation(
683            decl_type + ppt_marker, "parent", var.getName(), "this", var.getName());
684      }
685    }
686
687    return null;
688  }
689
690  /**
691   * Looks for all of the object-user ppt/variable hiearchy relations beginning at var. Once a
692   * relation is found, no more relations are looked for under that variable. In most cases, it
693   * would be expected that only one relation will be found (either var is a class with a
694   * corresponding object ppt or it is not). However, depending on what classes are being
695   * instrumented, it might be possible for a class not to have an object ppt while multiple
696   * children do have object ppts.
697   *
698   * <p>Any relations that are found are added to the relations list.
699   */
700  private void find_relations(
701      ClassInfo ci,
702      boolean is_static_method,
703      DaikonVariableInfo parent,
704      DaikonVariableInfo var,
705      List<VarRelation> relations) {
706
707    // If there is a new relation for this variable add it to the list and
708    // return it.  Note that each static variable in an object ppt will
709    // have a relation to the matching static variable in class ppt.  Only
710    // one of these should go in the list of relations.
711    VarRelation relation = find_relation(ci, is_static_method, parent, var);
712    if (relation != null) {
713      if ((relations.size() == 0)
714          || (relations.get(0).is_class_relation() && relation.is_class_relation())) {
715        relations.add(relation);
716        relation.id = relations.size();
717        return;
718      }
719    }
720
721    // Look for a relation in each child.
722    for (DaikonVariableInfo child : var) {
723      find_relations(ci, is_static_method, parent, child, relations);
724    }
725  }
726
727  /**
728   * Returns the number of variables in the CLASS program point. The CLASS ppt contains all of the
729   * static variables in the class (if any).
730   */
731  private int num_class_vars(ClassInfo cinfo) {
732
733    RootInfo class_root = RootInfo.getClassPpt(cinfo, Runtime.nesting_depth);
734    assert class_root.children.size() == 1;
735    DaikonVariableInfo static_root = class_root.children.get(0);
736    return static_root.children.size();
737  }
738}