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 /** A wrapper for a pritive class. */ 730 public static interface PrimitiveWrapper { 731 // returns corresponding java.lang wrapper 732 public Object getJavaWrapper(); 733 734 public Class<?> primitiveClass(); 735 } 736 737 /** wrapper used for boolean arguments. */ 738 public static class BooleanWrap implements PrimitiveWrapper { 739 boolean val; 740 741 public BooleanWrap(boolean val) { 742 this.val = val; 743 } 744 745 @SideEffectFree 746 @Override 747 public String toString(@GuardSatisfied BooleanWrap this) { 748 return Boolean.toString(val); 749 } 750 751 @Override 752 public Boolean getJavaWrapper() { 753 return val; 754 } 755 756 @Override 757 public Class<?> primitiveClass() { 758 return boolean.class; 759 } 760 } 761 762 /** wrapper used for int arguments. */ 763 public static class ByteWrap implements PrimitiveWrapper { 764 byte val; 765 766 public ByteWrap(byte val) { 767 this.val = val; 768 } 769 770 @SideEffectFree 771 @Override 772 public String toString(@GuardSatisfied ByteWrap this) { 773 return Byte.toString(val); 774 } 775 776 @Override 777 public Byte getJavaWrapper() { 778 return val; 779 } 780 781 @Override 782 public Class<?> primitiveClass() { 783 return byte.class; 784 } 785 } 786 787 /** wrapper used for int arguments. */ 788 public static class CharWrap implements PrimitiveWrapper { 789 char val; 790 791 public CharWrap(char val) { 792 this.val = val; 793 } 794 795 // Print characters as integers. 796 @SideEffectFree 797 @Override 798 public String toString(@GuardSatisfied CharWrap this) { 799 return Integer.toString(val); 800 } 801 802 @Override 803 public Character getJavaWrapper() { 804 return val; 805 } 806 807 @Override 808 public Class<?> primitiveClass() { 809 return char.class; 810 } 811 } 812 813 /** wrapper used for int arguments. */ 814 public static class FloatWrap implements PrimitiveWrapper { 815 float val; 816 817 public FloatWrap(float val) { 818 this.val = val; 819 } 820 821 @SideEffectFree 822 @Override 823 public String toString(@GuardSatisfied FloatWrap this) { 824 return Float.toString(val); 825 } 826 827 @Override 828 public Float getJavaWrapper() { 829 return val; 830 } 831 832 @Override 833 public Class<?> primitiveClass() { 834 return float.class; 835 } 836 } 837 838 /** wrapper used for int arguments. */ 839 public static class IntWrap implements PrimitiveWrapper { 840 int val; 841 842 public IntWrap(int val) { 843 this.val = val; 844 } 845 846 @SideEffectFree 847 @Override 848 public String toString(@GuardSatisfied IntWrap this) { 849 return Integer.toString(val); 850 } 851 852 @Override 853 public Integer getJavaWrapper() { 854 return val; 855 } 856 857 @Override 858 public Class<?> primitiveClass() { 859 return int.class; 860 } 861 } 862 863 /** wrapper used for int arguments. */ 864 public static class LongWrap implements PrimitiveWrapper { 865 long val; 866 867 public LongWrap(long val) { 868 this.val = val; 869 } 870 871 @SideEffectFree 872 @Override 873 public String toString(@GuardSatisfied LongWrap this) { 874 return Long.toString(val); 875 } 876 877 @Override 878 public Long getJavaWrapper() { 879 return val; 880 } 881 882 @Override 883 public Class<?> primitiveClass() { 884 return long.class; 885 } 886 } 887 888 /** wrapper used for int arguments. */ 889 public static class ShortWrap implements PrimitiveWrapper { 890 short val; 891 892 public ShortWrap(short val) { 893 this.val = val; 894 } 895 896 @SideEffectFree 897 @Override 898 public String toString(@GuardSatisfied ShortWrap this) { 899 return Short.toString(val); 900 } 901 902 @Override 903 public Short getJavaWrapper() { 904 return val; 905 } 906 907 @Override 908 public Class<?> primitiveClass() { 909 return short.class; 910 } 911 } 912 913 /** Wrapper used for double arguments. */ 914 public static class DoubleWrap implements PrimitiveWrapper { 915 double val; 916 917 public DoubleWrap(double val) { 918 this.val = val; 919 } 920 921 @SideEffectFree 922 @Override 923 public String toString(@GuardSatisfied DoubleWrap this) { 924 return Double.toString(val); 925 } 926 927 @Override 928 public Double getJavaWrapper() { 929 return val; 930 } 931 932 @Override 933 public Class<?> primitiveClass() { 934 return double.class; 935 } 936 } 937 938 // /////////////////////////////////////////////////////////////////////////// 939 // Copied code 940 // 941 942 // Lifted directly from plume/UtilPlume.java, where it is called 943 // escapeJava(), but repeated here to make this class self-contained. 944 /** Quote \, ", \n, and \r characters in the target; return a new string. */ 945 public static String quote(String orig) { 946 StringBuilder sb = new StringBuilder(); 947 // The previous escape (or escaped) character was seen right before 948 // this position. Alternately: from this character forward, the string 949 // should be copied out verbatim (until the next escaped character). 950 int post_esc = 0; 951 int orig_len = orig.length(); 952 for (int i = 0; i < orig_len; i++) { 953 char c = orig.charAt(i); 954 switch (c) { 955 case '\"': 956 case '\\': 957 if (post_esc < i) { 958 sb.append(orig.substring(post_esc, i)); 959 } 960 sb.append('\\'); 961 post_esc = i; 962 break; 963 case '\n': // not lineSep 964 if (post_esc < i) { 965 sb.append(orig.substring(post_esc, i)); 966 } 967 sb.append("\\n"); // not lineSep 968 post_esc = i + 1; 969 break; 970 case '\r': 971 if (post_esc < i) { 972 sb.append(orig.substring(post_esc, i)); 973 } 974 sb.append("\\r"); 975 post_esc = i + 1; 976 break; 977 default: 978 // Do nothing; i gets incremented. 979 } 980 } 981 if (sb.length() == 0) { 982 return orig; 983 } 984 sb.append(orig.substring(post_esc)); 985 return sb.toString(); 986 } 987 988 private static HashMap<String, String> primitiveClassesFromJvm = new HashMap<>(8); 989 990 static { 991 primitiveClassesFromJvm.put("Z", "boolean"); 992 primitiveClassesFromJvm.put("B", "byte"); 993 primitiveClassesFromJvm.put("C", "char"); 994 primitiveClassesFromJvm.put("D", "double"); 995 primitiveClassesFromJvm.put("F", "float"); 996 primitiveClassesFromJvm.put("I", "int"); 997 primitiveClassesFromJvm.put("J", "long"); 998 primitiveClassesFromJvm.put("S", "short"); 999 } 1000 1001 /** 1002 * Convert a classname from JVML format to Java format. For example, convert "[Ljava/lang/Object;" 1003 * to "java.lang.Object[]". 1004 * 1005 * <p>If the argument is not a field descriptor, returns it as is. This enables this method to be 1006 * used on the output of {@link Class#getName()}. 1007 */ 1008 @SuppressWarnings("signature") // conversion routine 1009 public static String fieldDescriptorToBinaryName(@FieldDescriptor String classname) { 1010 1011 // System.out.println(classname); 1012 1013 int dims = 0; 1014 while (classname.startsWith("[")) { 1015 dims++; 1016 classname = classname.substring(1); 1017 } 1018 1019 String result; 1020 // array of reference type 1021 if (classname.startsWith("L") && classname.endsWith(";")) { 1022 result = classname.substring(1, classname.length() - 1); 1023 result = result.replace('/', '.'); 1024 } else { 1025 if (dims > 0) { // array of primitives 1026 result = primitiveClassesFromJvm.get(classname); 1027 } else { 1028 // just a primitive 1029 result = classname; 1030 } 1031 1032 if (result == null) { 1033 // As a failsafe, use the input; perhaps it is in Java, not JVML, 1034 // format. 1035 result = classname; 1036 // throw new Error("Malformed base class: " + classname); 1037 } 1038 } 1039 for (int i = 0; i < dims; i++) { 1040 result += "[]"; 1041 } 1042 return result; 1043 } 1044 1045 @SuppressWarnings("signature") // conversion method 1046 public static final @BinaryName String classGetNameToBinaryName(@ClassGetName String cgn) { 1047 if (cgn.startsWith("[")) { 1048 return fieldDescriptorToBinaryName(cgn); 1049 } else { 1050 return cgn; 1051 } 1052 } 1053 1054 // /////////////////////////////////////////////////////////////////////////// 1055 // end of copied code 1056 // 1057 1058}