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) {
442        System.out.println("processing class " + class_info.class_name);
443      }
444      if (first_class) {
445        decl_writer.printHeaderInfo(class_info.class_name);
446        first_class = false;
447      }
448      class_info.initViaReflection();
449      // class_info.dump (System.out);
450
451      // Create tree structure for all method entries/exits in the class
452      for (MethodInfo mi : class_info.method_infos) {
453        mi.traversalEnter = RootInfo.enter_process(mi, Runtime.nesting_depth);
454        mi.traversalExit = RootInfo.exit_process(mi, Runtime.nesting_depth);
455      }
456
457      decl_writer.printDeclClass(class_info, comp_info);
458    }
459  }
460
461  /** Increment the number of records that have been printed. */
462  public static void incrementRecords() {
463    printedRecords++;
464
465    // This should only print a percentage if dtraceLimit is not its
466    // default value.
467    // if (printedRecords%1000 == 0)
468    //     System.out.printf("printed=%d, percent printed=%f%n", printedRecords,
469    //                       (float)(100.0*(float)printedRecords/(float)dtraceLimit));
470
471    if (printedRecords >= dtraceLimit) {
472      noMoreOutput();
473    }
474  }
475
476  /**
477   * Indicates that no more output should be printed to the dtrace file. The file is closed and iff
478   * dtraceLimitTerminate is true the program is terminated.
479   */
480  @SuppressWarnings("StaticGuardedByInstance")
481  public static void noMoreOutput() {
482    // The incrementRecords method (which calls this) is called inside a
483    // synchronized block, but re-synchronize just to be sure, or in case
484    // this is called from elsewhere.
485
486    // Runtime.dtrace should be effectively final in that it refers
487    // to the same value throughout the execution of the synchronized
488    // block below (including the lock acquisition).
489    // Unfortunately, the Lock Checker cannot verify this,
490    // so a final local variable is used to satisfy the Lock Checker's
491    // requirement that all variables used as locks be final or
492    // effectively final.  If a bug exists whereby Runtime.dtrace
493    // is not effectively final, this would unfortunately mask that error.
494    final @GuardedBy("<self>") PrintWriter dtrace = Runtime.dtrace;
495
496    synchronized (dtrace) {
497      // The shutdown hook is synchronized on this, so close it up
498      // ourselves, lest the call to System.exit cause deadlock.
499      dtrace.println();
500      dtrace.println("# EOF (added by no_more_output)");
501      dtrace.close();
502
503      // Don't set dtrace to null, because if we continue running, there will
504      // be many attempts to synchronize on it.  (Is that a performance
505      // bottleneck, if we continue running?)
506      // dtrace = null;
507      dtrace_closed = true;
508
509      if (dtraceLimitTerminate) {
510        System.out.println("Printed " + printedRecords + " records to dtrace file.  Exiting.");
511        throw new TerminationMessage(
512            "Printed " + printedRecords + " records to dtrace file.  Exiting.");
513        // System.exit(1);
514      } else {
515        // By default, no special output if the system continues to run.
516        no_dtrace = true;
517      }
518    }
519  }
520
521  @EnsuresNonNull("dtrace")
522  public static void setDtraceOnlineMode(int port) {
523    dtraceLimit = Long.getLong("DTRACELIMIT", Integer.MAX_VALUE).longValue();
524    dtraceLimitTerminate = Boolean.getBoolean("DTRACELIMITTERMINATE");
525
526    Socket daikonSocket;
527    try {
528      daikonSocket = new Socket();
529      @SuppressWarnings("nullness") // unannotated: java.net.Socket is not yet annotated
530      @NonNull SocketAddress dummy = null;
531      daikonSocket.bind(dummy);
532      // System.out.println("Attempting to connect to Daikon on port --- " + port);
533      daikonSocket.connect(new InetSocketAddress(InetAddress.getLocalHost(), port), 5000);
534    } catch (UnknownHostException e) {
535      System.out.println(
536          "UnknownHostException connecting to Daikon : " + e.getMessage() + ". Exiting");
537      System.exit(1);
538      throw new Error("Unreachable control flow");
539    } catch (IOException e) {
540      System.out.println(
541          "IOException, could not connect to Daikon : " + e.getMessage() + ". Exiting");
542      System.exit(1);
543      throw new Error("Unreachable control flow");
544    }
545
546    try {
547      dtrace =
548          new PrintWriter(
549              new BufferedWriter(new OutputStreamWriter(daikonSocket.getOutputStream(), UTF_8)));
550    } catch (IOException e) {
551      System.out.println("IOException connecting to Daikon : " + e.getMessage() + ". Exiting");
552      System.exit(1);
553    }
554
555    if (supportsAddShutdownHook()) {
556      addShutdownHook();
557    } else {
558      System.err.println("Warning: .dtrace file may be incomplete if program is aborted");
559    }
560  }
561
562  /**
563   * Specify the dtrace file to which to write.
564   *
565   * @param filename to use as the data trace file
566   * @param append whether to open dtrace file in append mode
567   */
568  @EnsuresNonNull("dtrace")
569  public static void setDtrace(String filename, boolean append) {
570    if (ChicoryPremain.verbose) {
571      System.out.printf("entered daikon.chicory.Runtime.setDtrace(%s, %b)...%n", filename, append);
572    }
573
574    if (no_dtrace) {
575      throw new Error("setDtrace called when no_dtrace was specified");
576    }
577    File file = new File(filename);
578    File parent = file.getParentFile();
579    if (parent != null) {
580      parent.mkdirs();
581    }
582    OutputStream os = null; // dummy initialization for compiler's definite assignment check
583    try {
584      os = new FileOutputStream(filename, append);
585      if (filename.endsWith(".gz")) {
586        if (append) {
587          throw new Error(
588              "DTRACEAPPEND environment variable is set, "
589                  + "Cannot append to gzipped dtrace file "
590                  + filename);
591        }
592        os = new GZIPOutputStream(os);
593      }
594      dtraceLimit = Long.getLong("DTRACELIMIT", Integer.MAX_VALUE).longValue();
595      dtraceLimitTerminate = Boolean.getBoolean("DTRACELIMITTERMINATE");
596
597      // System.out.println("limit = " + dtraceLimit + " terminate " + dtraceLimitTerminate);
598
599      // 8192 is the buffer size in BufferedReader
600      BufferedOutputStream bos = new BufferedOutputStream(os, 8192);
601      dtrace = new PrintWriter(new BufferedWriter(new OutputStreamWriter(bos, UTF_8)));
602    } catch (Exception e) {
603      if (os != null) {
604        try {
605          os.close();
606        } catch (IOException e2) {
607          // do nothing, Exception `e` will be thrown below
608        }
609      }
610      e.printStackTrace();
611      throw new Error(e);
612    }
613    if (supportsAddShutdownHook()) {
614      addShutdownHook();
615    } else {
616      System.err.println("Warning: .dtrace file may be incomplete if program is aborted");
617    }
618    // System.out.printf("exited daikon.chicory.Runtime.setDtrace(%s, %b)%n", filename, append);
619  }
620
621  /**
622   * If the current data trace file is not yet set, then set it. The value of the DTRACEFILE
623   * environment variable is used; if that environment variable is not set, then the argument to
624   * this method is used instead.
625   *
626   * @param default_filename the file to maybe use as the data trace file
627   */
628  public static void setDtraceMaybe(String default_filename) {
629    // System.out.println ("Setting dtrace maybe: " + default_filename);
630    if ((dtrace == null) && !no_dtrace) {
631      String filename = System.getProperty("DTRACEFILE", default_filename);
632      boolean append = System.getProperty("DTRACEAPPEND") != null;
633      setDtrace(filename, append);
634    }
635  }
636
637  /**
638   * Returns true if method Thread.addShutdownHook exists.
639   *
640   * @return true if method Thread.addShutdownHook exists
641   */
642  private static boolean supportsAddShutdownHook() {
643    try {
644      Class<java.lang.Runtime> rt = java.lang.Runtime.class;
645      rt.getMethod("addShutdownHook", new Class<?>[] {java.lang.Thread.class});
646      return true;
647    } catch (Exception e) {
648      return false;
649    }
650  }
651
652  /** Add a shutdown hook to close the PrintWriter when the program exits. */
653  private static void addShutdownHook() {
654    java.lang.Runtime.getRuntime()
655        .addShutdownHook(
656            new Thread() {
657              @Override
658              @SuppressWarnings("lock") // non-final field
659              public void run() {
660                if (!dtrace_closed) {
661                  // When the program being instrumented exits, the buffers
662                  // of the "dtrace" (PrintWriter) object are not flushed,
663                  // so we miss the tail of the file.
664
665                  synchronized (Runtime.dtrace) {
666                    dtrace.println();
667                    // These are for debugging, I assume. -MDE
668                    for (Pattern p : ppt_omit_pattern) {
669                      dtrace.println("# ppt-omit-pattern: " + p);
670                    }
671                    for (Pattern p : ppt_select_pattern) {
672                      dtrace.println("# ppt-select-pattern: " + p);
673                    }
674                    // This lets us know we didn't lose any data.
675                    dtrace.println("# EOF (added by Runtime.addShutdownHook)");
676                    dtrace.close();
677                  }
678                }
679
680                if (chicoryLoaderInstantiationError) {
681                  // Warning messages have already been printed.
682                } else if (SharedData.all_classes.size() == 0) {
683                  System.out.println("Chicory warning: No methods were instrumented.");
684                  if (!ppt_select_pattern.isEmpty() || !ppt_omit_pattern.isEmpty()) {
685                    System.out.println(
686                        "Check the --ppt-select-pattern and --ppt-omit-pattern options");
687                  }
688                } else if (printedRecords == 0) {
689                  System.out.println("Chicory warning: no records were printed");
690                }
691              }
692            });
693  }
694
695  /**
696   * Gets the ClassInfo structure corresponding to type. Returns null if the class was not
697   * instrumented.
698   *
699   * @param type declaring class
700   * @return ClassInfo structure corresponding to type
701   */
702  public static @Nullable ClassInfo getClassInfoFromClass(Class<?> type) {
703    try {
704      synchronized (SharedData.all_classes) {
705        for (ClassInfo cinfo : SharedData.all_classes) {
706          if (cinfo.clazz == null) {
707            cinfo.initViaReflection();
708          }
709          if (cinfo.clazz.equals(type)) {
710            return cinfo;
711          }
712        }
713      }
714    } catch (ConcurrentModificationException e) {
715      // occurs if cinfo.get_reflection() causes a new class to be loaded
716      // which causes all_classes to change
717      return getClassInfoFromClass(type);
718    }
719
720    // throw new RuntimeException("Class " + type.getName() + " is not in Runtime's class list");
721    return null;
722  }
723
724  ///////////////////////////////////////////////////////////////////////////
725  /// Wrappers for the various primitive types.
726  /// Used to distinguish wrappers created by user code
727  /// from wrappers created by Chicory.
728
729  public static interface PrimitiveWrapper {
730    // returns corresponding java.lang wrapper
731    public Object getJavaWrapper();
732
733    public Class<?> primitiveClass();
734  }
735
736  /** wrapper used for boolean arguments. */
737  public static class BooleanWrap implements PrimitiveWrapper {
738    boolean val;
739
740    public BooleanWrap(boolean val) {
741      this.val = val;
742    }
743
744    @SideEffectFree
745    @Override
746    public String toString(@GuardSatisfied BooleanWrap this) {
747      return Boolean.toString(val);
748    }
749
750    @Override
751    public Boolean getJavaWrapper() {
752      return val;
753    }
754
755    @Override
756    public Class<?> primitiveClass() {
757      return boolean.class;
758    }
759  }
760
761  /** wrapper used for int arguments. */
762  public static class ByteWrap implements PrimitiveWrapper {
763    byte val;
764
765    public ByteWrap(byte val) {
766      this.val = val;
767    }
768
769    @SideEffectFree
770    @Override
771    public String toString(@GuardSatisfied ByteWrap this) {
772      return Byte.toString(val);
773    }
774
775    @Override
776    public Byte getJavaWrapper() {
777      return val;
778    }
779
780    @Override
781    public Class<?> primitiveClass() {
782      return byte.class;
783    }
784  }
785
786  /** wrapper used for int arguments. */
787  public static class CharWrap implements PrimitiveWrapper {
788    char val;
789
790    public CharWrap(char val) {
791      this.val = val;
792    }
793
794    // Print characters as integers.
795    @SideEffectFree
796    @Override
797    public String toString(@GuardSatisfied CharWrap this) {
798      return Integer.toString(val);
799    }
800
801    @Override
802    public Character getJavaWrapper() {
803      return val;
804    }
805
806    @Override
807    public Class<?> primitiveClass() {
808      return char.class;
809    }
810  }
811
812  /** wrapper used for int arguments. */
813  public static class FloatWrap implements PrimitiveWrapper {
814    float val;
815
816    public FloatWrap(float val) {
817      this.val = val;
818    }
819
820    @SideEffectFree
821    @Override
822    public String toString(@GuardSatisfied FloatWrap this) {
823      return Float.toString(val);
824    }
825
826    @Override
827    public Float getJavaWrapper() {
828      return val;
829    }
830
831    @Override
832    public Class<?> primitiveClass() {
833      return float.class;
834    }
835  }
836
837  /** wrapper used for int arguments. */
838  public static class IntWrap implements PrimitiveWrapper {
839    int val;
840
841    public IntWrap(int val) {
842      this.val = val;
843    }
844
845    @SideEffectFree
846    @Override
847    public String toString(@GuardSatisfied IntWrap this) {
848      return Integer.toString(val);
849    }
850
851    @Override
852    public Integer getJavaWrapper() {
853      return val;
854    }
855
856    @Override
857    public Class<?> primitiveClass() {
858      return int.class;
859    }
860  }
861
862  /** wrapper used for int arguments. */
863  public static class LongWrap implements PrimitiveWrapper {
864    long val;
865
866    public LongWrap(long val) {
867      this.val = val;
868    }
869
870    @SideEffectFree
871    @Override
872    public String toString(@GuardSatisfied LongWrap this) {
873      return Long.toString(val);
874    }
875
876    @Override
877    public Long getJavaWrapper() {
878      return val;
879    }
880
881    @Override
882    public Class<?> primitiveClass() {
883      return long.class;
884    }
885  }
886
887  /** wrapper used for int arguments. */
888  public static class ShortWrap implements PrimitiveWrapper {
889    short val;
890
891    public ShortWrap(short val) {
892      this.val = val;
893    }
894
895    @SideEffectFree
896    @Override
897    public String toString(@GuardSatisfied ShortWrap this) {
898      return Short.toString(val);
899    }
900
901    @Override
902    public Short getJavaWrapper() {
903      return val;
904    }
905
906    @Override
907    public Class<?> primitiveClass() {
908      return short.class;
909    }
910  }
911
912  /** Wrapper used for double arguments. */
913  public static class DoubleWrap implements PrimitiveWrapper {
914    double val;
915
916    public DoubleWrap(double val) {
917      this.val = val;
918    }
919
920    @SideEffectFree
921    @Override
922    public String toString(@GuardSatisfied DoubleWrap this) {
923      return Double.toString(val);
924    }
925
926    @Override
927    public Double getJavaWrapper() {
928      return val;
929    }
930
931    @Override
932    public Class<?> primitiveClass() {
933      return double.class;
934    }
935  }
936
937  ///////////////////////////////////////////////////////////////////////////
938  /// Copied code
939  ///
940
941  // Lifted directly from plume/UtilPlume.java, where it is called
942  // escapeJava(), but repeated here to make this class self-contained.
943  /** Quote \, ", \n, and \r characters in the target; return a new string. */
944  public static String quote(String orig) {
945    StringBuilder sb = new StringBuilder();
946    // The previous escape (or escaped) character was seen right before
947    // this position.  Alternately:  from this character forward, the string
948    // should be copied out verbatim (until the next escaped character).
949    int post_esc = 0;
950    int orig_len = orig.length();
951    for (int i = 0; i < orig_len; i++) {
952      char c = orig.charAt(i);
953      switch (c) {
954        case '\"':
955        case '\\':
956          if (post_esc < i) {
957            sb.append(orig.substring(post_esc, i));
958          }
959          sb.append('\\');
960          post_esc = i;
961          break;
962        case '\n': // not lineSep
963          if (post_esc < i) {
964            sb.append(orig.substring(post_esc, i));
965          }
966          sb.append("\\n"); // not lineSep
967          post_esc = i + 1;
968          break;
969        case '\r':
970          if (post_esc < i) {
971            sb.append(orig.substring(post_esc, i));
972          }
973          sb.append("\\r");
974          post_esc = i + 1;
975          break;
976        default:
977          // Do nothing; i gets incremented.
978      }
979    }
980    if (sb.length() == 0) {
981      return orig;
982    }
983    sb.append(orig.substring(post_esc));
984    return sb.toString();
985  }
986
987  private static HashMap<String, String> primitiveClassesFromJvm = new HashMap<>(8);
988
989  static {
990    primitiveClassesFromJvm.put("Z", "boolean");
991    primitiveClassesFromJvm.put("B", "byte");
992    primitiveClassesFromJvm.put("C", "char");
993    primitiveClassesFromJvm.put("D", "double");
994    primitiveClassesFromJvm.put("F", "float");
995    primitiveClassesFromJvm.put("I", "int");
996    primitiveClassesFromJvm.put("J", "long");
997    primitiveClassesFromJvm.put("S", "short");
998  }
999
1000  /**
1001   * Convert a classname from JVML format to Java format. For example, convert "[Ljava/lang/Object;"
1002   * to "java.lang.Object[]".
1003   *
1004   * <p>If the argument is not a field descriptor, returns it as is. This enables this method to be
1005   * used on the output of {@link Class#getName()}.
1006   */
1007  @SuppressWarnings("signature") // conversion routine
1008  public static String fieldDescriptorToBinaryName(@FieldDescriptor String classname) {
1009
1010    // System.out.println(classname);
1011
1012    int dims = 0;
1013    while (classname.startsWith("[")) {
1014      dims++;
1015      classname = classname.substring(1);
1016    }
1017
1018    String result;
1019    // array of reference type
1020    if (classname.startsWith("L") && classname.endsWith(";")) {
1021      result = classname.substring(1, classname.length() - 1);
1022      result = result.replace('/', '.');
1023    } else {
1024      if (dims > 0) { // array of primitives
1025        result = primitiveClassesFromJvm.get(classname);
1026      } else {
1027        // just a primitive
1028        result = classname;
1029      }
1030
1031      if (result == null) {
1032        // As a failsafe, use the input; perhaps it is in Java, not JVML,
1033        // format.
1034        result = classname;
1035        // throw new Error("Malformed base class: " + classname);
1036      }
1037    }
1038    for (int i = 0; i < dims; i++) {
1039      result += "[]";
1040    }
1041    return result;
1042  }
1043
1044  @SuppressWarnings("signature") // conversion method
1045  public static final @BinaryName String classGetNameToBinaryName(@ClassGetName String cgn) {
1046    if (cgn.startsWith("[")) {
1047      return fieldDescriptorToBinaryName(cgn);
1048    } else {
1049      return cgn;
1050    }
1051  }
1052
1053  ///////////////////////////////////////////////////////////////////////////
1054  /// end of copied code
1055  ///
1056
1057}