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}