001package daikon.chicory;
002
003import static java.nio.charset.StandardCharsets.UTF_8;
004
005import java.io.BufferedOutputStream;
006import java.io.BufferedWriter;
007import java.io.File;
008import java.io.FileOutputStream;
009import java.io.IOException;
010import java.io.OutputStream;
011import java.io.OutputStreamWriter;
012import java.io.PrintWriter;
013import java.net.InetAddress;
014import java.net.InetSocketAddress;
015import java.net.Socket;
016import java.net.SocketAddress;
017import java.net.UnknownHostException;
018import java.util.ArrayDeque;
019import java.util.ArrayList;
020import java.util.ConcurrentModificationException;
021import java.util.Deque;
022import java.util.HashMap;
023import java.util.HashSet;
024import java.util.LinkedHashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028import java.util.concurrent.atomic.AtomicInteger;
029import java.util.regex.Pattern;
030import java.util.zip.GZIPOutputStream;
031import org.checkerframework.checker.lock.qual.GuardSatisfied;
032import org.checkerframework.checker.lock.qual.GuardedBy;
033import org.checkerframework.checker.lock.qual.Holding;
034import org.checkerframework.checker.mustcall.qual.Owning;
035import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
036import org.checkerframework.checker.nullness.qual.NonNull;
037import org.checkerframework.checker.nullness.qual.Nullable;
038import org.checkerframework.checker.signature.qual.BinaryName;
039import org.checkerframework.checker.signature.qual.ClassGetName;
040import org.checkerframework.checker.signature.qual.FieldDescriptor;
041import org.checkerframework.dataflow.qual.SideEffectFree;
042
043/**
044 * Runtime support for Chicory, the Daikon front end for Java. This class is a collection of
045 * methods; it should never be instantiated.
046 */
047@SuppressWarnings({
048  "JavaLangClash" // same class name as one in java.lang.
049})
050public class Runtime {
051  /** Unique id for method entry/exit (so they can be matched up) */
052  public static AtomicInteger nonce = new AtomicInteger();
053
054  /** debug flag */
055  public static boolean debug = false;
056
057  /**
058   * Flag indicating that a dtrace record is currently being written used to prevent a call to
059   * instrumented code that occurs as part of generating a dtrace record (eg, toArray when
060   * processing lists or pure functions) from generating a nested dtrace record.
061   */
062  public static boolean in_dtrace = false;
063
064  /** True if ChicoryPremain was unable to load. */
065  public static boolean chicoryLoaderInstantiationError = false;
066
067  /** Flag that indicates when the first class has been processed. */
068  static boolean first_class = true;
069
070  //
071  // Control over what classes (ppts) are instrumented
072  //
073
074  /** Ppts to omit (regular expression) */
075  public static List<Pattern> ppt_omit_pattern = new ArrayList<>();
076
077  /** Ppts to include (regular expression) */
078  public static List<Pattern> ppt_select_pattern = new ArrayList<>();
079
080  /** Comparability information (if any) */
081  static @Nullable DeclReader comp_info = null;
082
083  //
084  // Setups that control what information is written
085  //
086
087  /** Depth to wich to examine structure components. */
088  static int nesting_depth = 2;
089
090  //
091  // Dtrace file vars
092  //
093
094  /** Max number of records in dtrace file. */
095  static long dtraceLimit = Long.MAX_VALUE;
096
097  /** Number of records printed to date. */
098  static long printedRecords = 0;
099
100  /** Terminate the program when the dtrace limit is reached. */
101  static boolean dtraceLimitTerminate = false;
102
103  /** Dtrace output stream. Null if no_dtrace is true. */
104  @SuppressWarnings(
105      "nullness:initialization.static.field.uninitialized" // initialized and used in generated
106  // instrumentation code that cannot be type-checked by a source code checker.
107  )
108  static @Owning @GuardedBy("<self>") PrintWriter dtrace;
109
110  /** Set to true when the dtrace stream is closed. */
111  static boolean dtrace_closed = false;
112
113  /** True if no dtrace is being generated. */
114  static boolean no_dtrace = false;
115
116  static String method_indent = "";
117
118  /** Decl writer setup for writing to the trace file. */
119  @SuppressWarnings("nullness:initialization.static.field.uninitialized" // Set in
120  // ChicoryPremain.initializeDeclAndDTraceWriters.
121  )
122  static DeclWriter decl_writer;
123
124  /** Dtrace writer setup for writing to the trace file. */
125  @SuppressWarnings("nullness:initialization.static.field.uninitialized" // Set in
126  // ChicoryPremain.initializeDeclAndDTraceWriters.
127  )
128  static @GuardedBy("Runtime.class") DTraceWriter dtrace_writer;
129
130  /**
131   * Which static initializers have been run. Each element of the Set is a fully qualified class
132   * name.
133   */
134  private static Set<String> initSet = new HashSet<>();
135
136  /** Class of information about each active call. */
137  private static class CallInfo {
138    /** nonce of call */
139    int nonce;
140
141    /** whether or not the call was captured on enter */
142    boolean captured;
143
144    @Holding("Runtime.class")
145    public CallInfo(int nonce, boolean captured) {
146      this.nonce = nonce;
147      this.captured = captured;
148    }
149  }
150
151  /** Stack of active methods. */
152  private static @GuardedBy("Runtime.class") Map<Thread, Deque<CallInfo>> thread_to_callstack =
153      new LinkedHashMap<>();
154
155  /**
156   * Sample count at a call site to begin sampling. All previous calls will be recorded. Sampling
157   * starts at 10% and decreases by a factor of 10 each time another sample_start samples have been
158   * recorded. If sample_start is 0, then all calls will be recorded.
159   */
160  public static int sample_start = 0;
161
162  // Constructor
163  private Runtime() {
164    throw new Error("Do not create instances of Runtime");
165  }
166
167  /**
168   * Thrown to indicate that main should not print a stack trace, but only print the message itself
169   * to the user. If the string is null, then this is normal termination, not an error.
170   */
171  public static class TerminationMessage extends RuntimeException {
172    static final long serialVersionUID = 20050923L;
173
174    public TerminationMessage(String s) {
175      super(s);
176    }
177
178    public TerminationMessage() {
179      super();
180    }
181  }
182
183  // Whenever a method call occurs in the target program, output
184  // information about that call to the trace file.  However, if the
185  // method is a pure method that is being called to create a value for
186  // the trace file, don't record it.
187  // TODO: invokingPure should be annotated with @GuardedByName("Runtime.class")
188  // once that annotation is available.  Currently all the methods that access
189  // invokingPure are annotated with @Holding("Runtime.class"), but annotating
190  // the boolean would prevent any new methods from accessing it without holding
191  // the lock.
192  private static boolean invokingPure = false;
193
194  @Holding("Runtime.class")
195  public static boolean dontProcessPpts() {
196    return invokingPure;
197  }
198
199  @Holding("Runtime.class")
200  public static void startPure() {
201    invokingPure = true;
202  }
203
204  @Holding("Runtime.class")
205  public static void endPure() {
206    invokingPure = false;
207  }
208
209  /**
210   * Called when a method is entered.
211   *
212   * @param obj receiver of the method that was entered, or null if method is static
213   * @param nonce nonce identifying which enter/exit pair this is
214   * @param mi_index index in methods of the MethodInfo for this method
215   * @param args array of arguments to method
216   */
217  public static synchronized void enter(
218      @Nullable Object obj, int nonce, int mi_index, Object[] args) {
219
220    MethodInfo mi = null;
221    if (debug) {
222      synchronized (SharedData.methods) {
223        mi = SharedData.methods.get(mi_index);
224      }
225      System.out.printf(
226          "%smethod_entry %s.%s%n", method_indent, mi.class_info.class_name, mi.method_name);
227      method_indent = method_indent.concat("  ");
228    }
229
230    if (dontProcessPpts()) {
231      return;
232    }
233
234    // Make sure that the in_dtrace flag matches the stack trace
235    // check_in_dtrace();
236
237    // Ignore this call if we are already processing a dtrace record
238    if (in_dtrace) {
239      return;
240    }
241
242    // Note that we are processing a dtrace record until we return
243    in_dtrace = true;
244    try {
245      int num_new_classes = 0;
246      synchronized (SharedData.new_classes) {
247        num_new_classes = SharedData.new_classes.size();
248      }
249      if (num_new_classes > 0) {
250        process_new_classes();
251      }
252
253      synchronized (SharedData.methods) {
254        mi = SharedData.methods.get(mi_index);
255      }
256      mi.call_cnt++;
257
258      // If sampling, check to see if we are capturing this sample
259      boolean capture = true;
260      if (sample_start > 0) {
261        if (mi.call_cnt <= sample_start) {
262          // nothing to do
263        } else if (mi.call_cnt <= (sample_start * 10)) {
264          capture = (mi.call_cnt % 10) == 0;
265        } else if (mi.call_cnt <= (sample_start * 100)) {
266          capture = (mi.call_cnt % 100) == 0;
267        } else if (mi.call_cnt <= (sample_start * 1000)) {
268          capture = (mi.call_cnt % 1000) == 0;
269        } else {
270          capture = (mi.call_cnt % 10000) == 0;
271        }
272        Thread t = Thread.currentThread();
273        @SuppressWarnings("lock:method.invocation") // CF bug: inference failed
274        Deque<CallInfo> callstack =
275            thread_to_callstack.computeIfAbsent(t, __ -> new ArrayDeque<CallInfo>());
276        callstack.push(new CallInfo(nonce, capture));
277      }
278
279      if (capture) {
280        mi.capture_cnt++;
281        // long start = System.currentTimeMillis();
282        if (mi.member == null) {
283          dtrace_writer.clinitEntry(mi.class_info.class_name + ".<clinit>:::ENTER", nonce);
284        } else {
285          dtrace_writer.methodEntry(mi, nonce, obj, args);
286        }
287        // long duration = System.currentTimeMillis() - start;
288        // System.out.println ("Enter " + mi + " " + duration + "ms"
289        //                 + " " + mi.capture_cnt + "/" + mi.call_cnt);
290      } else {
291        // System.out.println ("skipped " + mi
292        //                 + " " + mi.capture_cnt + "/" + mi.call_cnt);
293      }
294    } finally {
295      in_dtrace = false;
296    }
297  }
298
299  /**
300   * Called when a method is exited.
301   *
302   * @param obj receiver of the method that was entered, or null if method is static
303   * @param nonce nonce identifying which enter/exit pair this is
304   * @param mi_index index in methods of the MethodInfo for this method
305   * @param args array of arguments to method
306   * @param ret_val return value of method, or null if method is void
307   * @param exitLineNum the line number at which this method exited
308   */
309  public static synchronized void exit(
310      @Nullable Object obj,
311      int nonce,
312      int mi_index,
313      Object[] args,
314      Object ret_val,
315      int exitLineNum) {
316
317    MethodInfo mi = null;
318    if (debug) {
319      synchronized (SharedData.methods) {
320        mi = SharedData.methods.get(mi_index);
321      }
322      method_indent = method_indent.substring(2);
323      System.out.printf(
324          "%smethod_exit  %s.%s%n", method_indent, mi.class_info.class_name, mi.method_name);
325    }
326
327    if (dontProcessPpts()) {
328      return;
329    }
330
331    // Make sure that the in_dtrace flag matches the stack trace
332    // check_in_dtrace();
333
334    // Ignore this call if we are already processing a dtrace record
335    if (in_dtrace) {
336      return;
337    }
338
339    // Note that we are processing a dtrace record until we return
340    in_dtrace = true;
341    try {
342
343      int num_new_classes = 0;
344      synchronized (SharedData.new_classes) {
345        num_new_classes = SharedData.new_classes.size();
346      }
347      if (num_new_classes > 0) {
348        process_new_classes();
349      }
350
351      // Skip this call if it was not sampled at entry to the method
352      if (sample_start > 0) {
353        CallInfo ci = null;
354        @SuppressWarnings("nullness") // map: key was put in map by enter()
355        @NonNull Deque<CallInfo> callstack = thread_to_callstack.get(Thread.currentThread());
356        while (!callstack.isEmpty()) {
357          ci = callstack.pop();
358          if (ci.nonce == nonce) {
359            break;
360          }
361        }
362        if (ci == null) {
363          synchronized (SharedData.methods) {
364            mi = SharedData.methods.get(mi_index);
365          }
366          System.out.printf("no enter for exit %s%n", mi);
367          return;
368        } else if (!ci.captured) {
369          return;
370        }
371      }
372
373      // Write out the infromation for this method
374      synchronized (SharedData.methods) {
375        mi = SharedData.methods.get(mi_index);
376      }
377      // long start = System.currentTimeMillis();
378      if (mi.member == null) {
379        dtrace_writer.clinitExit(
380            mi.class_info.class_name + ".<clinit>:::EXIT" + exitLineNum, nonce);
381      } else {
382        dtrace_writer.methodExit(mi, nonce, obj, args, ret_val, exitLineNum);
383      }
384      // long duration = System.currentTimeMillis() - start;
385      // System.out.println ("Exit " + mi + " " + duration + "ms");
386    } finally {
387      in_dtrace = false;
388    }
389  }
390
391  /**
392   * Called by classes when they have finished initialization (i.e., their static initializer has
393   * completed).
394   *
395   * <p>This functionality must be enabled by the flag Chicory.checkStaticInit. When enabled, this
396   * method should only be called by the hooks created in the Instrument class.
397   *
398   * @param className fully qualified class name
399   */
400  public static void initNotify(String className) {
401    if (initSet.contains(className)) {
402      throw new Error("initNotify(" + className + ") when initSet already contains " + className);
403    }
404
405    // System.out.println("initialized ---> " + name);
406    initSet.add(className);
407  }
408
409  /**
410   * Return true iff the class with fully qualified name className has been initialized.
411   *
412   * @param className fully qualified class name
413   */
414  public static boolean isInitialized(String className) {
415    return initSet.contains(className);
416  }
417
418  /**
419   * Writes out decl information for any new classes (those in the new_classes field) and removes
420   * them from that list.
421   */
422  @Holding("Runtime.class")
423  public static void process_new_classes() {
424
425    // Processing of the new_classes list must be
426    // very careful, as the call to get_reflection or printDeclClass
427    // may load other classes (which then get added to the list).
428    while (true) {
429
430      // Get the first class in the list (if any)
431      ClassInfo class_info = null;
432      synchronized (SharedData.new_classes) {
433        if (SharedData.new_classes.size() > 0) {
434          class_info = SharedData.new_classes.removeFirst();
435        }
436      }
437      if (class_info == null) {
438        break;
439      }
440
441      if (debug) System.out.println("processing class " + class_info.class_name);
442      if (first_class) {
443        decl_writer.printHeaderInfo(class_info.class_name);
444        first_class = false;
445      }
446      class_info.initViaReflection();
447      // class_info.dump (System.out);
448
449      // Create tree structure for all method entries/exits in the class
450      for (MethodInfo mi : class_info.method_infos) {
451        mi.traversalEnter = RootInfo.enter_process(mi, Runtime.nesting_depth);
452        mi.traversalExit = RootInfo.exit_process(mi, Runtime.nesting_depth);
453      }
454
455      decl_writer.printDeclClass(class_info, comp_info);
456    }
457  }
458
459  /** Increment the number of records that have been printed. */
460  public static void incrementRecords() {
461    printedRecords++;
462
463    // This should only print a percentage if dtraceLimit is not its
464    // default value.
465    // if (printedRecords%1000 == 0)
466    //     System.out.printf("printed=%d, percent printed=%f%n", printedRecords,
467    //                       (float)(100.0*(float)printedRecords/(float)dtraceLimit));
468
469    if (printedRecords >= dtraceLimit) {
470      noMoreOutput();
471    }
472  }
473
474  /**
475   * Indicates that no more output should be printed to the dtrace file. The file is closed and iff
476   * dtraceLimitTerminate is true the program is terminated.
477   */
478  @SuppressWarnings("StaticGuardedByInstance")
479  public static void noMoreOutput() {
480    // The incrementRecords method (which calls this) is called inside a
481    // synchronized block, but re-synchronize just to be sure, or in case
482    // this is called from elsewhere.
483
484    // Runtime.dtrace should be effectively final in that it refers
485    // to the same value throughout the execution of the synchronized
486    // block below (including the lock acquisition).
487    // Unfortunately, the Lock Checker cannot verify this,
488    // so a final local variable is used to satisfy the Lock Checker's
489    // requirement that all variables used as locks be final or
490    // effectively final.  If a bug exists whereby Runtime.dtrace
491    // is not effectively final, this would unfortunately mask that error.
492    final @GuardedBy("<self>") PrintWriter dtrace = Runtime.dtrace;
493
494    synchronized (dtrace) {
495      // The shutdown hook is synchronized on this, so close it up
496      // ourselves, lest the call to System.exit cause deadlock.
497      dtrace.println();
498      dtrace.println("# EOF (added by no_more_output)");
499      dtrace.close();
500
501      // Don't set dtrace to null, because if we continue running, there will
502      // be many attempts to synchronize on it.  (Is that a performance
503      // bottleneck, if we continue running?)
504      // dtrace = null;
505      dtrace_closed = true;
506
507      if (dtraceLimitTerminate) {
508        System.out.println("Printed " + printedRecords + " records to dtrace file.  Exiting.");
509        throw new TerminationMessage(
510            "Printed " + printedRecords + " records to dtrace file.  Exiting.");
511        // System.exit(1);
512      } else {
513        // By default, no special output if the system continues to run.
514        no_dtrace = true;
515      }
516    }
517  }
518
519  @EnsuresNonNull("dtrace")
520  public static void setDtraceOnlineMode(int port) {
521    dtraceLimit = Long.getLong("DTRACELIMIT", Integer.MAX_VALUE).longValue();
522    dtraceLimitTerminate = Boolean.getBoolean("DTRACELIMITTERMINATE");
523
524    Socket daikonSocket;
525    try {
526      daikonSocket = new Socket();
527      @SuppressWarnings("nullness") // unannotated: java.net.Socket is not yet annotated
528      @NonNull SocketAddress dummy = null;
529      daikonSocket.bind(dummy);
530      // System.out.println("Attempting to connect to Daikon on port --- " + port);
531      daikonSocket.connect(new InetSocketAddress(InetAddress.getLocalHost(), port), 5000);
532    } catch (UnknownHostException e) {
533      System.out.println(
534          "UnknownHostException connecting to Daikon : " + e.getMessage() + ". Exiting");
535      System.exit(1);
536      throw new Error("Unreachable control flow");
537    } catch (IOException e) {
538      System.out.println(
539          "IOException, could not connect to Daikon : " + e.getMessage() + ". Exiting");
540      System.exit(1);
541      throw new Error("Unreachable control flow");
542    }
543
544    try {
545      dtrace =
546          new PrintWriter(
547              new BufferedWriter(new OutputStreamWriter(daikonSocket.getOutputStream(), UTF_8)));
548    } catch (IOException e) {
549      System.out.println("IOException connecting to Daikon : " + e.getMessage() + ". Exiting");
550      System.exit(1);
551    }
552
553    if (supportsAddShutdownHook()) {
554      addShutdownHook();
555    } else {
556      System.err.println("Warning: .dtrace file may be incomplete if program is aborted");
557    }
558  }
559
560  /**
561   * Specify the dtrace file to which to write.
562   *
563   * @param filename to use as the data trace file
564   * @param append whether to open dtrace file in append mode
565   */
566  @EnsuresNonNull("dtrace")
567  public static void setDtrace(String filename, boolean append) {
568    if (ChicoryPremain.verbose) {
569      System.out.printf("entered daikon.chicory.Runtime.setDtrace(%s, %b)...%n", filename, append);
570    }
571
572    if (no_dtrace) {
573      throw new Error("setDtrace called when no_dtrace was specified");
574    }
575    File file = new File(filename);
576    File parent = file.getParentFile();
577    if (parent != null) parent.mkdirs();
578    OutputStream os = null; // dummy initialization for compiler's definite assignment check
579    try {
580      os = new FileOutputStream(filename, append);
581      if (filename.endsWith(".gz")) {
582        if (append) {
583          throw new Error(
584              "DTRACEAPPEND environment variable is set, "
585                  + "Cannot append to gzipped dtrace file "
586                  + filename);
587        }
588        os = new GZIPOutputStream(os);
589      }
590      dtraceLimit = Long.getLong("DTRACELIMIT", Integer.MAX_VALUE).longValue();
591      dtraceLimitTerminate = Boolean.getBoolean("DTRACELIMITTERMINATE");
592
593      // System.out.println("limit = " + dtraceLimit + " terminate " + dtraceLimitTerminate);
594
595      // 8192 is the buffer size in BufferedReader
596      BufferedOutputStream bos = new BufferedOutputStream(os, 8192);
597      dtrace = new PrintWriter(new BufferedWriter(new OutputStreamWriter(bos, UTF_8)));
598    } catch (Exception e) {
599      if (os != null) {
600        try {
601          os.close();
602        } catch (IOException e2) {
603          // do nothing, Exception `e` will be thrown below
604        }
605      }
606      e.printStackTrace();
607      throw new Error(e);
608    }
609    if (supportsAddShutdownHook()) {
610      addShutdownHook();
611    } else {
612      System.err.println("Warning: .dtrace file may be incomplete if program is aborted");
613    }
614    // System.out.printf("exited daikon.chicory.Runtime.setDtrace(%s, %b)%n", filename, append);
615  }
616
617  /**
618   * If the current data trace file is not yet set, then set it. The value of the DTRACEFILE
619   * environment variable is used; if that environment variable is not set, then the argument to
620   * this method is used instead.
621   *
622   * @param default_filename the file to maybe use as the data trace file
623   */
624  public static void setDtraceMaybe(String default_filename) {
625    // System.out.println ("Setting dtrace maybe: " + default_filename);
626    if ((dtrace == null) && !no_dtrace) {
627      String filename = System.getProperty("DTRACEFILE", default_filename);
628      boolean append = System.getProperty("DTRACEAPPEND") != null;
629      setDtrace(filename, append);
630    }
631  }
632
633  /**
634   * Returns true if method Thread.addShutdownHook exists.
635   *
636   * @return true if method Thread.addShutdownHook exists
637   */
638  private static boolean supportsAddShutdownHook() {
639    try {
640      Class<java.lang.Runtime> rt = java.lang.Runtime.class;
641      rt.getMethod("addShutdownHook", new Class<?>[] {java.lang.Thread.class});
642      return true;
643    } catch (Exception e) {
644      return false;
645    }
646  }
647
648  /** Add a shutdown hook to close the PrintWriter when the program exits. */
649  private static void addShutdownHook() {
650    java.lang.Runtime.getRuntime()
651        .addShutdownHook(
652            new Thread() {
653              @Override
654              @SuppressWarnings("lock") // non-final field
655              public void run() {
656                if (!dtrace_closed) {
657                  // When the program being instrumented exits, the buffers
658                  // of the "dtrace" (PrintWriter) object are not flushed,
659                  // so we miss the tail of the file.
660
661                  synchronized (Runtime.dtrace) {
662                    dtrace.println();
663                    // These are for debugging, I assume. -MDE
664                    for (Pattern p : ppt_omit_pattern) {
665                      dtrace.println("# ppt-omit-pattern: " + p);
666                    }
667                    for (Pattern p : ppt_select_pattern) {
668                      dtrace.println("# ppt-select-pattern: " + p);
669                    }
670                    // This lets us know we didn't lose any data.
671                    dtrace.println("# EOF (added by Runtime.addShutdownHook)");
672                    dtrace.close();
673                  }
674                }
675
676                if (chicoryLoaderInstantiationError) {
677                  // Warning messages have already been printed.
678                } else if (SharedData.all_classes.size() == 0) {
679                  System.out.println("Chicory warning: No methods were instrumented.");
680                  if (!ppt_select_pattern.isEmpty() || !ppt_omit_pattern.isEmpty()) {
681                    System.out.println(
682                        "Check the --ppt-select-pattern and --ppt-omit-pattern options");
683                  }
684                } else if (printedRecords == 0) {
685                  System.out.println("Chicory warning: no records were printed");
686                }
687              }
688            });
689  }
690
691  /**
692   * Gets the ClassInfo structure corresponding to type. Returns null if the class was not
693   * instrumented.
694   *
695   * @param type declaring class
696   * @return ClassInfo structure corresponding to type
697   */
698  public static @Nullable ClassInfo getClassInfoFromClass(Class<?> type) {
699    try {
700      synchronized (SharedData.all_classes) {
701        for (ClassInfo cinfo : SharedData.all_classes) {
702          if (cinfo.clazz == null) {
703            cinfo.initViaReflection();
704          }
705          if (cinfo.clazz.equals(type)) {
706            return cinfo;
707          }
708        }
709      }
710    } catch (ConcurrentModificationException e) {
711      // occurs if cinfo.get_reflection() causes a new class to be loaded
712      // which causes all_classes to change
713      return getClassInfoFromClass(type);
714    }
715
716    // throw new RuntimeException("Class " + type.getName() + " is not in Runtime's class list");
717    return null;
718  }
719
720  ///////////////////////////////////////////////////////////////////////////
721  /// Wrappers for the various primitive types.
722  /// Used to distinguish wrappers created by user code
723  /// from wrappers created by Chicory.
724
725  public static interface PrimitiveWrapper {
726    // returns corresponding java.lang wrapper
727    public Object getJavaWrapper();
728
729    public Class<?> primitiveClass();
730  }
731
732  /** wrapper used for boolean arguments */
733  public static class BooleanWrap implements PrimitiveWrapper {
734    boolean val;
735
736    public BooleanWrap(boolean val) {
737      this.val = val;
738    }
739
740    @SideEffectFree
741    @Override
742    public String toString(@GuardSatisfied BooleanWrap this) {
743      return Boolean.toString(val);
744    }
745
746    @Override
747    public Boolean getJavaWrapper() {
748      return val;
749    }
750
751    @Override
752    public Class<?> primitiveClass() {
753      return boolean.class;
754    }
755  }
756
757  /** wrapper used for int arguments */
758  public static class ByteWrap implements PrimitiveWrapper {
759    byte val;
760
761    public ByteWrap(byte val) {
762      this.val = val;
763    }
764
765    @SideEffectFree
766    @Override
767    public String toString(@GuardSatisfied ByteWrap this) {
768      return Byte.toString(val);
769    }
770
771    @Override
772    public Byte getJavaWrapper() {
773      return val;
774    }
775
776    @Override
777    public Class<?> primitiveClass() {
778      return byte.class;
779    }
780  }
781
782  /** wrapper used for int arguments */
783  public static class CharWrap implements PrimitiveWrapper {
784    char val;
785
786    public CharWrap(char val) {
787      this.val = val;
788    }
789
790    // Print characters as integers.
791    @SideEffectFree
792    @Override
793    public String toString(@GuardSatisfied CharWrap this) {
794      return Integer.toString(val);
795    }
796
797    @SuppressWarnings("signedness:override.return") // conversion routine
798    @Override
799    public Character getJavaWrapper() {
800      return val;
801    }
802
803    @Override
804    public Class<?> primitiveClass() {
805      return char.class;
806    }
807  }
808
809  /** wrapper used for int arguments */
810  public static class FloatWrap implements PrimitiveWrapper {
811    float val;
812
813    public FloatWrap(float val) {
814      this.val = val;
815    }
816
817    @SideEffectFree
818    @Override
819    public String toString(@GuardSatisfied FloatWrap this) {
820      return Float.toString(val);
821    }
822
823    @Override
824    public Float getJavaWrapper() {
825      return val;
826    }
827
828    @Override
829    public Class<?> primitiveClass() {
830      return float.class;
831    }
832  }
833
834  /** wrapper used for int arguments */
835  public static class IntWrap implements PrimitiveWrapper {
836    int val;
837
838    public IntWrap(int val) {
839      this.val = val;
840    }
841
842    @SideEffectFree
843    @Override
844    public String toString(@GuardSatisfied IntWrap this) {
845      return Integer.toString(val);
846    }
847
848    @Override
849    public Integer getJavaWrapper() {
850      return val;
851    }
852
853    @Override
854    public Class<?> primitiveClass() {
855      return int.class;
856    }
857  }
858
859  /** wrapper used for int arguments */
860  public static class LongWrap implements PrimitiveWrapper {
861    long val;
862
863    public LongWrap(long val) {
864      this.val = val;
865    }
866
867    @SideEffectFree
868    @Override
869    public String toString(@GuardSatisfied LongWrap this) {
870      return Long.toString(val);
871    }
872
873    @Override
874    public Long getJavaWrapper() {
875      return val;
876    }
877
878    @Override
879    public Class<?> primitiveClass() {
880      return long.class;
881    }
882  }
883
884  /** wrapper used for int arguments */
885  public static class ShortWrap implements PrimitiveWrapper {
886    short val;
887
888    public ShortWrap(short val) {
889      this.val = val;
890    }
891
892    @SideEffectFree
893    @Override
894    public String toString(@GuardSatisfied ShortWrap this) {
895      return Short.toString(val);
896    }
897
898    @Override
899    public Short getJavaWrapper() {
900      return val;
901    }
902
903    @Override
904    public Class<?> primitiveClass() {
905      return short.class;
906    }
907  }
908
909  /** wrapper used for double arguments */
910  public static class DoubleWrap implements PrimitiveWrapper {
911    double val;
912
913    public DoubleWrap(double val) {
914      this.val = val;
915    }
916
917    @SideEffectFree
918    @Override
919    public String toString(@GuardSatisfied DoubleWrap this) {
920      return Double.toString(val);
921    }
922
923    @Override
924    public Double getJavaWrapper() {
925      return val;
926    }
927
928    @Override
929    public Class<?> primitiveClass() {
930      return double.class;
931    }
932  }
933
934  ///////////////////////////////////////////////////////////////////////////
935  /// Copied code
936  ///
937
938  // Lifted directly from plume/UtilPlume.java, where it is called
939  // escapeJava(), but repeated here to make this class self-contained.
940  /** Quote \, ", \n, and \r characters in the target; return a new string. */
941  public static String quote(String orig) {
942    StringBuilder sb = new StringBuilder();
943    // The previous escape (or escaped) character was seen right before
944    // this position.  Alternately:  from this character forward, the string
945    // should be copied out verbatim (until the next escaped character).
946    int post_esc = 0;
947    int orig_len = orig.length();
948    for (int i = 0; i < orig_len; i++) {
949      char c = orig.charAt(i);
950      switch (c) {
951        case '\"':
952        case '\\':
953          if (post_esc < i) {
954            sb.append(orig.substring(post_esc, i));
955          }
956          sb.append('\\');
957          post_esc = i;
958          break;
959        case '\n': // not lineSep
960          if (post_esc < i) {
961            sb.append(orig.substring(post_esc, i));
962          }
963          sb.append("\\n"); // not lineSep
964          post_esc = i + 1;
965          break;
966        case '\r':
967          if (post_esc < i) {
968            sb.append(orig.substring(post_esc, i));
969          }
970          sb.append("\\r");
971          post_esc = i + 1;
972          break;
973        default:
974          // Do nothing; i gets incremented.
975      }
976    }
977    if (sb.length() == 0) {
978      return orig;
979    }
980    sb.append(orig.substring(post_esc));
981    return sb.toString();
982  }
983
984  private static HashMap<String, String> primitiveClassesFromJvm = new HashMap<>(8);
985
986  static {
987    primitiveClassesFromJvm.put("Z", "boolean");
988    primitiveClassesFromJvm.put("B", "byte");
989    primitiveClassesFromJvm.put("C", "char");
990    primitiveClassesFromJvm.put("D", "double");
991    primitiveClassesFromJvm.put("F", "float");
992    primitiveClassesFromJvm.put("I", "int");
993    primitiveClassesFromJvm.put("J", "long");
994    primitiveClassesFromJvm.put("S", "short");
995  }
996
997  /**
998   * Convert a classname from JVML format to Java format. For example, convert "[Ljava/lang/Object;"
999   * to "java.lang.Object[]".
1000   *
1001   * <p>If the argument is not a field descriptor, returns it as is. This enables this method to be
1002   * used on the output of {@link Class#getName()}.
1003   */
1004  @SuppressWarnings("signature") // conversion routine
1005  public static String fieldDescriptorToBinaryName(@FieldDescriptor String classname) {
1006
1007    // System.out.println(classname);
1008
1009    int dims = 0;
1010    while (classname.startsWith("[")) {
1011      dims++;
1012      classname = classname.substring(1);
1013    }
1014
1015    String result;
1016    // array of reference type
1017    if (classname.startsWith("L") && classname.endsWith(";")) {
1018      result = classname.substring(1, classname.length() - 1);
1019      result = result.replace('/', '.');
1020    } else {
1021      if (dims > 0) // array of primitives
1022      result = primitiveClassesFromJvm.get(classname);
1023      else {
1024        // just a primitive
1025        result = classname;
1026      }
1027
1028      if (result == null) {
1029        // As a failsafe, use the input; perhaps it is in Java, not JVML,
1030        // format.
1031        result = classname;
1032        // throw new Error("Malformed base class: " + classname);
1033      }
1034    }
1035    for (int i = 0; i < dims; i++) {
1036      result += "[]";
1037    }
1038    return result;
1039  }
1040
1041  @SuppressWarnings("signature") // conversion method
1042  public static final @BinaryName String classGetNameToBinaryName(@ClassGetName String cgn) {
1043    if (cgn.startsWith("[")) {
1044      return fieldDescriptorToBinaryName(cgn);
1045    } else {
1046      return cgn;
1047    }
1048  }
1049
1050  ///////////////////////////////////////////////////////////////////////////
1051  /// end of copied code
1052  ///
1053
1054}