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}