001package daikon.dcomp;
002
003import daikon.DynComp;
004import daikon.chicory.ArrayInfo;
005import daikon.chicory.ClassInfo;
006import daikon.chicory.ComparabilityProvider;
007import daikon.chicory.DaikonClassInfo;
008import daikon.chicory.DaikonVariableInfo;
009import daikon.chicory.DaikonWriter;
010import daikon.chicory.DeclReader;
011import daikon.chicory.DeclWriter;
012import daikon.chicory.FieldInfo;
013import daikon.chicory.MethodInfo;
014import daikon.chicory.ParameterInfo;
015import daikon.chicory.ReturnInfo;
016import daikon.chicory.RootInfo;
017import daikon.chicory.StaticObjInfo;
018import daikon.chicory.StringInfo;
019import daikon.chicory.ThisObjInfo;
020import daikon.plumelib.bcelutil.SimpleLog;
021import daikon.plumelib.util.StringsPlume;
022import daikon.plumelib.util.WeakIdentityHashMap;
023import java.io.ByteArrayOutputStream;
024import java.io.PrintStream;
025import java.io.PrintWriter;
026import java.io.UnsupportedEncodingException;
027import java.lang.reflect.Array;
028import java.lang.reflect.Field;
029import java.lang.reflect.InvocationTargetException;
030import java.lang.reflect.Method;
031import java.lang.reflect.Modifier;
032import java.nio.charset.StandardCharsets;
033import java.time.LocalDateTime;
034import java.time.ZoneId;
035import java.util.ArrayDeque;
036import java.util.ArrayList;
037import java.util.Collection;
038import java.util.Collections;
039import java.util.Deque;
040import java.util.HashMap;
041import java.util.HashSet;
042import java.util.IdentityHashMap;
043import java.util.LinkedHashMap;
044import java.util.List;
045import java.util.Map;
046import java.util.Set;
047import java.util.StringJoiner;
048import java.util.concurrent.ConcurrentHashMap;
049import java.util.regex.Matcher;
050import java.util.regex.Pattern;
051import org.checkerframework.checker.lock.qual.GuardSatisfied;
052import org.checkerframework.checker.mustcall.qual.MustCall;
053import org.checkerframework.checker.nullness.qual.Nullable;
054import org.checkerframework.checker.nullness.qual.PolyNull;
055import org.checkerframework.checker.signature.qual.BinaryName;
056import org.checkerframework.dataflow.qual.Pure;
057
058/**
059 * Runtime support for DynComp, a comparability front end for Chicory. This class is a collection of
060 * methods; it should never be instantiated.
061 */
062@SuppressWarnings({"nullness", "interning"}) // tricky code, skip for now
063public final class DCRuntime implements ComparabilityProvider {
064
065  /** List of all instrumented methods. */
066  public static final List<MethodInfo> methods = new ArrayList<>();
067
068  /**
069   * Keep track of whether or not we are already processing an enter/exit so we can avoid recursion.
070   * Only really necessary during debugging (where we call toString()).
071   */
072  private static boolean in_enter_exit = false;
073
074  /** Object used to represent nonsensical values. */
075  private static final Object nonsensical = new Object();
076
077  /** Object used to represent nonsensical list values. */
078  private static final Object nonsensical_list = new Object();
079
080  /** Depth to follow fields in classes. */
081  public static int depth = 2;
082
083  /** static count of tags in the JDK. Used as an offset for non-JDK code. */
084  static int max_jdk_static = 100000;
085
086  /** If the application exits with an exception, it should be placed here. */
087  @SuppressWarnings("StaticAssignmentOfThrowable") // for debugging (I presume)
088  public static @Nullable Throwable exit_exception = null;
089
090  /** Storage for each static tag. */
091  public static List<@Nullable Object> static_tags = new ArrayList<>();
092
093  /** Either "java.lang.DCompInstrumented" or "daikon.dcomp.DCompInstrumented". */
094  static @BinaryName String instrumentation_interface;
095
096  /**
097   * Object used to mark procedure entries in the tag stack. It is pushed on the stack at entry and
098   * checked on exit to make sure it is in on the top of the stack. That allows us to determine
099   * which method caused a tag stack problem.
100   */
101  public static Object method_marker = new Object();
102
103  /** Control debug printing. */
104  public static boolean debug = false;
105
106  /** Log comparability tag stack operations. */
107  public static boolean debug_tag_frame = false;
108
109  /** Log object compare operations. */
110  public static boolean debug_objects = false;
111
112  /** Log variable comparability operations. */
113  public static SimpleLog merge_dv = new SimpleLog(false);
114
115  /** Log array comparability operations. */
116  public static SimpleLog debug_arr_index = new SimpleLog(false);
117
118  /** Log primitive operations. */
119  public static SimpleLog debug_primitive = new SimpleLog(false);
120
121  /** Log comparability merges. */
122  public static SimpleLog debug_merge_comp = new SimpleLog(false);
123
124  /** Log execution time. */
125  public static SimpleLog debug_timing = new SimpleLog(false);
126
127  /** Log decl output. */
128  public static SimpleLog debug_decl_print = new SimpleLog(false);
129
130  /** Log execution time. */
131  public static SimpleLog time_decl = new SimpleLog(false);
132
133  /** Log internal data structure sizes. */
134  public static SimpleLog map_info = new SimpleLog(false);
135
136  /** If true, merge arrays and their indices. */
137  private static boolean merge_arrays_and_indices = true;
138
139  /** Decl writer to share output code with Chicory. */
140  // Set in Premain.premain().
141  static DeclWriter declWriter;
142
143  /** Used for calling {@link ComparabilityProvider#getComparability}. */
144  // Set in Premain.premain().
145  static ComparabilityProvider comparabilityProvider;
146
147  /** True if the header has been printed. */
148  private static boolean headerPrinted = false;
149
150  /** Class to hold per-thread comparability data. */
151  private static class ThreadData {
152    /** Tag stack. */
153    Deque<Object> tag_stack;
154
155    /** Number of methods currently on tag_stack. */
156    int tag_stack_call_depth;
157
158    /** class initializer. */
159    ThreadData() {
160      tag_stack = new ArrayDeque<Object>();
161      tag_stack_call_depth = 0;
162    }
163  }
164
165  /** Map from Thread to ThreadData. */
166  private static Map<Thread, ThreadData> thread_to_data =
167      new ConcurrentHashMap<Thread, ThreadData>();
168
169  /** Map from each object to the tags used for each primitive value in the object. */
170  public static WeakIdentityHashMap<Object, Object[]> field_map =
171      new WeakIdentityHashMap<Object, Object[]>();
172
173  /** List of all classes encountered. These are the classes that will have comparability output. */
174  private static List<ClassInfo> all_classes = new ArrayList<>();
175
176  /** Set of classes whose static initializer has run. */
177  private static Set<String> initialized_eclassses = new HashSet<>();
178
179  /**
180   * Class used as a tag for primitive constants. Only different from Object for debugging purposes.
181   */
182  private static class Constant {}
183
184  /**
185   * Class used as a tag for uninitialized instance fields. Only different from Object for debugging
186   * purposes.
187   */
188  @SuppressWarnings("UnusedVariable") // used only for debugging
189  private static class UninitFieldTag {
190    final String descr;
191    final Throwable stack_trace;
192
193    UninitFieldTag(String descr, Throwable stack_trace) {
194      this.descr = descr;
195      this.stack_trace = stack_trace;
196    }
197  }
198
199  /**
200   * Class used as a tag for uninitialized array elements. Only different from Object for debugging
201   * purposes.
202   */
203  private static class UninitArrayElem {}
204
205  /** Either java.lang.DCompMarker or daikon.dcomp.DCompMarker */
206  private static Class<?> dcomp_marker_class;
207
208  /** java.lang.Object */
209  private static Class<Object> java_lang_Object_class;
210
211  /** Perform any initialization required before instrumentation begins. */
212  public static void init() {
213
214    debug_decl_print.enabled = DynComp.debug_decl_print;
215    if (Premain.debug_dcruntime) {
216      debug = true;
217      debug_tag_frame = true;
218      debug_primitive.enabled = true;
219    }
220    if (Premain.debug_dcruntime_all) {
221      debug = true;
222      debug_tag_frame = true;
223      debug_objects = true;
224      merge_dv.enabled = true;
225      debug_arr_index.enabled = true;
226      debug_primitive.enabled = true;
227      debug_merge_comp.enabled = true;
228      debug_decl_print.enabled = true;
229    }
230
231    try {
232      if (!Premain.jdk_instrumented) {
233        dcomp_marker_class = Class.forName("daikon.dcomp.DCompMarker");
234      } else {
235        dcomp_marker_class = Class.forName("java.lang.DCompMarker");
236      }
237      @SuppressWarnings("unchecked")
238      Class<Object> tmp = (Class<Object>) Class.forName("java.lang.Object");
239      java_lang_Object_class = tmp;
240    } catch (Exception e) {
241      throw new RuntimeException("Error initializing DCRuntime", e);
242    }
243
244    // Initialize the array of static tags
245    ((ArrayList<@Nullable Object>) static_tags).ensureCapacity(max_jdk_static);
246    while (static_tags.size() <= max_jdk_static) {
247      static_tags.add(null);
248    }
249  }
250
251  public static void debug_print_call_stack() {
252    if (!debug) {
253      return;
254    }
255
256    StackTraceElement[] stack_trace;
257    stack_trace = Thread.currentThread().getStackTrace();
258    // [0] is getStackTrace
259    // [1] is debug_print_call_stack
260    for (int i = 2; i < stack_trace.length; i++) {
261      System.out.printf("call stack: %s%n", stack_trace[i]);
262    }
263    System.out.println();
264  }
265
266  /**
267   * Handles calls to instrumented equals() methods. Makes the arguments comparable, and returns
268   * true if they are equals() to one another.
269   *
270   * @param o1 the first argument to equals()
271   * @param o2 the second argument to equals()
272   * @return true if the two values are equal
273   */
274  public static boolean dcomp_equals(Object o1, Object o2) {
275    // Make obj1 and obj2 comparable
276    if ((o1 != null) && (o2 != null)) {
277      TagEntry.union(o1, o2);
278    }
279
280    if (debug) {
281      System.out.printf("In dcomp_equals%n");
282    }
283
284    Method m;
285    try {
286      m =
287          o1.getClass()
288              .getMethod("equals_dcomp_instrumented", new Class<?>[] {java_lang_Object_class});
289    } catch (NoSuchMethodException e) {
290      m = null;
291    } catch (Exception e) {
292      throw new RuntimeException("Error locating equals_dcomp_instrumented", e);
293    }
294
295    if (m != null) {
296      try {
297        // In case the class containing "equals_dcomp_instrumented" is not accessible:
298        m.setAccessible(true);
299        return (Boolean) m.invoke(o1, o2);
300      } catch (Exception e) {
301        throw new RuntimeException("Error invoking equals_dcomp_instrumented", e);
302      }
303    }
304
305    // Push tag for return value, and call the uninstrumented version
306    ThreadData td = thread_to_data.get(Thread.currentThread());
307    td.tag_stack.push(new Constant());
308    if (debug_tag_frame) {
309      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
310    }
311    return o1.equals(o2);
312  }
313
314  /**
315   * This map keeps track of active super.equals() calls.
316   *
317   * <p>Each time we make a call on a particular Object, we keep track of which superclass's equals
318   * method we called last. If that Object makes another call to super.equals before the original
319   * call is done, then invoke the equals method of the next-higher class in the class hierarchy
320   * (i.e. the next superclass).
321   *
322   * <p>The map maps an Object to the last Class whose equals method we invoked while invoking that
323   * Object's original super.equals call. Once the equals call terminates, whether by returning a
324   * value or by throwing an exception, the corresponding key is removed from this map.
325   */
326  static Map<Object, Class<?>> active_equals_calls = new HashMap<>();
327
328  /**
329   * Handles {@code super.equals(Object)} calls. Makes the arguments comparable, and returns true if
330   * super.equals() returns true for them
331   *
332   * @param o1 the first argument to super.equals()
333   * @param o2 the second argument to super.equals()
334   * @return true if the two values are equal, according to super.equals()
335   * @see #active_equals_calls
336   */
337  public static boolean dcomp_super_equals(Object o1, Object o2) {
338    // Make obj1 and obj2 comparable
339    if ((o1 != null) && (o2 != null)) {
340      TagEntry.union(o1, o2);
341    }
342
343    if (debug) {
344      System.out.printf("In dcomp_super_equals%n");
345    }
346
347    Class<?> o1c = o1.getClass();
348    Class<?> o1super;
349
350    // Check to see if we're already in the middle of a super.equals
351    // call for this Object
352    if (null == active_equals_calls.get(o1)) {
353      // No, we are not
354      o1super = o1c.getSuperclass();
355    } else {
356      // Yes, we are -- continue up the class hierarchy
357      o1super = active_equals_calls.get(o1).getSuperclass();
358    }
359
360    // Update the active_equals_calls map
361    active_equals_calls.put(o1, o1super);
362
363    Class<?>[] o1superifaces = o1super.getInterfaces();
364
365    boolean instrumented = false;
366    for (Class<?> c : o1superifaces) {
367      if (c.getName().equals(instrumentation_interface)) {
368        instrumented = true;
369        break;
370      }
371    }
372
373    boolean return_val;
374    try {
375      // if the superclass whose method we are calling is instrumented...
376      if (instrumented) {
377        // call the instrumented version
378        Method m =
379            o1super.getMethod(
380                "equals", new Class<?>[] {java_lang_Object_class, dcomp_marker_class});
381        return_val = ((Boolean) m.invoke(o1, o2, null));
382      } else {
383        // Push tag for return value, and call the uninstrumented version
384        @MustCall ThreadData td = thread_to_data.get(Thread.currentThread());
385        td.tag_stack.push(new Constant());
386        Method m = o1super.getMethod("equals", new Class<?>[] {java_lang_Object_class});
387        return_val = ((Boolean) m.invoke(o1, o2));
388        if (debug_tag_frame) {
389          System.out.printf("tag stack size: %d%n", td.tag_stack.size());
390        }
391      }
392    } catch (NoSuchMethodException e) {
393      System.err.printf("dcomp_super_equals(%s, %s)%n", obj_str(o1), obj_str(o2));
394      System.err.printf("o1super %s%n", o1super);
395      for (Class<?> c : o1superifaces) {
396        System.err.printf("  o1super interface %s%n", c);
397      }
398      for (Method m : o1super.getDeclaredMethods()) {
399        System.err.printf("  o1super method %s%n", m);
400      }
401      throw new RuntimeException(e);
402    } catch (IllegalAccessException e) {
403      throw new RuntimeException(e);
404    } catch (InvocationTargetException e) {
405      e.printStackTrace();
406      throw new RuntimeException(e);
407    } catch (Exception e) {
408      throw new RuntimeException("Error locating equals method", e);
409    }
410
411    // We are now done with the call, so remove the entry for this
412    // call from the active_equals_calls map
413    active_equals_calls.remove(o1);
414    return return_val;
415  }
416
417  /**
418   * Clone an object and return the cloned object.
419   *
420   * <p>DCInstrument guarantees we will not see a clone of an array. If the object has been
421   * instrumented, call the instrumented version of clone; if not, call the uninstrumented version
422   * and make the objects comparable. Really should make all of their fields comparable instead.
423   *
424   * @param orig_obj object being cloned
425   * @param target_class class to search for clone method
426   * @return the result of the clone
427   * @throws Throwable if unable to clone object
428   */
429  public static Object dcomp_clone(Object orig_obj, Class<?> target_class) throws Throwable {
430    if (debug) {
431      System.out.printf("In dcomp_clone%n");
432      System.out.printf("orig: %s, target: %s%n", orig_obj.getClass(), target_class);
433    }
434
435    Object clone_obj = null;
436    while (true) {
437      try {
438        clone_obj = dcomp_clone_worker(orig_obj, target_class);
439        assert (clone_obj != null);
440        break;
441      } catch (Exception e) {
442        if (e.getCause() == null) {
443          // Some exception other than NoSuchMethod.
444          throw e;
445        }
446        if (e.getCause() instanceof java.lang.NoSuchMethodException) {
447          if (debug) {
448            System.out.println("NoSuchMethod " + target_class.getName());
449          }
450
451          // Should never reach top of class hierarchy without finding a clone() method.
452          assert !target_class.getName().equals("java.lang.Object");
453
454          // We didn't find a clone method, get next higher super and try again.
455          target_class = target_class.getSuperclass();
456        } else {
457          // Some exception other than NoSuchMethod.
458          throw e;
459        }
460      }
461    }
462
463    // Make orig_obj and its clone comparable.
464    if ((orig_obj != null) && (clone_obj != null)) {
465      TagEntry.union(orig_obj, clone_obj);
466    }
467    return clone_obj;
468  }
469
470  /**
471   * Tracks active {@code super.clone()} calls.
472   *
473   * @see active_equals_calls
474   */
475  static Map<Object, Class<?>> active_clone_calls = new HashMap<>();
476
477  /**
478   * Handles {@code super.clone()} calls.
479   *
480   * <p>Clone an object using its superclass, and returns the cloned object.
481   *
482   * <p>DCInstrument guarantees we will not see a clone of an array. If the object has been
483   * instrumented, call the instrumented version of clone; if not, call the uninstrumented version
484   * and make the objects comparable. TODO: This really should make all of their fields comparable
485   * instead.
486   *
487   * @param orig_obj object being cloned
488   * @param target_class class to search for clone method
489   * @return the result of the clone
490   * @throws Throwable if unable to clone object
491   * @see #active_clone_calls
492   */
493  public static Object dcomp_super_clone(Object orig_obj, Class<?> target_class) throws Throwable {
494    if (debug) {
495      System.out.printf("In dcomp_super_clone%n");
496      System.out.printf("orig: %s, target: %s%n", orig_obj.getClass(), target_class);
497    }
498
499    // Check to see if we're already in the middle of a super.clone call for this object.
500    if (null != active_clone_calls.get(orig_obj)) {
501      // Yes, we are -- continue up the class hierarchy
502      target_class = active_clone_calls.get(orig_obj).getSuperclass();
503    }
504    // Update the active_clone_calls map
505    active_clone_calls.put(orig_obj, target_class);
506
507    Object clone_obj = null;
508    while (true) {
509      try {
510        clone_obj = dcomp_clone_worker(orig_obj, target_class);
511        assert (clone_obj != null);
512        break;
513      } catch (Exception e) {
514
515        if (e.getCause() == null) {
516          // Some exception other than NoSuchMethod.
517          active_clone_calls.remove(orig_obj);
518          throw e;
519        }
520        if (e.getCause() instanceof java.lang.NoSuchMethodException) {
521          if (debug) {
522            System.out.println("NoSuchMethod " + target_class.getName());
523          }
524
525          // Should never reach top of class hierarchy without finding a clone() method.
526          assert !target_class.getName().equals("java.lang.Object");
527
528          // We didn't find a clone method, get next higher super and try again.
529          target_class = active_clone_calls.get(orig_obj).getSuperclass();
530          // Update the active_clone_calls map
531          active_clone_calls.put(orig_obj, target_class);
532        } else {
533          // Some exception other than NoSuchMethod.
534          active_clone_calls.remove(orig_obj);
535          throw e;
536        }
537      }
538    }
539
540    // Make orig_obj and its clone comparable.
541    if ((orig_obj != null) && (clone_obj != null)) {
542      TagEntry.union(orig_obj, clone_obj);
543    }
544    active_clone_calls.remove(orig_obj);
545    return clone_obj;
546  }
547
548  /**
549   * Clone an object and return the cloned object. Throws an exception if unable to clone object.
550   *
551   * <p>DCInstrument guarantees we will not see a clone of an array. If the object has been
552   * instrumented, call the instrumented version of clone; if not, call the uninstrumented version.
553   *
554   * <p>Note: we use getDeclaredMethod as clone is often protected; also, in the case of
555   * dcomp_super_clone we do not want to automatically find clone in a superclass. We will search
556   * the hierarchy ourselves.
557   *
558   * @param orig_obj object being cloned
559   * @param target_class class to search for clone method
560   * @return the result of the clone
561   * @throws Throwable if unable to clone object
562   */
563  public static Object dcomp_clone_worker(Object orig_obj, Class<?> target_class) throws Throwable {
564    if (debug) {
565      System.out.printf("In dcomp_clone_worker%n");
566      System.out.printf("orig_obj: %s, target_class: %s%n", obj_str(orig_obj), target_class);
567    }
568
569    Method m = null;
570    try {
571      m = target_class.getDeclaredMethod("clone", new Class<?>[] {dcomp_marker_class});
572    } catch (NoSuchMethodException e) {
573      m = null;
574    } catch (Exception e) {
575      throw new RuntimeException("Error locating clone(DCompMarker)", e);
576    }
577
578    if (m != null) {
579      try {
580        if (debug) {
581          System.out.printf("found: %s%n", m);
582        }
583        // In case the class containing "clone()" is not accessible
584        // or clone() is protected.
585        m.setAccessible(true);
586        // Since invoke takes a variable number of arguments, we use an array containing null
587        // as the DCompMarker to distinguish from just null, which indicates 0 arguments.
588        return m.invoke(orig_obj, new Object[] {null});
589      } catch (InvocationTargetException e) {
590        // This might happen - if an exception is thrown from clone(),
591        // propagate it by rethrowing it.
592        throw e.getCause();
593      } catch (Exception e) {
594        throw new RuntimeException(
595            "Error invoking clone(DCompMarker) on object of class " + orig_obj.getClass(), e);
596      }
597    }
598
599    // No instrumented clone(), try for uninstrumented version.
600    try {
601      m = target_class.getDeclaredMethod("clone", new Class<?>[] {});
602    } catch (NoSuchMethodException e) {
603      throw new RuntimeException("unable to locate clone()", e);
604    } catch (Exception e) {
605      throw new RuntimeException("Error locating clone()", e);
606    }
607    try {
608      if (debug) {
609        System.out.printf("found: %s%n", m);
610      }
611      // In case the class containing "clone()" is not accessible
612      // or clone() is protected.
613      m.setAccessible(true);
614      return m.invoke(orig_obj);
615    } catch (InvocationTargetException e) {
616      // This might happen - if an exception is thrown from clone(),
617      // propagate it by rethrowing it.
618      throw e.getCause();
619    } catch (Exception e) {
620      throw new RuntimeException(
621          "Error invoking clone() on object of class " + orig_obj.getClass(), e);
622    }
623  }
624
625  /**
626   * Handle object comparison. Marks the two objects as comparable and returns whether or not they
627   * are equal. Used as part of a replacement for IF_ACMPEQ.
628   */
629  public static boolean object_eq(Object obj1, Object obj2) {
630
631    if (debug_objects) {
632      System.out.printf("comparing (eq) '%s' and '%s'%n", obj_str(obj1), obj_str(obj2));
633    }
634
635    // Note that obj1 and obj2 are comparable
636    if ((obj1 != null) && (obj2 != null)) {
637      TagEntry.union(obj1, obj2);
638    }
639
640    return (obj1 == obj2);
641  }
642
643  /**
644   * Handle object comparison. Marks the two objects as comparable and returns whether or not they
645   * are equal. Used as part of a replacement for IF_ACMPNE.
646   */
647  public static boolean object_ne(Object obj1, Object obj2) {
648
649    if (debug_objects) {
650      System.out.printf("comparing (ne) '%s' and '%s'%n", obj_str(obj1), obj_str(obj2));
651    }
652    // Note that obj1 and obj2 are comparable
653    if ((obj1 != null) && (obj2 != null)) {
654      TagEntry.union(obj1, obj2);
655    }
656
657    return (obj1 != obj2);
658  }
659
660  static Map<String, Integer> methodCountMap = new HashMap<>(64);
661
662  /**
663   * Create the tag frame for this method. Pop the tags for any primitive parameters off of the tag
664   * stack and store them in the tag frame.
665   *
666   * @param params encodes the position of the primitive parameters into a string. The first
667   *     character is size of the tag frame. The remaining characters indicate where each parameter
668   *     on the tag stack should be stored into the frame. For example "20" allocates a tag frame
669   *     with two elements and stores the top of the tag stack into element 0. A string is used for
670   *     simplicity in code generation since strings can easily be placed into the constant portion
671   *     of the class file. Note that characters are determined by adding the integer value to '0'.
672   *     Values greater than 9 will have unintuitive (but printable) values.
673   * @return the allocated and initialized tag frame
674   */
675  public static Object[] create_tag_frame(String params) {
676    if (debug) {
677      System.out.printf("%nEnter: %s%n", caller_name());
678    }
679
680    if (DynComp.verbose) {
681      String method_name = caller_name();
682      Integer count = methodCountMap.get(method_name);
683      if (count == null) {
684        count = 1;
685      } else {
686        count = count.intValue() + 1;
687      }
688      methodCountMap.put(method_name, count);
689    }
690
691    // create_tag_frame is the first DCRuntime method called for an
692    // instrumented user method.  Since it might be on a new thread
693    // we need to check/set the per-thread data map.
694    Thread t = Thread.currentThread();
695    ThreadData td = thread_to_data.computeIfAbsent(t, __ -> new ThreadData());
696
697    int frame_size = ((int) params.charAt(0)) - '0';
698    // Character.digit (params.charAt(0), Character.MAX_RADIX);
699    Object[] tag_frame = new Object[frame_size];
700    if (debug_tag_frame) {
701      System.out.printf(
702          "Creating tag frame of size %d [%s] for %s%n", frame_size, params, caller_name());
703    }
704    for (int ii = 1; ii < params.length(); ii++) {
705      int offset = params.charAt(ii) - '0';
706      // Character.digit (params.charAt(ii), Character.MAX_RADIX);
707      assert td.tag_stack.peek() != method_marker;
708      tag_frame[offset] = td.tag_stack.pop();
709      if (debug_tag_frame) {
710        System.out.printf("popped %s into tag_frame[%d]%n", tag_frame[offset], offset);
711      }
712    }
713
714    // Push the method marker on the tag stack (now that we have removed
715    // the parameters
716    td.tag_stack.push(method_marker);
717    // save the tag stack call_depth for debugging
718    tag_frame[frame_size - 1] = ++td.tag_stack_call_depth;
719    if (debug_tag_frame) {
720      System.out.printf("push method marker tag: %s%n", method_marker);
721      System.out.printf("tag stack call_depth: %d%n", td.tag_stack_call_depth);
722      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
723    }
724
725    // debug_print_call_stack();
726
727    return tag_frame;
728  }
729
730  /**
731   * Make sure the tag stack for this method is empty before exit.
732   *
733   * @param tag_frame the tag frame
734   */
735  public static void normal_exit(Object[] tag_frame) {
736    if (debug) {
737      System.out.printf("Begin normal exit from %s%n", caller_name());
738    }
739
740    ThreadData td = thread_to_data.get(Thread.currentThread());
741    if (td.tag_stack.peek() != method_marker) {
742      // Something has gone wrong.  It's probably an exception that
743      // was handled by an exception handler other than one added by
744      // DCInstrument. From an <init> method or native code, for example.
745      if (debug_tag_frame) {
746        System.out.printf("tag stack value mismatch! top not method marker%n");
747        Thread.dumpStack();
748      }
749      // discard tags until we get to a method marker
750      while (td.tag_stack.peek() != method_marker) td.tag_stack.pop();
751    }
752
753    int orig_tag_stack_call_depth = ((Integer) tag_frame[tag_frame.length - 1]).intValue();
754    if (td.tag_stack_call_depth != orig_tag_stack_call_depth) {
755      // Something has gone wrong.  It's almost certainly an exception that
756      // was handled by an exception handler other than one added by
757      // DCInstrument. From an <init> method or native code, for example.
758      if (debug_tag_frame) {
759        System.out.printf(
760            "tag stack call_depth mismatch! at %d expected %d%n",
761            td.tag_stack_call_depth, orig_tag_stack_call_depth);
762        Thread.dumpStack();
763      }
764    }
765    while (td.tag_stack_call_depth > orig_tag_stack_call_depth) {
766      td.tag_stack.pop(); // discard marker
767      td.tag_stack_call_depth--;
768      // discard tags until we get to a method marker
769      while (td.tag_stack.peek() != method_marker) td.tag_stack.pop();
770    }
771
772    // now do normal exit process
773    td.tag_stack.pop(); // discard marker
774    td.tag_stack_call_depth--;
775    if (debug_tag_frame) {
776      System.out.printf("tag stack call_depth: %d%n", td.tag_stack_call_depth);
777      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
778    }
779    if (debug) {
780      System.out.printf("Normal exit from %s%n%n", caller_name());
781    }
782  }
783
784  /**
785   * Called for exits from methods with a primitive return type. Pop the return type off of the tag
786   * stack, make sure the tags stack is empty for this method and then put the return value back on
787   * the tag stack.
788   *
789   * @param tag_frame the tag frame
790   */
791  public static void normal_exit_primitive(Object[] tag_frame) {
792    if (debug) {
793      System.out.printf("Begin normal exit primitive from %s%n", caller_name());
794    }
795
796    ThreadData td = thread_to_data.get(Thread.currentThread());
797    Object ret_tag = td.tag_stack.pop(); // save what we hope is the return value tag
798    assert ret_tag != null;
799
800    if (td.tag_stack.peek() != method_marker) {
801      // Something has gone wrong.  It's probably an exception that
802      // was handled by an exception handler other than one added by
803      // DCInstrument. From an <init> method or native code, for example.
804      if (debug_tag_frame) {
805        System.out.printf("tag stack value mismatch! top not method marker%n");
806        Thread.dumpStack();
807      }
808      // discard tags until we get to a method marker
809      while (td.tag_stack.peek() != method_marker) td.tag_stack.pop();
810    }
811
812    int orig_tag_stack_call_depth = ((Integer) tag_frame[tag_frame.length - 1]).intValue();
813    if (td.tag_stack_call_depth != orig_tag_stack_call_depth) {
814      // Something has gone wrong.  It's almost certainly an exception that
815      // was handled by an exception handler other than one added by
816      // DCInstrument. From an <init> method or native code, for example.
817      if (debug_tag_frame) {
818        System.out.printf(
819            "tag stack call_depth mismatch! at %d expected %d%n",
820            td.tag_stack_call_depth, orig_tag_stack_call_depth);
821        Thread.dumpStack();
822      }
823    }
824    while (td.tag_stack_call_depth > orig_tag_stack_call_depth) {
825      td.tag_stack.pop(); // discard marker
826      td.tag_stack_call_depth--;
827      // discard tags until we get to a method marker
828      while (td.tag_stack.peek() != method_marker) td.tag_stack.pop();
829    }
830
831    // now do normal exit process
832    td.tag_stack.pop(); // discard marker
833    td.tag_stack_call_depth--;
834    if (debug_tag_frame) {
835      System.out.printf("tag stack call_depth: %d%n", td.tag_stack_call_depth);
836    }
837    if (debug) {
838      System.out.printf("Normal exit primitive from %s%n%n", caller_name());
839    }
840    td.tag_stack.push(ret_tag);
841    if (debug_tag_frame) {
842      System.out.printf("push return value tag: %s%n", ret_tag);
843      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
844    }
845  }
846
847  /**
848   * Clean up the tag stack on an exception exit from a method. Pops items off of the tag stack
849   * until the method marker is found.
850   *
851   * @param throwable cause of the exception
852   */
853  public static void exception_exit(Object throwable) {
854    if (debug) {
855      System.out.printf("Begin exception exit from %s%n", caller_name());
856      ((Throwable) throwable).printStackTrace();
857    }
858
859    ThreadData td = thread_to_data.get(Thread.currentThread());
860    td.tag_stack_call_depth--;
861    if (debug_tag_frame) {
862      System.out.printf("tag stack call_depth: %d%n", td.tag_stack_call_depth);
863    }
864    if (debug) {
865      System.out.printf("Exception exit from %s%n", caller_name());
866    }
867    while (!td.tag_stack.isEmpty()) {
868      if (td.tag_stack.pop() == method_marker) {
869        if (debug_tag_frame) {
870          System.out.printf("tag stack size: %d%n", td.tag_stack.size());
871        }
872        return;
873      }
874    }
875
876    System.err.printf("Method marker not found in exception exit%n");
877  }
878
879  /** Cleans up the tag stack when an exception is thrown. */
880  public static void throw_op() {
881    if (debug) {
882      System.out.printf("In throw_op%n");
883    }
884    ThreadData td = thread_to_data.get(Thread.currentThread());
885    while (td.tag_stack.peek() != method_marker) td.tag_stack.pop();
886    if (debug_tag_frame) {
887      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
888    }
889  }
890
891  /** Pushes the tag at tag_frame[index] on the tag stack. */
892  public static void push_local_tag(Object[] tag_frame, int index) {
893
894    ThreadData td = thread_to_data.get(Thread.currentThread());
895    debug_primitive.log("push_local_tag[%d] %s%n", index, tag_frame[index]);
896    assert tag_frame[index] != null : "index " + index;
897    td.tag_stack.push(tag_frame[index]);
898    if (debug_tag_frame) {
899      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
900    }
901  }
902
903  /** Pops the top of the tag stack into tag_frame[index] */
904  public static void pop_local_tag(Object[] tag_frame, int index) {
905
906    ThreadData td = thread_to_data.get(Thread.currentThread());
907    assert td.tag_stack.peek() != method_marker;
908    tag_frame[index] = td.tag_stack.pop();
909    assert tag_frame[index] != null : "index " + index;
910    debug_primitive.log("pop_local_tag[%d] %s%n", index, tag_frame[index]);
911    if (debug_tag_frame) {
912      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
913    }
914  }
915
916  /** Pushes the tag associated with the static static_num on the tag stack. */
917  public static void push_static_tag(int static_num) {
918
919    ThreadData td = thread_to_data.get(Thread.currentThread());
920    Object static_tag = static_tags.get(static_num);
921    if (static_tag == null) {
922      static_tag = new Object();
923      static_tags.set(static_num, static_tag);
924    }
925    td.tag_stack.push(static_tag);
926    debug_primitive.log("push_static_tag[%d] %s%n", static_num, static_tag);
927    if (debug_tag_frame) {
928      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
929    }
930  }
931
932  /**
933   * Pushes an array reference on the tag stack.
934   *
935   * @param arr_ref array being accessed
936   */
937  public static void push_array_tag(Object arr_ref) {
938
939    ThreadData td = thread_to_data.get(Thread.currentThread());
940    td.tag_stack.push(arr_ref);
941    if (debug_arr_index.enabled()) {
942      debug_arr_index.log("push_array_tag %s%n", obj_str(arr_ref));
943    }
944    if (debug_tag_frame) {
945      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
946    }
947  }
948
949  /**
950   * Pops the top of the tag stack into the tag storage for static_num.
951   *
952   * @param static_num identifies the static variable being accessed
953   */
954  public static void pop_static_tag(int static_num) {
955
956    ThreadData td = thread_to_data.get(Thread.currentThread());
957    assert td.tag_stack.peek() != method_marker;
958    static_tags.set(static_num, td.tag_stack.pop());
959    assert static_tags.get(static_num) != null;
960    debug_primitive.log("pop_static_tag[%d] %s%n", static_num, static_tags.get(static_num));
961    if (debug_tag_frame) {
962      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
963    }
964  }
965
966  /**
967   * Discard the tag on the top of the tag stack. Called when primitives are pushed but not used in
968   * expressions (such as when allocating arrays). (No longer used?)
969   *
970   * @param cnt number of tags to discard
971   */
972  public static void discard_tag(int cnt) {
973    if (debug) {
974      System.out.printf("In discard_tag%n");
975    }
976
977    ThreadData td = thread_to_data.get(Thread.currentThread());
978    // debug_print_call_stack();
979    while (--cnt >= 0) {
980      assert td.tag_stack.peek() != method_marker;
981      if (debug) {
982        System.out.printf("   discard a tag%n");
983      }
984      td.tag_stack.pop();
985    }
986    if (debug_tag_frame) {
987      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
988    }
989  }
990
991  /**
992   * Manipulate the tags for an array store instruction. The tag at the top of stack is stored into
993   * the tag storage for the array. Mark the array and the index as comparable.
994   *
995   * @param arr_ref array being accessed
996   * @param length size of the array
997   * @param index index of the array element being accessed
998   */
999  private static void primitive_array_store(Object arr_ref, int length, int index) {
1000    if (debug) {
1001      System.out.printf("In primitive_array_store%n");
1002    }
1003
1004    // This is a helper routine always called as the first step
1005    // so we can set the per-thread data here.
1006    ThreadData td = thread_to_data.get(Thread.currentThread());
1007
1008    // look for the tag storage for this array
1009    Object[] obj_tags = field_map.get(arr_ref);
1010
1011    // If none has been allocated, allocate the space and associate it with
1012    // the array
1013    if (obj_tags == null) {
1014      obj_tags = new Object[length];
1015      field_map.put(arr_ref, obj_tags);
1016    }
1017
1018    // Pop the tag off of the stack and assign it into the tag storage for
1019    // this index
1020    assert td.tag_stack.peek() != method_marker;
1021    obj_tags[index] = td.tag_stack.pop();
1022    if (debug_primitive.enabled()) {
1023      debug_primitive.log("array store %s[%d] = %s%n", obj_str(arr_ref), index, obj_tags[index]);
1024    }
1025
1026    // Mark the array and its index as comparable
1027    assert td.tag_stack.peek() != method_marker;
1028    Object index_tag = td.tag_stack.pop();
1029    if (debug_arr_index.enabled()) {
1030      debug_arr_index.log("Merging array '%s' and index '%s'", obj_str(arr_ref), index_tag);
1031    }
1032    if (merge_arrays_and_indices) {
1033      TagEntry.union(arr_ref, index_tag);
1034    }
1035    if (debug_tag_frame) {
1036      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
1037    }
1038  }
1039
1040  /** Execute an aastore instruction and mark the array and its index as comparable. */
1041  public static void aastore(Object[] arr, int index, Object val) {
1042
1043    ThreadData td = thread_to_data.get(Thread.currentThread());
1044    // Mark the array and its index as comparable
1045    assert td.tag_stack.peek() != method_marker;
1046    Object index_tag = td.tag_stack.pop();
1047    debug_arr_index.log("Merging array '%s' and index '%s'", arr, index_tag);
1048    if (merge_arrays_and_indices) {
1049      TagEntry.union(arr, index_tag);
1050    }
1051
1052    // Store the value
1053    arr[index] = val;
1054    if (debug_tag_frame) {
1055      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
1056    }
1057  }
1058
1059  /**
1060   * The JVM uses bastore for both boolean and byte data types. With the addition of StackMap
1061   * verification in Java 7 we can no longer use the same runtime routine for both data types.
1062   * Hence, the addition of zastore below for boolean.
1063   *
1064   * <p>Execute an bastore instruction and manipulate the tags accordingly. The tag at the top of
1065   * stack is stored into the tag storage for the array.
1066   */
1067  public static void bastore(byte[] arr, int index, byte val) {
1068
1069    // Store the tag for val in the tag storage for array and mark
1070    // the array and the index as comparable.
1071    primitive_array_store(arr, arr.length, index);
1072
1073    // Execute the array store
1074    arr[index] = val;
1075  }
1076
1077  public static void zastore(boolean[] arr, int index, boolean val) {
1078
1079    // Store the tag for val in the tag storage for array and mark
1080    // the array and the index as comparable.
1081    primitive_array_store(arr, arr.length, index);
1082
1083    // Execute the array store
1084    arr[index] = val;
1085  }
1086
1087  /**
1088   * Execute an castore instruction and manipulate the tags accordingly. The tag at the top of stack
1089   * is stored into the tag storage for the array.
1090   */
1091  public static void castore(char[] arr, int index, char val) {
1092
1093    // Store the tag for val in the tag storage for array and mark
1094    // the array and the index as comparable.
1095    primitive_array_store(arr, arr.length, index);
1096
1097    // Execute the array store
1098    arr[index] = val;
1099  }
1100
1101  /**
1102   * Execute an dastore instruction and manipulate the tags accordingly. The tag at the top of stack
1103   * is stored into the tag storage for the array.
1104   */
1105  public static void dastore(double[] arr, int index, double val) {
1106
1107    // Store the tag for val in the tag storage for array and mark
1108    // the array and the index as comparable.
1109    primitive_array_store(arr, arr.length, index);
1110
1111    // Execute the array store
1112    arr[index] = val;
1113  }
1114
1115  /**
1116   * Execute an fastore instruction and manipulate the tags accordingly. The tag at the top of stack
1117   * is stored into the tag storage for the array.
1118   */
1119  public static void fastore(float[] arr, int index, float val) {
1120
1121    // Store the tag for val in the tag storage for array and mark
1122    // the array and the index as comparable.
1123    primitive_array_store(arr, arr.length, index);
1124
1125    // Execute the array store
1126    arr[index] = val;
1127  }
1128
1129  /**
1130   * Execute an iastore instruction and manipulate the tags accordingly. The tag at the top of stack
1131   * is stored into the tag storage for the array.
1132   */
1133  public static void iastore(int[] arr, int index, int val) {
1134
1135    // Store the tag for val in the tag storage for array and mark
1136    // the array and the index as comparable.
1137    primitive_array_store(arr, arr.length, index);
1138
1139    // Execute the array store
1140    arr[index] = val;
1141  }
1142
1143  /**
1144   * Execute an lastore instruction and manipulate the tags accordingly. The tag at the top of stack
1145   * is stored into the tag storage for the array.
1146   */
1147  public static void lastore(long[] arr, int index, long val) {
1148
1149    // Store the tag for val in the tag storage for array and mark
1150    // the array and the index as comparable.
1151    primitive_array_store(arr, arr.length, index);
1152
1153    // Execute the array store
1154    arr[index] = val;
1155  }
1156
1157  /**
1158   * Execute an sastore instruction and manipulate the tags accordingly. The tag at the top of stack
1159   * is stored into the tag storage for the array.
1160   */
1161  public static void sastore(short[] arr, int index, short val) {
1162
1163    // Store the tag for val in the tag storage for array and mark
1164    // the array and the index as comparable.
1165    primitive_array_store(arr, arr.length, index);
1166
1167    // Execute the array store
1168    arr[index] = val;
1169  }
1170
1171  /**
1172   * Make the count arguments to multianewarray comparable to the corresponding array indices.
1173   * count1 is made comparable to the index of the given array (arr), and count2 is made comparable
1174   * to the index of each array that is an element of arr.
1175   *
1176   * @param count1 number items in 1st dimension (unused, associated tag value is on tag stack)
1177   * @param count2 number items in 2nd dimension (unused, associated tag value is on tag stack)
1178   * @param arr the new array
1179   */
1180  public static void multianewarray2(int count1, int count2, Object[] arr) {
1181    if (debug) {
1182      System.out.printf("In multianewarray2%n");
1183    }
1184
1185    ThreadData td = thread_to_data.get(Thread.currentThread());
1186    assert td.tag_stack.peek() != method_marker;
1187    Object count2tag = td.tag_stack.pop();
1188    assert td.tag_stack.peek() != method_marker;
1189    Object count1tag = td.tag_stack.pop();
1190
1191    TagEntry.union(count1tag, arr);
1192
1193    for (Object subarr : arr) {
1194      TagEntry.union(count2tag, subarr);
1195    }
1196    if (debug_tag_frame) {
1197      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
1198    }
1199  }
1200
1201  /**
1202   * Called when a user method is entered. Any daikon variables whose current values are comparable
1203   * are marked as comparable.
1204   *
1205   * @param tag_frame tag_frame containing the tags for the primitive arguments of this method
1206   * @param obj value of 'this', or null if the method is static
1207   * @param mi_index index into the list of all methods (methods)
1208   * @param args array of the arguments to the method
1209   */
1210  public static void enter(Object[] tag_frame, @Nullable Object obj, int mi_index, Object[] args) {
1211
1212    // Don't be recursive
1213    if (in_enter_exit) {
1214      return;
1215    }
1216    in_enter_exit = true;
1217
1218    if (debug) {
1219      Throwable stack = new Throwable("enter");
1220      StackTraceElement[] ste_arr = stack.getStackTrace();
1221      StackTraceElement ste = ste_arr[1];
1222      System.out.printf("%n%s.%s():::ENTER%n", ste.getClassName(), ste.getMethodName());
1223      System.out.printf("this = %s%nmi = %s%n", obj_str(obj), methods.get(mi_index));
1224      System.out.printf("args: ");
1225      for (Object arg : args) {
1226        System.out.printf("'%s' ", obj_str(arg));
1227      }
1228      System.out.println();
1229    }
1230
1231    MethodInfo mi = methods.get(mi_index);
1232    mi.call_cnt++;
1233    ClassInfo ci = mi.class_info;
1234    if (ci.clazz == null) {
1235      ci.initViaReflection();
1236      if (debug) {
1237        System.out.printf("DCRuntime.enter adding %s to all class list%n", ci);
1238      }
1239      all_classes.add(ci);
1240      merge_dv.log("initializing traversal for %s%n", ci);
1241      ci.init_traversal(depth);
1242    }
1243    if (mi.traversalEnter == null) {
1244      mi.init_traversal(depth);
1245    }
1246
1247    // Merge comparability information for the Daikon variables
1248    merge_dv.log("processing method %s:::ENTER%n", mi);
1249    merge_dv.indent();
1250    process_all_vars(mi, mi.traversalEnter, tag_frame, obj, args, null);
1251    merge_dv.exdent();
1252
1253    in_enter_exit = false;
1254  }
1255
1256  /**
1257   * Called when a user method exits. Any daikon variables whose current values are comparable are
1258   * marked as comparable.
1259   *
1260   * @param tag_frame tag_frame containing the tags for the primitive arguments of this method
1261   * @param obj value of 'this', or null if the method is static
1262   * @param mi_index index into the list of all methods (methods)
1263   * @param args array of the arguments to the method
1264   * @param ret_val value returned by the method, or null if the method is a constructor or void
1265   * @param exit_line_number the source line number of this exit point
1266   */
1267  public static void exit(
1268      Object[] tag_frame,
1269      @Nullable Object obj,
1270      int mi_index,
1271      Object[] args,
1272      Object ret_val,
1273      int exit_line_number) {
1274
1275    // Don't be recursive
1276    if (in_enter_exit) {
1277      return;
1278    }
1279    in_enter_exit = true;
1280
1281    if (debug) {
1282      Throwable stack = new Throwable("exit");
1283      StackTraceElement[] ste_arr = stack.getStackTrace();
1284      StackTraceElement ste = ste_arr[1];
1285      System.out.printf("%n%s.%s():::EXIT%n", ste.getClassName(), ste.getMethodName());
1286      System.out.printf("this = %s%nmi = %s%n", obj_str(obj), methods.get(mi_index));
1287      System.out.printf("args: ");
1288      for (Object arg : args) {
1289        System.out.printf("'%s' ", obj_str(arg));
1290      }
1291      System.out.println();
1292      System.out.printf(
1293          "ret_val = %s%nexit_line_number= %d%n%n", obj_str(ret_val), exit_line_number);
1294    }
1295
1296    MethodInfo mi = methods.get(mi_index);
1297
1298    // Merge comparability information for the Daikon variables
1299    merge_dv.log("processing method %s:::EXIT%n", mi);
1300    merge_dv.indent();
1301    process_all_vars(mi, mi.traversalExit, tag_frame, obj, args, ret_val);
1302    merge_dv.exdent();
1303
1304    in_enter_exit = false;
1305  }
1306
1307  /**
1308   * Process all of the daikon variables in the tree starting at root. If the values referenced by
1309   * those variables are comparable mark the variables as comparable.
1310   *
1311   * @param mi a MethodInfo
1312   * @param root the daikon variables
1313   * @param tag_frame the tags for the primitive arguments of this method
1314   * @param obj the value of {@code this}, or null if the method is static
1315   * @param args the arguments to the method
1316   * @param ret_val value returned by the method, or null if the method is a constructor or void
1317   */
1318  public static void process_all_vars(
1319      MethodInfo mi, RootInfo root, Object[] tag_frame, Object obj, Object[] args, Object ret_val) {
1320
1321    debug_timing.log("process_all_vars for %s%n", mi);
1322
1323    merge_dv.log("this: %s%n", obj_str(obj));
1324
1325    // For some reason the following line causes DynComp to behave incorrectly.
1326    // I have not take the time to investigate.
1327    // merge_dv.log("arguments: %s%n", Arrays.toString(args));
1328
1329    // Map from an Object to the Daikon variable that currently holds
1330    // that object.
1331    IdentityHashMap<Object, DaikonVariableInfo> varmap =
1332        new IdentityHashMap<Object, DaikonVariableInfo>(2048);
1333
1334    for (DaikonVariableInfo dv : root.children) {
1335      if (dv instanceof ThisObjInfo) {
1336        merge_comparability(varmap, null, obj, dv);
1337      } else if (dv instanceof ParameterInfo) {
1338        ParameterInfo pi = (ParameterInfo) dv;
1339        Object p = args[pi.getArgNum()];
1340        // Class arg_type = mi.arg_types[pi.getArgNum()];
1341        // if (arg_type.isPrimitive())
1342        if (pi.isPrimitive()) {
1343          p = tag_frame[pi.get_param_offset() + ((obj == null) ? 0 : 1)];
1344        }
1345        merge_comparability(varmap, null, p, pi);
1346      } else if (dv instanceof ReturnInfo) {
1347        if (mi.return_type().isPrimitive()) {
1348          ThreadData td = thread_to_data.get(Thread.currentThread());
1349          ret_val = td.tag_stack.peek();
1350        }
1351        merge_comparability(varmap, null, ret_val, dv);
1352      } else if (dv instanceof FieldInfo) {
1353        assert ((FieldInfo) dv).isStatic() : "non static field at root " + dv;
1354        merge_comparability(varmap, null, null, dv);
1355      } else if (dv instanceof StaticObjInfo) {
1356        for (DaikonVariableInfo static_dv : dv.children) {
1357          assert ((FieldInfo) static_dv).isStatic() : "non static field at root " + dv;
1358          merge_comparability(varmap, null, null, static_dv);
1359        }
1360      } else {
1361        throw new Error("unexpected node " + dv);
1362      }
1363    }
1364    debug_timing.log("exit process_all_vars for %s%n%n", mi);
1365    map_info.log("varmap size: %d%n", varmap.size());
1366  }
1367
1368  /**
1369   * Returns the tag for the specified field. If that field is an array, a list of tags will be
1370   * returned.
1371   *
1372   * @param fi a field
1373   * @param parent object that contains the field (if any)
1374   * @param obj value of the field itself (if available and if it's an object)
1375   * @return the tag for the specified field
1376   */
1377  static Object get_field_tag(FieldInfo fi, Object parent, Object obj) {
1378
1379    // Initialize the code that gets the tag for various field types
1380    if (fi.field_tag == null) {
1381      if (fi.isStatic()) {
1382        if (fi.isPrimitive()) {
1383          // a static final primitive is a constant and there is no tag accessor
1384          if (fi.isFinal()) {
1385            return null;
1386          }
1387          fi.field_tag = new StaticPrimitiveTag(fi);
1388        } else {
1389          fi.field_tag = new StaticReferenceTag(fi);
1390        }
1391      } else { // not static
1392        if (fi.isPrimitive() && fi.isArray()) {
1393          fi.field_tag = new PrimitiveArrayTag(fi);
1394        } else if (fi.isPrimitive()) {
1395          fi.field_tag = new PrimitiveTag(fi);
1396        } else {
1397          fi.field_tag = new ReferenceTag(fi);
1398        }
1399      }
1400    }
1401
1402    // get the tag
1403    return fi.field_tag.get_tag(parent, obj);
1404  }
1405
1406  /**
1407   * Gets the object in field f in object obj. Exceptions are turned into Errors.
1408   *
1409   * @param f which field to return
1410   * @param obj the object that contains the field
1411   * @return the specified object
1412   */
1413  public static Object get_object_field(Field f, Object obj) {
1414    if (debug) {
1415      System.out.printf("In get_object_field%n");
1416    }
1417    try {
1418      return f.get(obj);
1419    } catch (Exception e) {
1420      throw new Error("can't get field " + f + " in " + obj_str(obj), e);
1421    }
1422  }
1423
1424  /**
1425   * Merges the comparability of the daikon variable dv and its children whose current values are
1426   * comparable.
1427   *
1428   * @param varmap map from value set leaders to the first daikon variable encountered with that
1429   *     leader. Whenever a second daikon variable is encountered whose value has the same leader,
1430   *     that daikon variable is merged with the first daikon variable.
1431   * @param parent value of dv's parent
1432   * @param obj value of dv
1433   * @param dv DaikonVariable to process
1434   */
1435  static void merge_comparability(
1436      IdentityHashMap<Object, DaikonVariableInfo> varmap,
1437      Object parent,
1438      Object obj,
1439      DaikonVariableInfo dv) {
1440
1441    // merge_dv.enabled = dv.getName().contains ("mtfFreq");
1442
1443    long start_millis = 0;
1444    if (debug_timing.enabled()) {
1445      start_millis = System.currentTimeMillis();
1446    }
1447    if (merge_dv.enabled()) {
1448      merge_dv.log("merge_comparability: checking var %s = '%s' %n", dv, obj_str(obj));
1449    }
1450
1451    // Ignore ClassInfo and StringInfo variables.  These are not real
1452    // variables in the program
1453    if ((dv instanceof DaikonClassInfo) || (dv instanceof StringInfo)) {
1454      debug_timing.log("  Variable %s : %d msecs%n", dv, System.currentTimeMillis() - start_millis);
1455      return;
1456    }
1457
1458    // Get the tag for this object.  For non-primitives this is normally the
1459    // object itself.  For static fields, the object is not passed in, but is
1460    // obtained via reflection.
1461    Object tag = obj;
1462    if (dv instanceof FieldInfo) {
1463      tag = get_field_tag((FieldInfo) dv, parent, obj);
1464    }
1465
1466    if (!dv.declShouldPrint()) {
1467      // do nothing
1468    } else if (dv.isArray() && (tag instanceof List<?>)) {
1469      @SuppressWarnings("unchecked")
1470      List<Object> elements = (List<Object>) tag;
1471      if (debug_timing.enabled()) {
1472        debug_timing.log("  ArrayInfo %d elements", elements.size());
1473      }
1474      for (Object atag : elements) {
1475        // Ignore null and nonsensical tags.  There is no reason to process
1476        // their children, because they can't have any with reasonable values
1477        if ((atag == null) || (atag == nonsensical) || (atag == nonsensical_list)) {
1478          continue;
1479        }
1480
1481        // Look up this object.  If it already is associated with a
1482        // DaikonVariable merge those variables.  Otherwise, add it to
1483        // the map
1484        Object leader = TagEntry.find(atag);
1485        if (merge_dv.enabled()) {
1486          merge_dv.log("Leader for atag '%s' is '%s'%n", obj_str(atag), obj_str(leader));
1487        }
1488        DaikonVariableInfo current = varmap.get(leader);
1489        merge_dv.log("Daikon variable for leader = %s%n", current);
1490        if (current != null) {
1491          merge_dv.log("**Merging %s and %s%n", current, dv);
1492          TagEntry.union(current, dv);
1493        } else {
1494          varmap.put(leader, dv);
1495        }
1496      }
1497    } else if (dv.isArray()) {
1498      if (tag == null) {
1499        if (debug_timing.enabled()) {
1500          debug_timing.log(
1501              "  no array tags for Variable %s : %d msecs%n",
1502              dv, System.currentTimeMillis() - start_millis);
1503        }
1504        return;
1505      }
1506      Object[] elements = (Object[]) tag;
1507      if (debug_timing.enabled()) {
1508        debug_timing.log("  Prim ArrayInfo %d elements", elements.length);
1509      }
1510      Object prev_tag = null;
1511      for (Object atag : elements) {
1512        // Ignore null and nonsensical tags.  There is no reason to process
1513        // their children, because they can't have any with reasonable values
1514        if ((atag == null) || (atag == nonsensical) || (atag == nonsensical_list)) {
1515          continue;
1516        }
1517
1518        // No need to handle the same tag twice
1519        if (prev_tag == atag) {
1520          continue;
1521        }
1522        prev_tag = atag;
1523
1524        // Look up this object.  If it already is associated with a
1525        // DaikonVariable merge those variables.  Otherwise, add it to
1526        // the map
1527        Object leader = TagEntry.find(atag);
1528        if (merge_dv.enabled()) {
1529          merge_dv.log("Leader for atag '%s' is '%s'%n", obj_str(atag), obj_str(leader));
1530        }
1531        DaikonVariableInfo current = varmap.get(leader);
1532        merge_dv.log("Daikon variable for leader = %s%n", current);
1533        if (current != null) {
1534          merge_dv.log("**Merging %s and %s%n", current, dv);
1535          TagEntry.union(current, dv);
1536        } else {
1537          varmap.put(leader, dv);
1538        }
1539      }
1540    } else {
1541      // Ignore null and nonsensical tags.  There is no reason to process
1542      // their children, because they can't have any with reasonable values
1543      if ((tag == null) || (tag == nonsensical) || (tag == nonsensical_list)) {
1544        debug_timing.log(
1545            "  Variable %s : %d msecs%n", dv, System.currentTimeMillis() - start_millis);
1546        return;
1547      }
1548
1549      // Look up this object.  If it already is associated with a
1550      // DaikonVariable merge those variables.  Otherwise, add it to
1551      // the map
1552      Object leader = TagEntry.find(tag);
1553      if (merge_dv.enabled()) {
1554        merge_dv.log("Leader for tag '%s' is '%s'%n", obj_str(tag), obj_str(leader));
1555      }
1556      DaikonVariableInfo current = varmap.get(leader);
1557      assert leader != null : "null leader for " + obj_str(tag);
1558      merge_dv.log("Daikon variable for leader = %s%n", current);
1559      if (current != null) {
1560        merge_dv.log("**Merging variable '%s' and '%s'%n", current, dv);
1561        TagEntry.union(current, dv);
1562      } else {
1563        varmap.put(leader, dv);
1564      }
1565    }
1566
1567    debug_timing.log("  Variable %s : %d msecs%n", dv, System.currentTimeMillis() - start_millis);
1568
1569    // Process all of the children
1570    for (DaikonVariableInfo child : dv) {
1571      Object child_obj;
1572      if ((child instanceof ArrayInfo) && ((ArrayInfo) child).getType().isPrimitive()) {
1573        // It's a primitive array.
1574        // System.out.printf("child array type %s = %s%n", ai, ai.getType());
1575        Object[] arr_tags = field_map.get(tag);
1576        // System.out.printf("found arr_tag %s for arr %s%n", arr_tags, tag);
1577        // System.out.printf("tag values = %s%n", Arrays.toString (arr_tags));
1578        child_obj = arr_tags;
1579      } else {
1580        // It's not a primitive array.
1581        child_obj = child.getMyValFromParentVal(obj);
1582      }
1583      merge_dv.indent();
1584      merge_comparability(varmap, obj, child_obj, child);
1585      merge_dv.exdent();
1586    }
1587  }
1588
1589  /**
1590   * Dumps out comparability information for all classes that were processed.
1591   *
1592   * @param pw PrintWriter to write on
1593   */
1594  public static void printAllComparable(PrintWriter pw) {
1595
1596    for (ClassInfo ci : all_classes) {
1597      merge_class_comparability(ci);
1598      for (MethodInfo mi : ci.method_infos) {
1599        if (mi.is_class_initializer()) {
1600          continue;
1601        }
1602        // skip our added method
1603        if (mi.method_name.equals("equals_dcomp_instrumented")) {
1604          continue;
1605        }
1606        pw.println();
1607        printComparable(pw, mi);
1608      }
1609    }
1610  }
1611
1612  /**
1613   * Dumps out comparability trace information for all classes that were processed.
1614   *
1615   * @param pw PrintWriter to write on
1616   */
1617  public static void traceAllComparable(PrintWriter pw) {
1618
1619    for (ClassInfo ci : all_classes) {
1620      merge_class_comparability(ci);
1621      for (MethodInfo mi : ci.method_infos) {
1622        if (mi.is_class_initializer()) {
1623          continue;
1624        }
1625        if (mi.method_name.equals("equals_dcomp_instrumented")) {
1626          continue;
1627        }
1628        pw.println();
1629        printComparableTraced(pw, mi);
1630      }
1631    }
1632  }
1633
1634  /**
1635   * Prints header information to the decls file. Should be called once before emitting any other
1636   * declarations.
1637   *
1638   * @param pw where to write output
1639   * @param className name of the top-level class (used only for printing comments)
1640   */
1641  public static void printHeaderInfo(PrintWriter pw, String className) {
1642
1643    // Write the file header
1644    pw.println("// Declarations for " + className);
1645    pw.println(
1646        "// Declarations written "
1647            + LocalDateTime.now(ZoneId.systemDefault())
1648            + " by daikon.DynComp");
1649    pw.println();
1650    pw.println("decl-version 2.0");
1651    pw.println("var-comparability implicit");
1652    pw.println();
1653  }
1654
1655  /**
1656   * Dumps out .decl file information for all classes that were processed.
1657   *
1658   * @param pw where to write output
1659   */
1660  public static void printDeclFile(PrintWriter pw) {
1661
1662    // Write the information for each class
1663    for (ClassInfo ci : all_classes) {
1664      if (!headerPrinted) {
1665        printHeaderInfo(pw, ci.class_name);
1666        headerPrinted = true;
1667      }
1668      printClassDecl(pw, ci);
1669    }
1670    debug_decl_print.log("finished %d classes%n", all_classes.size());
1671  }
1672
1673  static int class_cnt = 0;
1674  static int method_cnt = 0;
1675  static int instance_var_cnt = 0;
1676  static int static_final_cnt = 0;
1677  static int hashcode_var_cnt = 0;
1678  static int hashcode_arr_cnt = 0;
1679  static int primitive_var_cnt = 0;
1680  static int tostring_cnt = 0;
1681  static int class_var_cnt = 0;
1682  static int this_instance_cnt = 0;
1683  static int other_instance_cnt = 0;
1684  static int other_cnt = 0;
1685  static int parameter_cnt = 0;
1686  static int static_cnt = 0;
1687  static int synthetic_cnt = 0;
1688  static int enum_cnt = 0;
1689
1690  // Only called if 'verbose' is true.
1691  /** Prints statistics about the number of decls to stdout. */
1692  public static void decl_stats() {
1693
1694    for (ClassInfo ci : all_classes) {
1695      class_cnt++;
1696      System.out.printf("processing class %s%n", ci);
1697      add_dv_stats(ci.traversalClass);
1698      add_dv_stats(ci.traversalObject);
1699      for (MethodInfo mi : ci.method_infos) {
1700        if (mi.is_class_initializer()) {
1701          continue;
1702        }
1703        method_cnt++;
1704        System.out.printf("  Processing method %s [%d calls]%n", mi, mi.call_cnt);
1705        if (mi.traversalEnter == null) {
1706          System.out.printf("  Skipping method %s%n", mi);
1707          continue;
1708        }
1709        System.out.printf("    Enter%n");
1710        add_dv_stats(mi.traversalEnter);
1711        for (Integer ii : mi.exit_locations) {
1712          System.out.printf("    Exit%d%n", ii);
1713          add_dv_stats(mi.traversalExit);
1714        }
1715      }
1716    }
1717
1718    // The section above output method count data for non-omitted user code.
1719    // The section below outputs method count data for all methods - including java runtime.
1720    System.out.printf("%ndisplaying all method counts%n");
1721    for (String key : methodCountMap.keySet()) {
1722      System.out.printf("  method %s [%d calls]%n", key, methodCountMap.get(key));
1723    }
1724    System.out.println();
1725
1726    System.out.printf("Classes             = %,d%n", class_cnt);
1727    System.out.printf("Methods             = %,d%n", method_cnt);
1728    System.out.printf("------------------------------%n");
1729    System.out.printf("Hashcodes           = %,d%n", hashcode_var_cnt);
1730    System.out.printf("Hashcode arrays     = %,d%n", hashcode_arr_cnt);
1731    System.out.printf("primitives          = %,d%n", primitive_var_cnt);
1732    System.out.printf("------------------------------%n");
1733    System.out.printf("tostring vars       = %,d%n", tostring_cnt);
1734    System.out.printf("class vars          = %,d%n", class_var_cnt);
1735    System.out.printf("Enums               = %,d%n", enum_cnt);
1736    System.out.printf("Synthetic           = %,d%n", synthetic_cnt);
1737    System.out.printf("static final vars   = %,d%n", static_final_cnt);
1738    System.out.printf("static vars         = %,d%n", static_cnt);
1739    System.out.printf("this instance vars  = %,d%n", this_instance_cnt);
1740    System.out.printf("other instance vars = %,d%n", other_instance_cnt);
1741    System.out.printf("Parameters          = %,d%n", parameter_cnt);
1742    System.out.printf("Others              = %,d%n", other_cnt);
1743  }
1744
1745  private static void add_dv_stats(RootInfo root) {
1746
1747    if (root == null) {
1748      return;
1749    }
1750    List<DaikonVariableInfo> dv_list = root.tree_as_list();
1751    for (DaikonVariableInfo dv : dv_list) {
1752      if (dv instanceof RootInfo) {
1753        continue;
1754      }
1755      if (!dv.declShouldPrint()) {
1756        continue;
1757      }
1758      // System.out.printf("      processing dv %s [%s]%n", dv,
1759      //                    dv.getTypeName());
1760      if (dv.isHashcode()) {
1761        hashcode_var_cnt++;
1762      } else if (dv.isHashcodeArray()) {
1763        hashcode_arr_cnt++;
1764      } else {
1765        primitive_var_cnt++;
1766      }
1767      if (dv.getName().contains(".toString")) {
1768        tostring_cnt++;
1769      } else if (dv.getName().contains(DaikonVariableInfo.class_suffix)) {
1770        class_var_cnt++;
1771      } else if (dv instanceof FieldInfo) {
1772        Field field = ((FieldInfo) dv).getField();
1773        int modifiers = field.getModifiers();
1774        if (field.isEnumConstant()) {
1775          enum_cnt++;
1776        } else if (field.isSynthetic()) synthetic_cnt++;
1777        else if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)) static_final_cnt++;
1778        else if (Modifier.isStatic(modifiers)) static_cnt++;
1779        else if (dv.getName().startsWith("this")) this_instance_cnt++;
1780        else {
1781          other_instance_cnt++;
1782        }
1783      } else if (dv instanceof ParameterInfo) {
1784        parameter_cnt++;
1785      } else {
1786        other_cnt++;
1787      }
1788    }
1789  }
1790
1791  /**
1792   * Calculates and prints the declarations for the specified class.
1793   *
1794   * @param pw where to produce output
1795   * @param ci the class to write
1796   */
1797  public static void printClassDecl(PrintWriter pw, ClassInfo ci) {
1798
1799    time_decl.log("Printing decl file for class %s%n", ci.class_name);
1800    time_decl.indent();
1801    debug_decl_print.log("class %s%n", ci.class_name);
1802
1803    // Make sure that two variables have the same comparability at all
1804    // program points
1805    merge_class_comparability(ci);
1806
1807    // Write the class ppt
1808    String classPptName = ci.class_name + ":::CLASS";
1809    pw.println("ppt " + classPptName);
1810    pw.println("ppt-type class");
1811    printDeclVars(get_comparable(ci.traversalClass), ci.traversalClass, classPptName);
1812    pw.println();
1813    time_decl.log("printed class ppt");
1814
1815    // Write the object ppt
1816    String objectPptName = ci.class_name + ":::OBJECT";
1817    pw.println("ppt " + objectPptName);
1818    pw.println("ppt-type object");
1819    printDeclVars(get_comparable(ci.traversalObject), ci.traversalObject, objectPptName);
1820    pw.println();
1821    time_decl.log("printed object ppt");
1822
1823    // Print the information for each enter/exit point
1824    for (MethodInfo mi : ci.method_infos) {
1825      if (mi.is_class_initializer()) {
1826        continue;
1827      }
1828      debug_decl_print.log("  method %s%n", mi.method_name);
1829      printMethod(pw, mi);
1830    }
1831
1832    time_decl.log("finished class %s%n", ci.class_name);
1833    time_decl.exdent();
1834  }
1835
1836  static long comp_list_ms = 0;
1837  static long ppt_name_ms = 0;
1838  static long decl_vars_ms = 0;
1839  static long total_ms = 0;
1840
1841  // static Stopwatch watch = new Stopwatch();
1842
1843  /**
1844   * Prints a decl ENTER/EXIT records with comparability. Returns the list of comparabile DVSets for
1845   * the exit.
1846   *
1847   * @param pw where to produce output
1848   * @param mi the class to output
1849   * @return the list of comparabile DVSets for the exit
1850   */
1851  public static List<DVSet> printMethod(PrintWriter pw, MethodInfo mi) {
1852
1853    // long start = System.currentTimeMillis();
1854    // watch.reset();
1855
1856    time_decl.log("Print decls for method '%s'", mi.method_name);
1857    time_decl.indent();
1858    List<DVSet> l = get_comparable(mi.traversalEnter);
1859    // comp_list_ms += watch.snapshot(); watch.reset();
1860    if (l == null) {
1861      return null;
1862    }
1863    time_decl.log("got %d comparable sets", l.size());
1864
1865    // Print the enter point
1866    String enterPptName = clean_decl_name(DaikonWriter.methodEntryName(mi.member));
1867    pw.println("ppt " + enterPptName);
1868    pw.println("ppt-type enter");
1869    // ppt_name_ms += watch.snapshot();  watch.reset();
1870    printDeclVars(l, mi.traversalEnter, enterPptName);
1871    // decl_vars_ms += watch.snapshot();  watch.reset();
1872    pw.println();
1873    time_decl.log("after enter");
1874
1875    // Print the exit points
1876    l = get_comparable(mi.traversalExit);
1877    // comp_list_ms += watch.snapshot();  watch.reset();
1878
1879    time_decl.log("got exit comparable sets");
1880    for (Integer ii : mi.exit_locations) {
1881      String exitPptName = clean_decl_name(DaikonWriter.methodExitName(mi.member, ii));
1882      pw.println("ppt " + exitPptName);
1883      pw.println("ppt-type subexit");
1884      // ppt_name_ms += watch.snapshot();  watch.reset();
1885
1886      time_decl.log("after exit clean_decl_name");
1887      printDeclVars(l, mi.traversalExit, exitPptName);
1888      pw.println();
1889      // decl_vars_ms += watch.snapshot();  watch.reset();
1890
1891    }
1892
1893    // total_ms += System.currentTimeMillis() - start;
1894    time_decl.log("Finished processing method '%s'", mi.method_name);
1895    time_decl.exdent();
1896    return l;
1897  }
1898
1899  /** Map from array name to comparability for its indices (if any). */
1900  private static Map<String, Integer> arr_index_map;
1901
1902  /** Map from variable to its comparability. */
1903  private static IdentityHashMap<DaikonVariableInfo, Integer> dv_comp_map;
1904
1905  /** Comparability value for a variable. */
1906  private static int base_comp;
1907
1908  /**
1909   * Print the variables in sets to pw in DECL file format. Each variable in the same set is given
1910   * the same comparability. Constructed classname variables are made comparable to other classname
1911   * variables only.
1912   *
1913   * @param sets the comparability sets
1914   * @param dv_tree the tree of variables
1915   * @param pptName used only for debugging output
1916   */
1917  private static void printDeclVars(List<DVSet> sets, RootInfo dv_tree, String pptName) {
1918
1919    time_decl.indent();
1920    time_decl.log("printDeclVars start");
1921
1922    debug_decl_print.log("printDeclVars(%s)%n", pptName);
1923
1924    // Map from array name to comparability for its indices (if any)
1925    arr_index_map = new LinkedHashMap<>();
1926
1927    // Map from daikon variable to its comparability
1928    dv_comp_map = new IdentityHashMap<DaikonVariableInfo, Integer>(256);
1929
1930    // Initial comparability values
1931    int class_comp = 1;
1932    base_comp = 2;
1933
1934    // Loop through each set of comparable variables
1935    for (DVSet set : sets) {
1936
1937      if ((set.size() == 1) && (set.get(0) instanceof StaticObjInfo)) {
1938        continue;
1939      }
1940
1941      // Determine if the set has both hashcode variables and integer
1942      // variables.  If it does, it is indicating index comparability
1943      boolean hashcode_vars = false;
1944      boolean non_hashcode_vars = false;
1945      // System.out.printf("Checking dv set %s%n", set);
1946      for (DaikonVariableInfo dv : set) {
1947        if (dv.isHashcode() || dv.isHashcodeArray()) {
1948          hashcode_vars = true;
1949        } else {
1950          non_hashcode_vars = true;
1951        }
1952        // System.out.printf("dv = %s, hashcode_var = %b%n",
1953        //                   dv, dv.isHashcode() || dv.isHashcodeArray());
1954      }
1955      debug_decl_print.log(
1956          "        %d vars in set, hashcode/non = %b/%b%n",
1957          set.size(), hashcode_vars, non_hashcode_vars);
1958
1959      // Loop through each variable and assign its comparability
1960      // Since hashcodes and their indices are in the same set, assign
1961      // hashcodes one higher comparability number.  Note that there is
1962      // not necessarily an array child for a hashcode that is comparable
1963      // to an integer.  This can happen when an array and a non-array object
1964      // become comparable to one another.  An integer that is comparable
1965      // to the array will also be comparable to the non-array object, but
1966      // that comparability isn't interesting (and it can't be expressed)
1967      for (DaikonVariableInfo dv : set) {
1968        debug_decl_print.log("          dv %s [%s]%n", dv, System.identityHashCode(dv));
1969        if (dv instanceof DaikonClassInfo) {
1970          dv_comp_map.put(dv, class_comp);
1971          assert set.size() == 1 : "odd set " + set;
1972          base_comp--; // negate increment of base_comp below
1973        } else if (dv.isHashcode() && non_hashcode_vars) {
1974          assert !dv_comp_map.containsKey(dv) : dv + " " + base_comp;
1975          dv_comp_map.put(dv, base_comp + 1);
1976          DaikonVariableInfo array_child = dv.array_child();
1977          if (array_child != null) {
1978            // System.out.printf("array_index_map put: %s, %d%n", array_child.getName(),
1979            // base_comp);
1980            arr_index_map.put(array_child.getName(), base_comp);
1981          }
1982        } else {
1983          assert !dv_comp_map.containsKey(dv) : dv + " " + base_comp;
1984          dv_comp_map.put(dv, base_comp);
1985        }
1986      }
1987
1988      // Increment the comparability number to the next valid number
1989      base_comp++;
1990      if (hashcode_vars && non_hashcode_vars) {
1991        base_comp++;
1992      }
1993    }
1994
1995    time_decl.log("finished filling maps%n");
1996
1997    // Loop through each variable and print out its comparability
1998    // Use the dv_tree rather than sets so that we print out in the
1999    // same order each time.  Note that arrays get two comparablities, one
2000    // for the elements of the array and one for indices.  Normally, these
2001    // comparabilities will be different, but if an index is placed in the
2002    // array the comparabilities can be the same.
2003    List<DaikonVariableInfo> dv_list = dv_tree.tree_as_list();
2004    time_decl.log("built tree as list with %d elements", dv_list.size());
2005    for (DaikonVariableInfo dv : dv_list) {
2006      if ((dv instanceof RootInfo) || (dv instanceof StaticObjInfo) || !dv.declShouldPrint()) {
2007        continue;
2008      }
2009
2010      declWriter.printDecl(null, dv, null, comparabilityProvider);
2011    }
2012
2013    time_decl.log("printDeclVars end%n");
2014    map_info.log("dv_comp_map size: %d%n", dv_comp_map.size());
2015    time_decl.exdent();
2016  }
2017
2018  /**
2019   * Calculates a comparability value.
2020   *
2021   * @param dv variable to calculate comparability for
2022   * @param compare_ppt (not used)
2023   * @return string containing comparability value
2024   */
2025  @Override
2026  public String getComparability(DaikonVariableInfo dv, DeclReader.DeclPpt compare_ppt) {
2027    int comp = dv_comp_map.get(dv);
2028    String comp_str = Integer.toString(comp);
2029    if (dv.isArray()) {
2030      String name = dv.getName();
2031      // If we an array of CLASSNAME or TO_STRING get the index
2032      // comparability from the base array.
2033      if (name.endsWith(DaikonVariableInfo.class_suffix)) {
2034        name = name.substring(0, name.length() - DaikonVariableInfo.class_suffix.length());
2035      } else if (name.endsWith(".toString")) {
2036        name = name.substring(0, name.length() - ".toString".length());
2037      }
2038      Integer index_comp = arr_index_map.get(name);
2039      // System.out.printf("compare: %d [ %s ] ", comp, index_comp);
2040      if (index_comp != null) {
2041        // System.out.println(comp + "[" + index_comp + "]");
2042        comp_str = comp_str + "[" + index_comp + "]";
2043      } else {
2044        // There is no index comparability, so just set it to a unique value.
2045        // System.out.println(comp + "[" + base_comp + "]");
2046        comp_str = comp_str + "[" + base_comp++ + "]";
2047      }
2048    }
2049    return comp_str;
2050  }
2051
2052  /**
2053   * Prints comparability information for the enter and exit points of the specified method. By
2054   * default, outputs to {@code foo.txt-cset}.
2055   *
2056   * @param pw where to produce output
2057   * @param mi the method whose comparability to output
2058   */
2059  /* TO DO: Find a way to make this work correctly without using normal
2060   * get_comparable.
2061   */
2062  public static void printComparable(PrintWriter pw, MethodInfo mi) {
2063
2064    List<DVSet> l = get_comparable(mi.traversalEnter);
2065    pw.printf("Variable sets for %s enter%n", clean_decl_name(mi.toString()));
2066    if (l == null) {
2067      pw.printf("  not called%n");
2068    } else {
2069      for (DVSet set : l) {
2070        if ((set.size() == 1) && (set.get(0) instanceof StaticObjInfo)) {
2071          continue;
2072        }
2073        List<String> stuff = skinnyOutput(set, daikon.DynComp.abridged_vars);
2074        // To see "daikon.chicory.FooInfo:variable", change true to false
2075        pw.printf("  [%d] %s%n", stuff.size(), stuff);
2076      }
2077    }
2078
2079    l = get_comparable(mi.traversalExit);
2080    pw.printf("Variable sets for %s exit%n", clean_decl_name(mi.toString()));
2081    if (l == null) {
2082      pw.printf("  not called%n");
2083    } else {
2084      for (DVSet set : l) {
2085        if ((set.size() == 1) && (set.get(0) instanceof StaticObjInfo)) {
2086          continue;
2087        }
2088        List<String> stuff = skinnyOutput(set, daikon.DynComp.abridged_vars);
2089        // To see "daikon.chicory.FooInfo:variable", change true to false
2090        pw.printf("  [%d] %s%n", stuff.size(), stuff);
2091      }
2092    }
2093  }
2094
2095  /**
2096   * Dumps out comparability trace information for a single method.
2097   *
2098   * @param pw where to write output
2099   * @param mi the method to process
2100   */
2101  public static void printComparableTraced(PrintWriter pw, MethodInfo mi) {
2102    List<DVSet> l = get_comparable(mi.traversalEnter);
2103    Map<DaikonVariableInfo, DVSet> t = get_comparable_traced(mi.traversalEnter);
2104    pw.printf("DynComp Traced Tree for %s enter%n", clean_decl_name(mi.toString()));
2105    if (t == null) {
2106      pw.printf("  not called%n");
2107    } else {
2108      for (DVSet set : l) {
2109        if ((set.size() == 1) && (set.get(0) instanceof StaticObjInfo)) {
2110          continue;
2111        }
2112        printTree(pw, t, (DaikonVariableInfo) TagEntry.troot_find(set.get(0)), 0);
2113        pw.println();
2114      }
2115    }
2116    pw.println();
2117
2118    l = get_comparable(mi.traversalExit);
2119    t = get_comparable_traced(mi.traversalExit);
2120    pw.printf("DynComp Traced Tree for %s exit%n", clean_decl_name(mi.toString()));
2121    if (t == null) {
2122      pw.printf("  not called%n");
2123    } else {
2124      for (DVSet set : l) {
2125        if ((set.size() == 1) && (set.get(0) instanceof StaticObjInfo)) {
2126          continue;
2127        }
2128        printTree(pw, t, (DaikonVariableInfo) TagEntry.troot_find(set.get(0)), 0);
2129        pw.println();
2130      }
2131    }
2132    pw.println();
2133  }
2134
2135  /**
2136   * Prints to [stream] the segment of the tree that starts at [node], interpreting [node] as
2137   * [depth] steps from the root. Requires a Map [tree] that represents a tree though key-value sets
2138   * of the form {@code <}parent, set of children{@code >}.
2139   *
2140   * @param pw where to write output
2141   * @param tree map parents to children
2142   * @param node starting point of tree to print
2143   * @param depth distance from node to root of tree
2144   */
2145  static void printTree(
2146      PrintWriter pw, Map<DaikonVariableInfo, DVSet> tree, DaikonVariableInfo node, int depth) {
2147
2148    /* This method, for some reason, triggers a segfault due to the way
2149     * DVSets are handled conceptually. A trace-tree of one element creates
2150     * a key-value pair DVI foo &rarr; DVSet {foo}, whereas a trace-tree of
2151     * two elements creates a key-value pair DVI foo &rarr; DVSet {bar}.
2152     */
2153
2154    if (depth == 0) {
2155      pw.printf("%s%n", skinnyOutput(node, daikon.DynComp.abridged_vars));
2156      if (tree.get(node) == null) {
2157        return;
2158      }
2159      for (DaikonVariableInfo child : tree.get(node)) {
2160        if (child != node) {
2161          printTree(pw, tree, child, depth + 1);
2162        }
2163      }
2164    } else {
2165      for (int i = 0; i < depth; i++) {
2166        pw.printf("--");
2167      }
2168      pw.printf(
2169          "%s (%s)%n",
2170          skinnyOutput(node, daikon.DynComp.abridged_vars), TagEntry.get_line_trace(node));
2171      if (tree.get(node) == null) {
2172        return;
2173      }
2174      for (DaikonVariableInfo child : tree.get(node)) {
2175        if (child != node) {
2176          printTree(pw, tree, child, depth + 1);
2177        }
2178      }
2179    }
2180  }
2181
2182  /**
2183   * If {@code on} is true, returns an ArrayList of Strings that converts the usual
2184   * DVInfo.toString() output to a more readable form. Just uses DVInfo.toString if {@code on} is
2185   * false.
2186   *
2187   * <p>e.g. "daikon.chicory.ParameterInfo:foo" becomes "Parameter foo"
2188   *
2189   * <p>"daikon.chicory.FieldInfo:this.foo" becomes "Field foo"
2190   *
2191   * @param l a DVSet
2192   * @param on value of daikon.Daikon.abridger_vars
2193   * @return a readable version of {@code l}
2194   */
2195  private static List<String> skinnyOutput(DVSet l, boolean on) {
2196    List<String> o = new ArrayList<>();
2197    for (DaikonVariableInfo dvi : l) {
2198      o.add(skinnyOutput(dvi, on));
2199    }
2200    return o;
2201  }
2202
2203  // TODO: This should be a method of DaikonVariableInfo.
2204  /**
2205   * If {@code on} is false, returns {@code dv.toString()}. If {@code on} is true, returns a more
2206   * readable and informative string.
2207   *
2208   * @param dv a Daikon variable
2209   * @param on value of daikon.Daikon.abridger_vars
2210   * @return a readable version of {@code dv}
2211   */
2212  private static String skinnyOutput(DaikonVariableInfo dv, boolean on) {
2213    if (!on) {
2214      return dv.toString();
2215    }
2216    String dvtxt = dv.toString();
2217    String type = dvtxt.split(":")[0];
2218    type = type.substring(type.lastIndexOf('.') + 1);
2219    String name = dvtxt.split(":")[1];
2220    if (type.equals("ThisObjInfo")) {
2221      dvtxt = "this";
2222    } else if (type.equals("ReturnInfo")) {
2223      dvtxt = "return";
2224    } else if (type.endsWith("Info")) {
2225      type = type.substring(0, type.length() - 4);
2226      if (name.endsWith(DaikonVariableInfo.class_suffix)) {
2227        name = name.substring(0, name.length() - DaikonVariableInfo.class_suffix.length());
2228        type = "Class of";
2229      }
2230      if (name.startsWith("this.")) {
2231        name = name.substring(5);
2232        if (!type.endsWith("Field")) {
2233          type = (type + " Field").trim();
2234        }
2235      }
2236      dvtxt = type + " " + name;
2237    }
2238    return dvtxt;
2239  }
2240
2241  /** Set of Daikon variables. Implements comparable on first DaikonVariable in each set. */
2242  private static class DVSet extends ArrayList<DaikonVariableInfo> implements Comparable<DVSet> {
2243    static final long serialVersionUID = 20050923L;
2244
2245    /** Creates an empty DVSet. */
2246    private DVSet() {
2247      super();
2248    }
2249
2250    /**
2251     * Creates a DVSet with that contains the given variables.
2252     *
2253     * @param variables the variables
2254     */
2255    private DVSet(Collection<DaikonVariableInfo> variables) {
2256      super(variables);
2257    }
2258
2259    @Pure
2260    @Override
2261    public int compareTo(@GuardSatisfied DVSet this, DVSet s1) {
2262      if (s1.isEmpty()) {
2263        return 1;
2264      } else if (isEmpty()) {
2265        return -1;
2266      } else {
2267        return this.get(0).compareTo(s1.get(0));
2268      }
2269    }
2270
2271    void sort() {
2272      Collections.sort(this);
2273    }
2274
2275    /**
2276     * Returns a multi-line representation of the list of variables.
2277     *
2278     * @return a multi-line representation of the list of variables
2279     */
2280    String toStringWithIdentityHashCode() {
2281      StringJoiner result = new StringJoiner(System.lineSeparator());
2282      result.add("DVSet(");
2283      for (DaikonVariableInfo dvi : this) {
2284        result.add("  " + dvi.toStringWithIdentityHashCode());
2285      }
2286      result.add("  )");
2287      return result.toString();
2288    }
2289  }
2290
2291  /**
2292   * Gets a list of comparability sets of Daikon variables. Returns null if the method has never
2293   * been executed (it would probably be better to return each variable in a separate set, but I
2294   * wanted to differentiate this case for now).
2295   *
2296   * <p>The sets are calculated by processing each daikon variable and adding it to a list
2297   * associated with the leader of that set.
2298   */
2299  static @Nullable List<DVSet> get_comparable(RootInfo root) {
2300
2301    if (root == null) {
2302      return null;
2303    }
2304
2305    // List of all of the sets of comparable daikon variables
2306    IdentityHashMap<DaikonVariableInfo, DVSet> sets =
2307        new IdentityHashMap<DaikonVariableInfo, DVSet>();
2308
2309    for (DaikonVariableInfo dv : root) {
2310      add_variable(sets, dv);
2311    }
2312    map_info.log("sets size: %d%n", sets.size());
2313
2314    // Get each set, sort it, and add it to the list of all sets.  Then sort
2315    // the list of all sets.  The sorting is not critical except to create
2316    // a reproducible order.
2317    List<DVSet> set_list = new ArrayList<>(sets.size());
2318    for (DVSet dvs : sets.values()) {
2319      dvs.sort();
2320      set_list.add(dvs);
2321    }
2322    Collections.sort(set_list);
2323
2324    return set_list;
2325  }
2326
2327  /**
2328   * Produce debugging output for a {@code List<DVSet>}.
2329   *
2330   * @param dvsets a list of DVSet objects
2331   * @param indent how many spaces to indent each line
2332   * @return a string representation of {@code dvsets}
2333   */
2334  static String dvSetsToString(List<DVSet> dvsets, int indent) {
2335    // On Java 11, do
2336    //   ByteArrayOutputStream baos = new ByteArrayOutputStream();
2337    //   try (PrintStream ps = new PrintStream(baos, true, StandardCharsets.UTF_8)) {
2338    //   ...
2339    //   return baos.toString(StandardCharsets.UTF_8);
2340    // and drop the catch clause.
2341    ByteArrayOutputStream baos = new ByteArrayOutputStream();
2342    String utf8 = StandardCharsets.UTF_8.name();
2343    try (PrintStream ps = new PrintStream(baos, true, utf8)) {
2344      for (DVSet dvset : dvsets) {
2345        ps.println(StringsPlume.indentLines(indent, dvset.toStringWithIdentityHashCode()));
2346      }
2347      return baos.toString(utf8);
2348    } catch (UnsupportedEncodingException e) {
2349      throw new Error(e);
2350    }
2351  }
2352
2353  /**
2354   * Returns a map representing the tree of tracers. Represents the tree as entries in a map with
2355   * each parent node as the key to a set contains all its children. The parameter RootInfo node is
2356   * included as a key to all its children.
2357   */
2358  static @PolyNull Map<DaikonVariableInfo, DVSet> get_comparable_traced(@PolyNull RootInfo root) {
2359    if (root == null) {
2360      return null;
2361    }
2362
2363    // List of all of the parent-child relationships, where parent-child
2364    //   represents the equivalence relation of being comparable.
2365    // The keyset of this Map is exactly the RootInfo node and the set of all
2366    //   nodes that have children.
2367    // The valueset of this Map is exactly the set of all nodes.
2368    IdentityHashMap<DaikonVariableInfo, DVSet> sets =
2369        new IdentityHashMap<DaikonVariableInfo, DVSet>(256);
2370
2371    for (DaikonVariableInfo child : root) {
2372      if (child.declShouldPrint()) {
2373        add_variable_traced(sets, child);
2374      }
2375    }
2376    for (DVSet dvs : sets.values()) {
2377      dvs.sort();
2378    }
2379
2380    return sets;
2381  }
2382
2383  static void add_variable_traced(Map<DaikonVariableInfo, DVSet> sets, DaikonVariableInfo dv) {
2384    try {
2385      DaikonVariableInfo parent = (DaikonVariableInfo) TagEntry.tracer_find(dv);
2386      DVSet set = sets.computeIfAbsent(parent, __ -> new DVSet());
2387      set.add(dv);
2388    } catch (NullPointerException e) {
2389      throw new Error(e);
2390    }
2391
2392    for (DaikonVariableInfo child : dv) {
2393      add_variable_traced(sets, child);
2394    }
2395  }
2396
2397  /**
2398   * Merges comparability so that the same variable has the same comparability at all points in the
2399   * program point hierarchy. The comparability at the class/object points is calculated by merging
2400   * the comparability at each exit point (i.e., if two variables are in the same set at any exit
2401   * point, they are in the same set at the class point). That comparability is then applied back to
2402   * the exit points so that if two class variables are comparable at any exit point they are
2403   * comparable at each exit point. Finally exit point comparability is merged to the enter point so
2404   * that their comparabilities are the same.
2405   *
2406   * <p>This is not the only valid definition of comparability but it is the one that Daikon expects
2407   * because of how equality sets are handled.
2408   */
2409  static void merge_class_comparability(ClassInfo ci) {
2410
2411    // Get the variables at the object and class point
2412    assert ci.traversalObject != null : ci;
2413    assert ci.traversalClass != null : ci;
2414    // ci.init_traversal (depth);
2415
2416    // If any methods have not been executed, create their information
2417    // now (which will note all of their variables as not comparable)
2418    for (MethodInfo mi : ci.method_infos) {
2419      if (mi.is_class_initializer()) {
2420        continue;
2421      }
2422      if (mi.traversalEnter == null) {
2423        // mi.initViaReflection();
2424        mi.init_traversal(depth);
2425        // System.out.printf("Warning: Method %s never executed%n", mi);
2426      }
2427    }
2428
2429    // Merge the comparability from each exit point into the object point
2430    for (MethodInfo mi : ci.method_infos) {
2431      if (mi.is_class_initializer()) {
2432        continue;
2433      }
2434      merge_dv_comparability(mi.traversalExit, mi.traversalEnter, "Merging exit to enter: " + mi);
2435      merge_dv_comparability(mi.traversalExit, ci.traversalObject, "Merging exit to object: " + mi);
2436      merge_dv_comparability(mi.traversalEnter, ci.traversalObject, "Merging enter to object" + mi);
2437    }
2438
2439    // Merge the comparability from the object point back to each exit point
2440    for (MethodInfo mi : ci.method_infos) {
2441      if (mi.is_class_initializer()) {
2442        continue;
2443      }
2444      merge_dv_comparability(ci.traversalObject, mi.traversalExit, "Merging object to exit: " + mi);
2445    }
2446
2447    // Merge the comparability for each exit point back to the enter
2448    for (MethodInfo mi : ci.method_infos) {
2449      if (mi.is_class_initializer()) {
2450        continue;
2451      }
2452      merge_dv_comparability(mi.traversalExit, mi.traversalEnter, "Merging exit to enter: " + mi);
2453    }
2454
2455    // Merge the object comparability to the class
2456    merge_dv_comparability(ci.traversalObject, ci.traversalClass, "Merging object to class: " + ci);
2457  }
2458
2459  /**
2460   * Merges any variables in the dest tree that are in the same set in the source tree. The source
2461   * tree's comparability is unchanged. Variables are identified by name.
2462   *
2463   * @param src the comparability to read
2464   * @param dest the comparability to modify
2465   * @param debuginfo information about this method call, for debugging
2466   */
2467  static void merge_dv_comparability(RootInfo src, RootInfo dest, String debuginfo) {
2468
2469    // TODO: Why does this take a RootInfo?  A RootInfo contains many more variables than we are
2470    // interested in.
2471    // What is a better way to obtain just the relevant DaikonVariableInfo objects for a given
2472    // program point?
2473
2474    // TODO: We should never merge across different program points.
2475
2476    debug_merge_comp.log("merge_dv_comparability: %s%n", debuginfo);
2477
2478    debug_merge_comp.indent();
2479
2480    // Create a map relating destination names to their variables
2481    Map<String, DaikonVariableInfo> dest_map = new LinkedHashMap<>();
2482    for (DaikonVariableInfo dest_var : varlist(dest)) {
2483      String dest_var_name = dest_var.getName();
2484      if (false) { // temporarily commented out because it is failing
2485        if (dest_map.containsKey(dest_var_name)) {
2486          DaikonVariableInfo old_dest_var = dest_map.get(dest_var_name);
2487          String msg =
2488              String.format(
2489                  "duplicate var name %s%n from old_dest_var = %s%n and dest_var = %s%n" + " in %s",
2490                  dest_var_name,
2491                  old_dest_var.toStringWithIdentityHashCode(),
2492                  dest_var.toStringWithIdentityHashCode(),
2493                  new DVSet(varlist(dest)).toStringWithIdentityHashCode());
2494          System.out.println(msg);
2495          System.err.println(msg);
2496          throw new Error(msg);
2497        }
2498      }
2499      dest_map.put(dest_var_name, dest_var);
2500    }
2501
2502    // Get the variable sets for the source
2503    List<DVSet> src_sets = get_comparable(src);
2504
2505    // Merge any destination variables that are in the same source set
2506    for (DVSet src_set : src_sets) {
2507      if (src_set.size() == 1) {
2508        continue;
2509      }
2510      DaikonVariableInfo dest_canonical = null;
2511      for (DaikonVariableInfo src_var : src_set) {
2512        if (dest_canonical == null) {
2513          dest_canonical = dest_map.get(src_var.getName());
2514          continue;
2515        }
2516        DaikonVariableInfo dest_var = dest_map.get(src_var.getName());
2517        if (dest_var != null) {
2518          TagEntry.union(dest_canonical, dest_var);
2519          debug_merge_comp.log(
2520              "merged '%s' [%s] and '%s' [%s]%n",
2521              dest_canonical,
2522              System.identityHashCode(dest_canonical),
2523              dest_var,
2524              System.identityHashCode(dest_var));
2525        }
2526      }
2527    }
2528    debug_merge_comp.exdent();
2529  }
2530
2531  /**
2532   * Adds this daikon variable and all of its children into their appropriate sets (those of their
2533   * leader) in sets.
2534   */
2535  static void add_variable(Map<DaikonVariableInfo, DVSet> sets, DaikonVariableInfo dv) {
2536
2537    // Add this variable into the set of its leader
2538    if (dv.declShouldPrint()) {
2539      DaikonVariableInfo leader = (DaikonVariableInfo) TagEntry.find(dv);
2540      DVSet set = sets.computeIfAbsent(leader, __ -> new DVSet());
2541      set.add(dv);
2542    }
2543
2544    // Process the children
2545    for (DaikonVariableInfo child : dv) {
2546      add_variable(sets, child);
2547    }
2548  }
2549
2550  /**
2551   * Pushes the tag associated with field_num in obj on the tag stack. A tag value must have been
2552   * previously stored for this field.
2553   *
2554   * @param obj where to store tag
2555   * @param field_num which field within obj to store into
2556   */
2557  public static void push_field_tag(Object obj, int field_num) {
2558    if (debug) {
2559      System.out.printf("In push_field_tag%n");
2560    }
2561    // Since instance variables by default initialize to zero, any field
2562    // can possibly be read before it is set.
2563    push_field_tag_null_ok(obj, field_num);
2564  }
2565
2566  /**
2567   * Pushes the tag associated with field_num in obj on the tag stack. If tag storage for this
2568   * object has not been previously allocated it is allocated now and a tag is allocated for this
2569   * field. This should only be called for objects whose fields can be read without having been
2570   * previously written (in Java).
2571   *
2572   * @param obj where to store tag
2573   * @param field_num which field within obj to store into
2574   */
2575  public static void push_field_tag_null_ok(Object obj, int field_num) {
2576    if (debug) {
2577      System.out.printf("In push_field_tag_null_ok%n");
2578    }
2579
2580    ThreadData td = thread_to_data.get(Thread.currentThread());
2581    Object[] obj_tags = field_map.get(obj);
2582    if (obj_tags != null) {
2583      Object tag = obj_tags[field_num];
2584      if (tag == null) {
2585        Throwable stack_trace = new Throwable();
2586        obj_tags[field_num] =
2587            tag =
2588                new UninitFieldTag(
2589                    obj.getClass().getName() + ":uninit-field:" + field_num, stack_trace);
2590      }
2591      td.tag_stack.push(tag);
2592      if (debug_primitive.enabled()) {
2593        debug_primitive.log(
2594            "push_field_tag %s %d = %s%n", obj_str(obj), field_num, obj_tags[field_num]);
2595      }
2596    } else {
2597      int fcnt = num_prim_fields(obj.getClass());
2598      assert field_num < fcnt : obj.getClass() + " " + field_num + " " + fcnt;
2599      obj_tags = new Object[fcnt];
2600      field_map.put(obj, obj_tags);
2601      debug_primitive.log("push_field_tag: Created tag storage%n");
2602      Throwable stack_trace = new Throwable();
2603      Object tag =
2604          new UninitFieldTag(obj.getClass().getName() + ":uninit-field" + field_num, stack_trace);
2605      obj_tags[field_num] = tag;
2606      td.tag_stack.push(tag);
2607      if (debug_primitive.enabled()) {
2608        debug_primitive.log("push_field_tag %s %d = %s%n", obj_str(obj), field_num, tag);
2609      }
2610    }
2611    if (debug_tag_frame) {
2612      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2613    }
2614  }
2615
2616  /**
2617   * Pops the tag from the top of the tag stack and stores it in the tag storage for the specified
2618   * field of the specified object. If tag storage was not previously allocated, it is allocated
2619   * now.
2620   *
2621   * @param obj where to store tag
2622   * @param field_num which field within obj to store into
2623   */
2624  public static void pop_field_tag(Object obj, int field_num) {
2625    if (debug) {
2626      System.out.printf("In pop_field_tag%n");
2627    }
2628
2629    ThreadData td = thread_to_data.get(Thread.currentThread());
2630    // Look for the tag storage for this object
2631    Object[] obj_tags = field_map.get(obj);
2632
2633    // If none has been allocated, determine how many locations are
2634    // required (the number of primitive fields), allocate the space,
2635    // and associate it with the object.
2636    if (obj_tags == null) {
2637      int fcnt = num_prim_fields(obj.getClass());
2638      assert field_num < fcnt : obj.getClass() + " " + field_num + " " + fcnt;
2639      obj_tags = new Object[fcnt];
2640      field_map.put(obj, obj_tags);
2641      debug_primitive.log("pop_field_tag: Created tag storage%n");
2642    }
2643
2644    // Pop the tag off of the stack and assign into the tag storage for
2645    // this field.
2646    assert td.tag_stack.peek() != method_marker;
2647    Object tag = td.tag_stack.pop();
2648    assert tag != null : "Object " + obj.getClass() + " '" + obj + "' field_num " + field_num;
2649    obj_tags[field_num] = tag;
2650    if (debug_primitive.enabled()) {
2651      debug_primitive.log(
2652          "pop_field_tag (%s %d = %s%n", obj_str(obj), field_num, obj_tags[field_num]);
2653    }
2654    if (debug_tag_frame) {
2655      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2656    }
2657  }
2658
2659  /** Returns the number of primitive fields in clazz and all of its superclasses. */
2660  public static int num_prim_fields(Class<?> clazz) {
2661    if (clazz == Object.class) {
2662      return 0;
2663    } else {
2664      int field_cnt = num_prim_fields(clazz.getSuperclass());
2665      for (Field f : clazz.getDeclaredFields()) {
2666        if (f.getType().isPrimitive()) {
2667          field_cnt++;
2668        }
2669      }
2670      return field_cnt;
2671    }
2672  }
2673
2674  /**
2675   * Handle a binary operation on the two items at the top of the tag stack. Binary operations pop
2676   * the two items off of the top of the stack perform an operation and push the result back on the
2677   * stack. The tags of the two items on the top of the stack must thus be merged and a
2678   * representative tag pushed back on the stack.
2679   */
2680  public static void binary_tag_op() {
2681    ThreadData td = thread_to_data.get(Thread.currentThread());
2682    debug_primitive.log("binary tag op%n");
2683    assert td.tag_stack.peek() != method_marker;
2684    Object tag1 = td.tag_stack.pop();
2685    assert td.tag_stack.peek() != method_marker;
2686    TagEntry.union(tag1, td.tag_stack.peek());
2687    if (debug_tag_frame) {
2688      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2689    }
2690  }
2691
2692  /**
2693   * Handles an i_cmpXX operation. This opcode compares the two integers on the top of the stack and
2694   * jumps accordingly. Thus the two tags on the top of the stack are popped from the tag stack and
2695   * merged. Very similar to binary_tag_op except that nothing is pushed back on the tag stack.
2696   */
2697  public static void cmp_op() {
2698    ThreadData td = thread_to_data.get(Thread.currentThread());
2699    debug_primitive.log("cmp_op%n");
2700    assert td.tag_stack.peek() != method_marker;
2701    Object tag1 = td.tag_stack.pop();
2702    assert td.tag_stack.peek() != method_marker;
2703    TagEntry.union(tag1, td.tag_stack.pop());
2704    if (debug_tag_frame) {
2705      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2706    }
2707    // debug_print_call_stack();
2708  }
2709
2710  /** Handles a dup opcode on a primitive. */
2711  public static void dup() {
2712    ThreadData td = thread_to_data.get(Thread.currentThread());
2713    debug_primitive.log("dup%n");
2714    assert td.tag_stack.peek() != method_marker;
2715    td.tag_stack.push(td.tag_stack.peek());
2716    if (debug_tag_frame) {
2717      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2718    }
2719  }
2720
2721  /** Handles a dup_x1 opcode on a primitive. */
2722  public static void dup_x1() {
2723    ThreadData td = thread_to_data.get(Thread.currentThread());
2724    debug_primitive.log("dup_x1%n");
2725    assert td.tag_stack.peek() != method_marker;
2726    Object top = td.tag_stack.pop();
2727    assert td.tag_stack.peek() != method_marker;
2728    Object nxt = td.tag_stack.pop();
2729    td.tag_stack.push(top);
2730    td.tag_stack.push(nxt);
2731    td.tag_stack.push(top);
2732    if (debug_tag_frame) {
2733      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2734    }
2735  }
2736
2737  /**
2738   * Handles a dup_x2 opcode on a primitive. Currently only support category 1 computational types.
2739   */
2740  public static void dup_x2() {
2741    ThreadData td = thread_to_data.get(Thread.currentThread());
2742    debug_primitive.log("dup_x2%n");
2743    assert td.tag_stack.peek() != method_marker;
2744    Object top = td.tag_stack.pop();
2745    assert td.tag_stack.peek() != method_marker;
2746    Object tag1 = td.tag_stack.pop();
2747    assert td.tag_stack.peek() != method_marker;
2748    Object tag2 = td.tag_stack.pop();
2749    td.tag_stack.push(top);
2750    td.tag_stack.push(tag2);
2751    td.tag_stack.push(tag1);
2752    td.tag_stack.push(top);
2753    if (debug_tag_frame) {
2754      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2755    }
2756  }
2757
2758  /** Handles a dup2 opcode on a primitive. */
2759  public static void dup2() {
2760    ThreadData td = thread_to_data.get(Thread.currentThread());
2761    debug_primitive.log("dup2%n");
2762    assert td.tag_stack.peek() != method_marker;
2763    Object top = td.tag_stack.pop();
2764    assert td.tag_stack.peek() != method_marker;
2765    Object tag1 = td.tag_stack.pop();
2766    td.tag_stack.push(tag1);
2767    td.tag_stack.push(top);
2768    td.tag_stack.push(tag1);
2769    td.tag_stack.push(top);
2770    if (debug_tag_frame) {
2771      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2772    }
2773  }
2774
2775  /** Handles a dup2_x1 opcode on a primitive. */
2776  public static void dup2_x1() {
2777    ThreadData td = thread_to_data.get(Thread.currentThread());
2778    debug_primitive.log("dup2_x1%n");
2779    assert td.tag_stack.peek() != method_marker;
2780    Object top = td.tag_stack.pop();
2781    assert td.tag_stack.peek() != method_marker;
2782    Object tag1 = td.tag_stack.pop();
2783    assert td.tag_stack.peek() != method_marker;
2784    Object tag2 = td.tag_stack.pop();
2785    td.tag_stack.push(tag1);
2786    td.tag_stack.push(top);
2787    td.tag_stack.push(tag2);
2788    td.tag_stack.push(tag1);
2789    td.tag_stack.push(top);
2790    if (debug_tag_frame) {
2791      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2792    }
2793  }
2794
2795  /** Handles a dup2_x2 opcode on a primitive. */
2796  public static void dup2_x2() {
2797    ThreadData td = thread_to_data.get(Thread.currentThread());
2798    debug_primitive.log("dup2_x2%n");
2799    assert td.tag_stack.peek() != method_marker;
2800    Object top = td.tag_stack.pop();
2801    assert td.tag_stack.peek() != method_marker;
2802    Object tag1 = td.tag_stack.pop();
2803    assert td.tag_stack.peek() != method_marker;
2804    Object tag2 = td.tag_stack.pop();
2805    assert td.tag_stack.peek() != method_marker;
2806    Object tag3 = td.tag_stack.pop();
2807    td.tag_stack.push(tag1);
2808    td.tag_stack.push(top);
2809    td.tag_stack.push(tag3);
2810    td.tag_stack.push(tag2);
2811    td.tag_stack.push(tag1);
2812    td.tag_stack.push(top);
2813    if (debug_tag_frame) {
2814      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2815    }
2816  }
2817
2818  /** Swaps the two elements on the top of the tag stack. */
2819  public static void swap() {
2820    ThreadData td = thread_to_data.get(Thread.currentThread());
2821    debug_primitive.log("swap%n");
2822    assert td.tag_stack.peek() != method_marker;
2823    Object top = td.tag_stack.pop();
2824    assert td.tag_stack.peek() != method_marker;
2825    Object tag1 = td.tag_stack.pop();
2826    td.tag_stack.push(top);
2827    td.tag_stack.push(tag1);
2828    if (debug_tag_frame) {
2829      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2830    }
2831  }
2832
2833  /**
2834   * Handles the various primitive (int, double, etc) array load instructions. The array and its
2835   * index are made comparable. The tag for the index is removed from the tag stack and the tag for
2836   * the array element is pushed on the stack.
2837   *
2838   * @param arr_ref array reference
2839   * @param index index into array
2840   */
2841  public static void primitive_array_load(Object arr_ref, int index) {
2842    debug_primitive.log("primitive_array_load%n");
2843    // Since instance variables by default initialize to zero, any field
2844    // can possibly be read before it is set.
2845    primitive_array_load_null_ok(arr_ref, index);
2846  }
2847
2848  /**
2849   * Handles the various primitive (int, double, etc) array load instructions. The array and its
2850   * index are made comparable. The tag for the index is removed from the tag stack and the tag for
2851   * the array element is pushed on the stack. Unlike primitive_array_load(), this method handles
2852   * array elements whose tags have not previously been set. This can happen when the JVM sets an
2853   * array element directly and there is no corresponding java code that can set the tag.
2854   *
2855   * @param arr_ref array reference
2856   * @param index index into array
2857   */
2858  public static void primitive_array_load_null_ok(Object arr_ref, int index) {
2859    ThreadData td = thread_to_data.get(Thread.currentThread());
2860    debug_primitive.log("primitive_array_load_null_ok%n");
2861    // Get the tag for the index and mark it as comparable with the array
2862    assert td.tag_stack.peek() != method_marker;
2863    Object index_tag = td.tag_stack.pop();
2864    if (arr_ref == null) {
2865      if (debug_tag_frame) {
2866        System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2867      }
2868      return;
2869    }
2870    if (debug_arr_index.enabled()) {
2871      debug_arr_index.log("Merging array '%s' and index '%s'", obj_str(arr_ref), index_tag);
2872    }
2873    if (merge_arrays_and_indices) {
2874      TagEntry.union(arr_ref, index_tag);
2875    }
2876
2877    // Push the tag for the element on the tag stack.
2878    Object[] obj_tags = field_map.get(arr_ref);
2879    if (obj_tags != null) {
2880      Object tag = obj_tags[index];
2881      if (tag == null) {
2882        obj_tags[index] = tag = new UninitArrayElem();
2883      }
2884      td.tag_stack.push(tag);
2885      if (debug_primitive.enabled()) {
2886        debug_primitive.log(
2887            "arrayload null-ok %s[%d] = %s%n", obj_str(arr_ref), index, obj_str(obj_tags[index]));
2888      }
2889    } else {
2890      int length = Array.getLength(arr_ref);
2891      obj_tags = new Object[length];
2892      field_map.put(arr_ref, obj_tags);
2893      Object tag = new UninitArrayElem();
2894      obj_tags[index] = tag;
2895      td.tag_stack.push(tag);
2896      if (debug_primitive.enabled()) {
2897        debug_primitive.log("arrayload null-ok %s[%d] = null%n", obj_str(arr_ref), index);
2898      }
2899    }
2900    if (debug_tag_frame) {
2901      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2902    }
2903  }
2904
2905  /**
2906   * Handles the aaload instruction. The array and its index are made comparable. The tag for the
2907   * index is removed from the tag stack.
2908   *
2909   * @param arr_ref array reference
2910   * @param index index into array
2911   */
2912  public static void ref_array_load(Object arr_ref, int index) {
2913    ThreadData td = thread_to_data.get(Thread.currentThread());
2914    debug_primitive.log("ref_array_load%n");
2915    // Get the tag for the index and mark it as comparable with the array
2916    assert td.tag_stack.peek() != method_marker;
2917    Object index_tag = td.tag_stack.pop();
2918    if (debug_tag_frame) {
2919      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2920    }
2921    if (arr_ref == null) {
2922      return;
2923    }
2924    if (debug_arr_index.enabled()) {
2925      debug_arr_index.log("Merging array '%s' and index '%s'", obj_str(arr_ref), index_tag);
2926    }
2927    if (merge_arrays_and_indices) {
2928      TagEntry.union(arr_ref, index_tag);
2929    }
2930  }
2931
2932  /**
2933   * Allocate a new tag for the constant and push it on the tag stack. Note that this allocates a
2934   * new tag each time the constant is pushed. If the same code is executed multiple time (eg, in a
2935   * loop), and different values interact with the constant each time, those values will not end up
2936   * comparable to each other.
2937   */
2938  public static void push_const() {
2939    ThreadData td = thread_to_data.get(Thread.currentThread());
2940    Object tag = new Constant();
2941    debug_primitive.log("push literal constant tag: %s%n", tag);
2942    td.tag_stack.push(tag);
2943    if (debug_tag_frame) {
2944      System.out.printf("tag stack size: %d%n", td.tag_stack.size());
2945    }
2946
2947    // debug_print_call_stack();
2948  }
2949
2950  /**
2951   * Marks the specified class as initialized. We don't look at static variables in classes until
2952   * they are initialized.
2953   *
2954   * @param classname class to mark initialized
2955   */
2956  public static void set_class_initialized(String classname) {
2957    debug_primitive.log("set_class_initialized: %s%n", classname);
2958    initialized_eclassses.add(classname);
2959  }
2960
2961  /**
2962   * Returns true if the specified class is initialized.
2963   *
2964   * @param clazz class to check
2965   * @return true if clazz has been initialized
2966   */
2967  @Pure
2968  public static boolean is_class_initialized(Class<?> clazz) {
2969    debug_primitive.log("is_class_initialized%n");
2970    return initialized_eclassses.contains(clazz.getName());
2971  }
2972
2973  /** Returns the fully-qualified name of the method that called the caller of caller_name(). */
2974  private static String caller_name() {
2975
2976    Throwable stack = new Throwable("caller");
2977    StackTraceElement[] ste_arr = stack.getStackTrace();
2978    StackTraceElement ste = ste_arr[2];
2979    // If JDK 11 runtime transfer method, need to skip another level.
2980    if (ste.getClassName().equals("java.lang.DCRuntime")) {
2981      ste = ste_arr[3];
2982    }
2983    return ste.getClassName() + "." + ste.getMethodName();
2984  }
2985
2986  /**
2987   * Returns a string description of the object that includes its class, identity hash code, and the
2988   * result of its toString() function - if it differs from the default implementation. Note that
2989   * the call to toString() may have unintended side effects. Hence, all calls to obj_str are
2990   * protected by debug flag checks or debug logging enabled() checks.
2991   *
2992   * @param obj object to be described
2993   * @return object description
2994   */
2995  private static String obj_str(Object obj) {
2996
2997    if (obj == null) {
2998      return "<null>";
2999    } else {
3000      String tostring;
3001      try {
3002        tostring = obj.toString();
3003      } catch (Exception e) {
3004        // We use System.identityHashCode(obj) as obj.hashCode() can fail.
3005        tostring =
3006            "toString of "
3007                + obj.getClass().getName()
3008                + "@"
3009                + Integer.toHexString(System.identityHashCode(obj))
3010                + " failed";
3011      }
3012      String default_tostring =
3013          String.format("%s@%s", obj.getClass().getName(), System.identityHashCode(obj));
3014      if (tostring != null && tostring.equals(default_tostring)) {
3015        return tostring;
3016      } else {
3017        // Limit display of object contents to 60 characters.
3018        return String.format(
3019            "%s [%s]",
3020            default_tostring, org.apache.commons.lang3.StringUtils.abbreviate(tostring, 60));
3021      }
3022    }
3023  }
3024
3025  /**
3026   * Returns all of the daikonvariables in the tree rooted at dvi.
3027   *
3028   * @param dvi the tree of variables
3029   * @return all the daikonvarables in the tree
3030   */
3031  private static List<DaikonVariableInfo> varlist(DaikonVariableInfo dvi) {
3032
3033    List<DaikonVariableInfo> list = new ArrayList<>();
3034    list.add(dvi);
3035    for (DaikonVariableInfo child : dvi) {
3036      list.addAll(varlist(child));
3037    }
3038    return list;
3039  }
3040
3041  /**
3042   * Returns the name of the tag field that corresponds to the specified field.
3043   *
3044   * @param field_name field name
3045   * @return tag field name
3046   */
3047  public static String tag_field_name(String field_name) {
3048    debug_primitive.log("tag_field_name: %s%n", field_name);
3049    return (field_name + "__$tag");
3050  }
3051
3052  private static Matcher jdk_decl_matcher =
3053      Pattern.compile("(, )?java.lang.DCompMarker( marker)?").matcher("");
3054  private static Matcher non_jdk_decl_matcher =
3055      Pattern.compile("(, )?daikon.dcomp.DCompMarker( marker)?").matcher("");
3056
3057  /** Removes DCompMarker from the signature. */
3058  public static String clean_decl_name(String decl_name) {
3059
3060    if (Premain.jdk_instrumented) {
3061      jdk_decl_matcher.reset(decl_name);
3062      return jdk_decl_matcher.replaceFirst("");
3063    } else {
3064      non_jdk_decl_matcher.reset(decl_name);
3065      return non_jdk_decl_matcher.replaceFirst("");
3066    }
3067  }
3068
3069  /**
3070   * Abstract base class for code that gets the tag associated with a particular field. There are
3071   * specific implementors for the various types of fields. FieldTag instances are stored in
3072   * FieldInfo so that tags can be efficiently obtained.
3073   */
3074  public abstract static class FieldTag {
3075
3076    /**
3077     * Gets the tag for the field.
3078     *
3079     * @param parent object that contains the field (if any)
3080     * @param obj value of the field itself (if available and if it's an object)
3081     * @return the tag for the field
3082     */
3083    abstract Object get_tag(Object parent, Object obj);
3084  }
3085
3086  /**
3087   * Class that gets the tag for static primitive fields. We retrieve the static tag by using the
3088   * same method as we use during run time to push the tag on the tag stack.
3089   */
3090  public static class StaticPrimitiveTag extends FieldTag {
3091
3092    Method get_tag;
3093
3094    /** Initialize with information from the field. */
3095    StaticPrimitiveTag(FieldInfo fi) {
3096      assert fi.isStatic();
3097      assert fi.isPrimitive();
3098      Field field = fi.getField();
3099      Class<?> clazz = field.getDeclaringClass();
3100      String name = Premain.tag_method_name(Premain.GET_TAG, clazz.getName(), field.getName());
3101      try {
3102        get_tag = clazz.getMethod(name);
3103      } catch (Exception e) {
3104        throw new Error("can't find tag method " + name, e);
3105      }
3106    }
3107
3108    /** Returns the tag associated with this field. */
3109    @Override
3110    Object get_tag(Object parent, Object obj) {
3111      Object tag;
3112      // jhp - not sure why these are not null...
3113      // assert parent == null && obj == null
3114      //  : " parent/obj = " + obj_str(parent) + "/" + obj_str(obj);
3115      try {
3116        ThreadData td = thread_to_data.get(Thread.currentThread());
3117        Object ret_val = get_tag.invoke(parent);
3118        assert ret_val == null;
3119        assert td.tag_stack.peek() != method_marker;
3120        tag = td.tag_stack.pop();
3121        assert tag != null;
3122        if (debug_tag_frame) {
3123          System.out.printf("tag stack size: %d%n", td.tag_stack.size());
3124        }
3125      } catch (Exception e) {
3126        throw new Error("can't execute tag method " + get_tag, e);
3127      }
3128      return tag;
3129    }
3130  }
3131
3132  /**
3133   * Class that gets the tag for a static reference variable. The tag for a reference variable is
3134   * the object itself, so that is obtained via reflection.
3135   */
3136  public static class StaticReferenceTag extends FieldTag {
3137
3138    /** Corresponding java field. */
3139    Field field;
3140
3141    /** Set to true when the class containing the field is initialized. */
3142    boolean is_class_initialized = false;
3143
3144    /** Class that contains the field. */
3145    Class<?> declaring_class;
3146
3147    /** Initialize for this field. */
3148    public StaticReferenceTag(FieldInfo fi) {
3149
3150      assert fi.isStatic();
3151      assert !fi.isPrimitive();
3152      field = fi.getField();
3153      declaring_class = field.getDeclaringClass();
3154    }
3155
3156    /** Gets the tag for this static reference. */
3157    @Override
3158    @SuppressWarnings("deprecation") // in Java 9+, use canAccess instead of isAccessible
3159    public Object get_tag(Object parent, Object obj) {
3160
3161      // assert parent == null && obj == null;
3162      if (!is_class_initialized) {
3163        if (is_class_initialized(declaring_class)) {
3164          if (!field.isAccessible()) {
3165            field.setAccessible(true);
3166          }
3167          is_class_initialized = true;
3168        } else {
3169          return nonsensical;
3170        }
3171      }
3172
3173      try {
3174        return field.get(null);
3175      } catch (Exception e) {
3176        throw new RuntimeException("Can't get val for static field " + field, e);
3177      }
3178    }
3179  }
3180
3181  /**
3182   * Class that gets the list of tags for primitive arrays. Note that primitive arrays can both be
3183   * actual arrays of primitives and also arrays of classes containing primitives. In the second
3184   * case, there is a separate object that contains each of the 'array' values.
3185   */
3186  public static class PrimitiveArrayTag extends FieldTag {
3187
3188    /** The field number for this field inside its object. */
3189    int field_num;
3190
3191    public PrimitiveArrayTag(FieldInfo fi) {
3192      assert !fi.isStatic() && fi.isPrimitive() && fi.isArray();
3193      field_num = fi.get_field_num();
3194    }
3195
3196    /** Returns a list of object tags. */
3197    @Override
3198    public Object get_tag(Object parent, Object obj) {
3199
3200      // Object is an array of objects containing each item
3201      // assert obj == null: "primitive array object = " + obj_str (obj);
3202      @SuppressWarnings("unchecked")
3203      List<Object> parent_list = (List<Object>) parent;
3204      List<Object> tag_list = new ArrayList<>(parent_list.size());
3205      for (Object parent_element : parent_list) {
3206        Object[] tags = field_map.get(parent_element);
3207        if (tags == null) {
3208          tag_list.add(nonsensical);
3209        } else {
3210          tag_list.add(tags[field_num]);
3211        }
3212      }
3213      return tag_list;
3214    }
3215  }
3216
3217  /**
3218   * Class that gets the tag for a primitive instance field. Each object with primitive fields has a
3219   * corresponding tag array in field map. The tag for a particular field is stored at that fields
3220   * offset in the tag array.
3221   */
3222  public static class PrimitiveTag extends FieldTag {
3223
3224    int field_num;
3225
3226    public PrimitiveTag(FieldInfo fi) {
3227      assert !fi.isStatic() && fi.isPrimitive() && !fi.isArray();
3228      field_num = fi.get_field_num();
3229    }
3230
3231    @Override
3232    public Object get_tag(Object parent, Object obj) {
3233
3234      // obj is the wrapper for the primitive
3235      // assert obj == null: "primitive object = " + obj_str (obj);
3236      Object[] tags = field_map.get(parent);
3237      if (tags == null) {
3238        return nonsensical; // happens if field has never been assigned to
3239      } else {
3240        return tags[field_num];
3241      }
3242    }
3243  }
3244
3245  /**
3246   * Class that returns the tag for a reference instance field. In this case, the tag is just the
3247   * object itself.
3248   */
3249  public static class ReferenceTag extends FieldTag {
3250
3251    public ReferenceTag(FieldInfo fi) {
3252      assert !fi.isStatic() && !fi.isPrimitive();
3253    }
3254
3255    @Override
3256    public Object get_tag(Object parent, Object obj) {
3257      return obj;
3258    }
3259  }
3260}