001//- Copyright (C) 2014 James Riely, DePaul University 002//- Copyright (C) 2004 John Hamer, University of Auckland [Graphviz code] 003//- 004//- This program is free software; you can redistribute it and/or 005//- modify it under the terms of the GNU General Public License 006//- as published by the Free Software Foundation; either version 2 007//- of the License, or (at your option) any later version. 008//- 009//- This program is distributed in the hope that it will be useful, 010//- but WITHOUT ANY WARRANTY; without even the implied warranty of 011//- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012//- GNU General Public License for more details. 013//- 014//- You should have received a copy of the GNU General Public License along 015//- with this program; if not, write to the Free Software Foundation, Inc., 016//- 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.package algs; 017 018//- Event monitoring code based on 019//- http://fivedots.coe.psu.ac.th/~ad/jg/javaArt5/ 020//- By Andrew Davison, [email protected], March 2009 021//- 022//- Graphviz code based on LJV 023//- https://www.cs.auckland.ac.nz/~j-hamer/ 024//- By John Hamer, <[email protected]>, 2003 025//- Copyright (C) 2004 John Hamer, University of Auckland 026 027package stdlib; 028 029import java.io.*; 030import java.util.ArrayList; 031import java.util.HashMap; 032import java.util.HashSet; 033import java.util.Iterator; 034import java.util.LinkedList; 035import java.util.List; 036import java.util.Map; 037import java.util.NoSuchElementException; 038import java.util.Set; 039import java.util.TreeMap; 040import java.util.function.Consumer; 041import com.sun.jdi.*; 042import com.sun.jdi.connect.*; 043import com.sun.jdi.event.*; 044import com.sun.jdi.request.*; 045 046/** 047 * <p> 048 * Traces the execution of a target program. 049 * </p><p> 050 * See <a href="http://fpl.cs.depaul.edu/jriely/visualization/">http://fpl.cs.depaul.edu/jriely/visualization/</a> 051 * </p><p> 052 * Command-line usage: java Trace [OptionalJvmArguments] fullyQualifiedClassName 053 * </p><p> 054 * Starts a new JVM (java virtual machine) running the main program in 055 * fullyQualifiedClassName, then traces it's behavior. The OptionalJvmArguments 056 * are passed to this underlying JVM. 057 * </p><p> 058 * Example usages: 059 * </p><pre> 060 * java Trace MyClass 061 * java Trace mypackage.MyClass 062 * java Trace -cp ".:/pathTo/Library.jar" mypackage.MyClass // mac/linux 063 * java Trace -cp ".;/pathTo/Library.jar" mypackage.MyClass // windows 064 * </pre><p> 065 * Two types of display are support: console and graphviz In order to use 066 * graphziv, you must install http://www.graphviz.org/ and perhaps call 067 * graphvizAddPossibleDotLocation to include the location of the "dot" 068 * executable. 069 * </p><p> 070 * You can either draw the state at each step --- <code>drawSteps()</code> --- or 071 * you can draw states selectively by calling <code>Trace.draw()</code> from the program 072 * you are tracing. See the example in <code>ZTraceExample.java</code>. 073 * </p> 074 * @author James Riely, [email protected], 2014-2015 075 */ 076 /* !!!!!!!!!!!!!!!!!!!!!!!! COMPILATION !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 077 * 078 * This class requires Java 8. It also requires the file tools.jar, which comes 079 * with the JDK (not the JRE) 080 * 081 * On windows, you can find it in 082 * 083 * <pre> 084 * C:\Program Files\Java\jdk...\lib\tools.jar 085 * </pre> 086 * 087 * On mac, look in 088 * 089 * <pre> 090 * /Library/Java/JavaVirtualMachines/jdk.../Contents/Home/lib/tools.jar 091 * </pre> 092 * 093 * To put this in your eclipse build path, select your project in the package 094 * explorer, then: 095 * 096 * <pre> 097 * Project > Properties > Java Build Path > Libraries > Add External Library 098 * </pre> 099 * 100 */ 101 102// TODO: Refactor to use GraphvizBuilder 103public class Trace { 104 private Trace () {} // noninstantiable class 105 protected static final String CALLBACK_CLASS_NAME = Trace.class.getCanonicalName (); 106 protected static final String GRAPHVIZ_CLASS_NAME = Graphviz.class.getCanonicalName (); 107 108 /** 109 * Draw the given object. 110 * 111 * This is a stub method, which is trapped by the debugger. It only has an 112 * effect if a variant of Trace.run() has been called to start the debugger 113 * and Trace.drawSteps() is false. 114 */ 115 public static void drawObject (Object object) { } // See Printer. methodEntryEvent 116 protected static final String CALLBACK_DRAW_OBJECT = "drawObject"; 117 protected static final HashSet<String> CALLBACKS = new HashSet<> (); 118 static { CALLBACKS.add (CALLBACK_DRAW_OBJECT); } 119 /** 120 * Draw the given object, labeling it with the given name. 121 * 122 * This is a stub method, which is trapped by the debugger. It only has an 123 * effect if a variant of Trace.run() has been called to start the debugger 124 * and Trace.drawSteps() is false. 125 */ 126 public static void drawObjectWithName (String name, Object object) { } // See Printer. methodEntryEvent 127 protected static final String CALLBACK_DRAW_OBJECT_NAMED = "drawObjectWithName"; 128 static { CALLBACKS.add (CALLBACK_DRAW_OBJECT_NAMED); } 129 /** 130 * Draw the given objects. 131 * 132 * This is a stub method, which is trapped by the debugger. It only has an 133 * effect if a variant of Trace.run() has been called to start the debugger 134 * and Trace.drawSteps() is false. 135 */ 136 public static void drawObjects (Object... objects) { } // See Printer. methodEntryEvent 137 protected static final String CALLBACK_DRAW_OBJECTS = "drawObjects"; 138 static { CALLBACKS.add (CALLBACK_DRAW_OBJECTS); } 139 /** 140 * Draw the given objects, labeling them with the given names. The array of 141 * namesAndObjects must alternate between names and objects, as in 142 * Trace.drawObjects("x", x, "y" y). 143 * 144 * This is a stub method, which is trapped by the debugger. It only has an 145 * effect if a variant of Trace.run() has been called to start the debugger 146 * and Trace.drawSteps() is false. 147 */ 148 public static void drawObjectsWithNames (Object... namesAndObjects) { } // See Printer. methodEntryEvent 149 protected static final String CALLBACK_DRAW_OBJECTS_NAMED = "drawObjectsWithNames"; 150 static { CALLBACKS.add (CALLBACK_DRAW_OBJECTS_NAMED); } 151 152 /** 153 * Draw the current frame, as well as all reachable objects. 154 * 155 * This is a stub method, which is trapped by the debugger. It only has an 156 * effect if a variant of Trace.run() has been called to start the debugger 157 * and Trace.drawSteps() is false. 158 */ 159 public static void drawThisFrame () { } // See Printer. methodEntryEvent 160 protected static final String CALLBACK_DRAW_THIS_FRAME = "drawThisFrame"; 161 static { CALLBACKS.add (CALLBACK_DRAW_THIS_FRAME); } 162 163 /** 164 * Stop drawing steps. 165 * 166 * This is a stub method, which is trapped by the debugger. It only has an 167 * effect if a variant of Trace.run() has been called to start the debugger. 168 */ 169 public static void drawStepsEnd () { } // See Printer. methodEntryEvent 170 protected static final String CALLBACK_DRAW_STEPS_END = "drawStepsEnd"; 171 static { CALLBACKS.add (CALLBACK_DRAW_STEPS_END); } 172 173 /** 174 * Start drawing steps. 175 * 176 * This is a stub method, which is trapped by the debugger. It only has an 177 * effect if a variant of Trace.run() has been called to start the debugger. 178 */ 179 public static void drawSteps () { } // See Printer. methodEntryEvent 180 protected static final String CALLBACK_DRAW_STEPS_BEGIN = "drawSteps"; 181 static { CALLBACKS.add (CALLBACK_DRAW_STEPS_BEGIN); } 182 183 /** 184 * Draw all stack frames and static variables, as well as all reachable 185 * objects. 186 * 187 * This is a stub method, which is trapped by the debugger. It only has an 188 * effect if a variant of Trace.run() has been called to start the debugger 189 * and Trace.drawSteps() is false. 190 */ 191 public static void draw () { } // See Printer. methodEntryEvent 192 protected static final String CALLBACK_DRAW_ALL_FRAMES = "draw"; 193 static { CALLBACKS.add (CALLBACK_DRAW_ALL_FRAMES); } 194 195 /** 196 * Clear the call tree, removing all previous entries. 197 * 198 * This is a stub method, which is trapped by the debugger. It only has an 199 * effect if a variant of Trace.run() has been called to start the debugger. 200 */ 201 public static void clearCallTree () { } // See Printer. methodEntryEvent 202 protected static final String CALLBACK_CLEAR_CALL_TREE = "clearCallTree"; 203 204 // Basic graphviz options 205 /** 206 * Run graphviz "dot" program to produce an output file (default==true). If 207 * false, a graphviz source file is created, but no graphic file is 208 * generated. 209 */ 210 public static void graphvizRunGraphviz (boolean value) { 211 GRAPHVIZ_RUN_GRAPHVIZ = value; 212 } 213 protected static boolean GRAPHVIZ_RUN_GRAPHVIZ = true; 214 215 /** 216 * The graphviz format -- see http://www.graphviz.org/doc/info/output.html . 217 * (default=="png"). 218 */ 219 public static void graphvizOutputFormat (String value) { 220 GRAPHVIZ_OUTPUT_FORMAT = value; 221 } 222 protected static String GRAPHVIZ_OUTPUT_FORMAT = "png"; 223 224 /** 225 * Sets the graphviz output directory. 226 * Creates the directory if necessary. 227 * Relative pathnames are interpreted with respect to the user's home directory. 228 * Default is "Desktop". 229 */ 230 public static void setGraphizOutputDir (String dirName) { 231 GRAPHVIZ_DIR = dirName; 232 } 233 private static String GRAPHVIZ_DIR = "Desktop"; 234 235 /** 236 * Sets the console output to the given filename. Console output will be 237 * written to: 238 * 239 * <pre> 240 * (user home directory)/(filename) 241 * </pre> 242 */ 243 public static void setConsoleFilenameRelativeToUserHome (String filename) { 244 setConsoleFilename (System.getProperty ("user.home") + File.separator + filename); 245 } 246 /** 247 * Sets the console output to the given filename. 248 */ 249 public static void setConsoleFilename (String filename) { 250 Printer.setFilename (filename); 251 } 252 /** 253 * Sets the console output to the default (the terminal). 254 */ 255 public static void setConsoleFilename () { 256 Printer.setFilename (); 257 } 258 259 // Basic options -- 260 protected static boolean GRAPHVIZ_SHOW_STEPS = false; 261// protected static void drawStepsOf (String className, String methodName) { 262// GRAPHVIZ_SHOW_STEPS = true; 263// if (GRAPHVIZ_SHOW_STEPS_OF == null) 264// GRAPHVIZ_SHOW_STEPS_OF = new HashSet<>(); 265// GRAPHVIZ_SHOW_STEPS_OF.add (new OptionalClassNameWithRequiredMethodName (className, methodName)); 266// REPRESS_RETURN_ON_GRAPHVIZ_SHOW_STEPS_OF = (GRAPHVIZ_SHOW_STEPS_OF.size () <= 1); 267// } 268 /** 269 * Create a new graphviz drawing for every step of the named method. 270 * The methodName does not include parameters. 271 * In order to show a constructor, use the method name "<init>". 272 */ 273 public static void drawStepsOfMethod (String methodName) { } // See Printer. methodEntryEvent 274 /** 275 * Create a new graphviz drawing for every step of the named methods. 276 * The methodName does not include parameters. 277 * In order to show a constructor, use the method name "<init>". 278 */ 279 public static void drawStepsOfMethods (String... methodName) { } // See Printer. methodEntryEvent 280 protected static final String CALLBACK_DRAW_STEPS_OF_METHOD = "drawStepsOfMethod"; 281 static { CALLBACKS.add (CALLBACK_DRAW_STEPS_OF_METHOD); } 282 protected static final String CALLBACK_DRAW_STEPS_OF_METHODS = "drawStepsOfMethods"; 283 static { CALLBACKS.add (CALLBACK_DRAW_STEPS_OF_METHODS); } 284 protected static void drawStepsOfMethodBegin (String methodName) { 285 GRAPHVIZ_SHOW_STEPS = true; 286 if (GRAPHVIZ_SHOW_STEPS_OF == null) 287 GRAPHVIZ_SHOW_STEPS_OF = new HashSet<>(); 288 GRAPHVIZ_SHOW_STEPS_OF.add (new OptionalClassNameWithRequiredMethodName (null, methodName)); 289 REPRESS_RETURN_ON_GRAPHVIZ_SHOW_STEPS_OF = (GRAPHVIZ_SHOW_STEPS_OF.size () <= 1); 290 } 291 protected static void drawStepsOfMethodEnd () { 292 GRAPHVIZ_SHOW_STEPS = true; 293 GRAPHVIZ_SHOW_STEPS_OF = new HashSet<>(); 294 REPRESS_RETURN_ON_GRAPHVIZ_SHOW_STEPS_OF = false; 295 } 296 297 protected static boolean drawStepsOfInternal (ThreadReference thr) { 298 if (GRAPHVIZ_SHOW_STEPS_OF == null) return true; 299 List<StackFrame> frames = null; 300 try { frames = thr.frames (); } catch (IncompatibleThreadStateException e) { } 301 return Trace.drawStepsOfInternal (frames, null); 302 } 303 protected static boolean drawStepsOfInternal (List<StackFrame> frames, Value returnVal) { 304 if (GRAPHVIZ_SHOW_STEPS_OF == null) return true; 305 if (frames == null) return true; 306 if (REPRESS_RETURN_ON_GRAPHVIZ_SHOW_STEPS_OF && returnVal != null && !(returnVal instanceof VoidValue)) return false; 307 StackFrame currentFrame = frames.get (0); 308 String className = currentFrame.location ().declaringType ().name (); 309 String methodName = currentFrame.location ().method ().name (); 310 return Trace.drawStepsOfInternal (className, methodName); 311 } 312 protected static boolean drawStepsOfInternal (String className, String methodName) { 313 if (GRAPHVIZ_SHOW_STEPS_OF == null) return true; 314 //System.err.println (className + "." + methodName + " " + new OptionalClassNameWithRequiredMethodName(className, methodName).hashCode() + " "+ GRAPHVIZ_SHOW_STEPS_OF); 315 return GRAPHVIZ_SHOW_STEPS_OF.contains (new OptionalClassNameWithRequiredMethodName(className, methodName)); 316 } 317 protected static Set<OptionalClassNameWithRequiredMethodName> GRAPHVIZ_SHOW_STEPS_OF = null; 318 private static boolean REPRESS_RETURN_ON_GRAPHVIZ_SHOW_STEPS_OF = false; 319 protected static class OptionalClassNameWithRequiredMethodName { 320 String className; 321 String methodName; 322 public OptionalClassNameWithRequiredMethodName (String className, String methodName) { 323 this.className = className; 324 this.methodName = methodName; 325 } 326 public String toString () { 327 return className + "." + methodName; 328 } 329 public boolean equals (Object other) { 330 //System.err.println (this + "==" + other); 331 if (other == this) return true; 332 if (other == null) return false; 333 if (other.getClass () != this.getClass ()) return false; 334 OptionalClassNameWithRequiredMethodName that = (OptionalClassNameWithRequiredMethodName) other; 335 if (this.className != null && that.className != null) { 336 if (! this.className.equals (that.className)) return false; 337 } 338 if (! this.methodName.equals (that.methodName)) return false; 339 return true; 340 } 341 public int hashCode() { return methodName.hashCode (); } 342 } 343 /** 344 * Show events on the console (default==false). 345 */ 346 public static void consoleShow (boolean value) { 347 CONSOLE_SHOW_THREADS = value; 348 CONSOLE_SHOW_CLASSES = value; 349 CONSOLE_SHOW_CALLS = value; 350 CONSOLE_SHOW_STEPS = value; 351 CONSOLE_SHOW_VARIABLES = value; 352 CONSOLE_SHOW_STEPS_VERBOSE = false; 353 } 354 /** 355 * Show events on the console, including code (default==false). 356 */ 357 public static void consoleShowVerbose (boolean value) { 358 CONSOLE_SHOW_THREADS = value; 359 CONSOLE_SHOW_CLASSES = value; 360 CONSOLE_SHOW_CALLS = value; 361 CONSOLE_SHOW_STEPS = value; 362 CONSOLE_SHOW_VARIABLES = value; 363 CONSOLE_SHOW_STEPS_VERBOSE = true; 364 } 365 protected static boolean CONSOLE_SHOW_THREADS = false; 366 protected static boolean CONSOLE_SHOW_CLASSES = false; 367 protected static boolean CONSOLE_SHOW_CALLS = false; 368 protected static boolean CONSOLE_SHOW_STEPS = false; 369 protected static boolean CONSOLE_SHOW_STEPS_VERBOSE = false; 370 protected static boolean CONSOLE_SHOW_VARIABLES = false; 371 /** 372 * Show String, Integer, Double, etc as simplified objects (default==false). 373 */ 374 public static void showBuiltInObjects (boolean value) { 375 SHOW_STRINGS_AS_PRIMITIVE = !value; 376 SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE = !value; 377 GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY = true; 378 } 379 /** 380 * Show String, Integer, Double, etc as regular objects (default==false). 381 */ 382 public static void showBuiltInObjectsVerbose (boolean value) { 383 SHOW_STRINGS_AS_PRIMITIVE = !value; 384 SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE = !value; 385 GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY = false; 386 } 387 protected static boolean SHOW_STRINGS_AS_PRIMITIVE = true; 388 protected static boolean SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE = true; 389 protected static boolean GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY = true; 390 391 /** 392 * Run the debugger on the current class. Execution of the current program 393 * ends and a new JVM is started under the debugger. 394 * 395 * The debugger will execute the main method of the class that calls run. 396 * 397 * The main method is run with no arguments. 398 */ 399 public static void run () { 400 Trace.run (new String[] {}); 401 } 402 /** 403 * Run the debugger on the current class. Execution of the current program 404 * ends and a new JVM is started under the debugger. 405 * 406 * The debugger will execute the main method of the class that calls run. 407 * 408 * The main method is run with the given arguments. 409 */ 410 public static void run (String[] args) { 411 StackTraceElement[] stackTrace = Thread.currentThread ().getStackTrace (); 412 String mainClassName = stackTrace[stackTrace.length - 1].getClassName (); 413 Trace.internalPrepAndRun (mainClassName, args, true); 414 } 415 /** 416 * Run the debugger on the given class. The current program will continue to 417 * execute after this call to run has completed. 418 * 419 * The main method of the given class is called with no arguments. 420 */ 421 public static void run (String mainClassName) { 422 Trace.internalPrepAndRun (mainClassName, new String[] {}, false); 423 } 424 /** 425 * Run the debugger on the given class. The current program will continue to 426 * execute after this call to run has completed. 427 * 428 * The main method of the given class is called with no arguments. 429 */ 430 public static void run (Class<?> mainClass) { 431 Trace.run (mainClass, new String[] {}); 432 } 433 /** 434 * Run the debugger on the given class. The current program will continue to 435 * execute after this call to run has completed. 436 * 437 * The main method of the given class is called with the given arguments. 438 */ 439 public static void run (String mainClassName, String[] args) { 440 internalPrepAndRun (mainClassName, args, false); 441 } 442 /** 443 * Run the debugger on the given class. The current program will continue to 444 * execute after this call to run has completed. 445 * 446 * The main method of the given class is called with the given arguments. 447 */ 448 public static void run (Class<?> mainClass, String[] args) { 449 Trace.internalPrepAndRun (mainClass.getCanonicalName (), args, false); 450 } 451 /** 452 * Run the debugger on the given class. The current program will continue to 453 * execute after this call to run has completed. 454 * 455 * The main method of the given class is called with the given arguments. 456 */ 457 public static void runWithArgs (Class<?> mainClass, String... args) { 458 Trace.run (mainClass, args); 459 } 460 /** 461 * Run the debugger on the given class. The current program will continue to 462 * execute after this call to run has completed. 463 * 464 * The main method of the given class is called with the given arguments. 465 */ 466 public static void runWithArgs (String mainClassName, String... args) { 467 Trace.internalPrepAndRun (mainClassName, args, false); 468 } 469 /** 470 * The debugger can be invoked from the command line using this method. The 471 * first argument must be the fully qualified name of the class to be 472 * debugged. Addition arguments are passed to the main method of the class 473 * to be debugged. 474 */ 475 public static void main (String[] args) { 476 if (args.length == 0) { 477 System.err.println ("Usage: java " + Trace.class.getCanonicalName () + " [OptionalJvmArguments] fullyQualifiedClassName"); 478 System.exit (-1); 479 } 480 int length = PREFIX_ARGS_FOR_VM.size (); 481 String[] allArgs = new String[length + args.length]; 482 for (int i = 0; i < length; i++) 483 allArgs[i] = PREFIX_ARGS_FOR_VM.get (i); 484 System.arraycopy (args, 0, allArgs, length, args.length); 485 internalRun ("Trace", allArgs, false); 486 } 487 488 //------------------------------------------------------------------------ 489 // _______.___________. ______ .______ __ __ 490 // / | | / __ \ | _ \ | | | | 491 // | (----`---| |----`| | | | | |_) | | | | | 492 // \ \ | | | | | | | ___/ | | | | 493 // .----) | | | | `--' | | | |__| |__| 494 // |_______/ |__| \______/ | _| (__) (__) 495 // 496 // _______. ______ ___ .______ ____ ____ 497 // / | / | / \ | _ \ \ \ / / 498 // | (----`| ,----' / ^ \ | |_) | \ \/ / 499 // \ \ | | / /_\ \ | / \_ _/ 500 // .----) | | `----./ _____ \ | |\ \----. | | 501 // |_______/ \______/__/ \__\ | _| `._____| |__| 502 // 503 // .___________. __ __ __ .__ __. _______ _______. 504 // | || | | | | | | \ | | / _____| / | 505 // `---| |----`| |__| | | | | \| | | | __ | (----` 506 // | | | __ | | | | . ` | | | |_ | \ \ 507 // | | | | | | | | | |\ | | |__| | .----) | 508 // |__| |__| |__| |__| |__| \__| \______| |_______/ 509 // 510 // .______ _______ __ ______ ____ __ ____ __ 511 // | _ \ | ____|| | / __ \ \ \ / \ / / | | 512 // | |_) | | |__ | | | | | | \ \/ \/ / | | 513 // | _ < | __| | | | | | | \ / | | 514 // | |_) | | |____ | `----.| `--' | \ /\ / |__| 515 // |______/ |_______||_______| \______/ \__/ \__/ (__) 516 // 517 //------------------------------------------------------------------------ 518 // This program uses all sorts of crazy java foo. 519 // You should not have to read anything else in this file. 520 521 /** 522 * This code is based on the Trace.java example included in the 523 * demo/jpda/examples.jar file in the JDK. 524 * 525 * For more information on JPDA and JDI, see: 526 * 527 * <pre> 528 * http://docs.oracle.com/javase/8/docs/technotes/guides/jpda/trace.html 529 * http://docs.oracle.com/javase/8/docs/jdk/api/jpda/jdi/index.html 530 * http://forums.sun.com/forum.jspa?forumID=543 531 * </pre> 532 * 533 * Changes made by Riely: 534 * 535 * - works with packages other than default 536 * 537 * - prints values of variables Objects values are printed as "@uniqueId" 538 * Arrays include the values in the array, up to 539 * 540 * - handles exceptions 541 * 542 * - works for arrays, when referenced by local variables, static fields, or 543 * fields of "this" 544 * 545 * - options for more or less detail 546 * 547 * - indenting to show position in call stack 548 * 549 * - added methods to draw the state of the system using graphviz 550 * 551 * Known bugs/limitations: 552 * 553 * - There appears to be a bug in the JDI: steps from static initializers 554 * are not always reported. I don't see a real pattern here. Some static 555 * initializers work and some don't. When the step event is not generated by 556 * the JDI, this code cannot report on it, of course, since we rely on the 557 * JDI to generate the steps. 558 * 559 * - Works for local arrays, including updates to fields of "this", but will 560 * not print changes through other object references, such as 561 * yourObject.publicArray[0] = 22 As long as array fields are private (as 562 * they should be), it should be okay. 563 * 564 * - Updates to arrays that are held both in static fields and also in local 565 * variables or object fields will be shown more than once in the console 566 * view. 567 * 568 * - Space leak: copies of array references are kept forever. See 569 * "registerArray". 570 * 571 * - Not debugged for multithreaded code. Monitor events are currently 572 * ignored. 573 * 574 * - Slow. Only good for short programs. 575 * 576 * - This is a hodgepodge of code from various sources, not well debugged, 577 * not super clean. 578 * 579 */ 580 /** 581 * Macintosh OS-X sometimes sets the hostname to an unroutable name and this 582 * may cause the socket connection to fail. To see your hostname, open a 583 * Terminal window and type the "hostname" command. On my machine, the 584 * terminal prompt is "$", and so the result looks like this: 585 * 586 * <pre> 587 * $ hostname 588 * escarole.local 589 * $ 590 * </pre> 591 * 592 * To see that this machine is routable, I can "ping" it: 593 * 594 * <pre> 595 * $ ping escarole.local 596 * PING escarole.local (192.168.1.109): 56 data bytes 597 * 64 bytes from 192.168.1.109: icmp_seq=0 ttl=64 time=0.046 ms 598 * 64 bytes from 192.168.1.109: icmp_seq=1 ttl=64 time=0.104 ms 599 * ^C 600 * --- escarole.local ping statistics --- 601 * 2 packets transmitted, 2 packets received, 0.0% packet loss 602 * round-trip min/avg/max/stddev = 0.046/0.075/0.104/0.029 ms 603 * </pre> 604 * 605 * When I am connected to some networks, the result is like this: 606 * 607 * <pre> 608 * $ hostname 609 * loop-depaulsecure-182-129.depaulsecure-employee.depaul.edu 610 * $ ping loop-depaulsecure-182-129.depaulsecure-employee.depaul.edu 611 * ping: cannot resolve loop-depaulsecure-182-129.depaulsecure-employee.depaul.edu: Unknown host 612 * </pre> 613 * 614 * Or this: 615 * 616 * <pre> 617 * $ hostname 618 * asteelembook.cstcis.cti.depaul.edu 619 * $ ping asteelembook.cstcis.cti.depaul.edu 620 * PING asteelembook.cstcis.cti.depaul.edu (140.192.38.100): 56 data bytes 621 * Request timeout for icmp_seq 0 622 * Request timeout for icmp_seq 1 623 * ^C 624 * --- asteelembook.cstcis.cti.depaul.edu ping statistics --- 625 * 3 packets transmitted, 0 packets received, 100.0% packet loss 626 * </pre> 627 * 628 * To stop OS-X from taking bogus hostname like this, you can fix the 629 * hostname, as follows: 630 * 631 * <pre> 632 * $ scutil --set HostName escarole.local 633 * $ 634 * </pre> 635 * 636 * Where "escarole" is your computer name (no spaces or punctuation). You 637 * will be prompted for your password in order to modify the configuration. 638 * 639 * To reset OS-X to it's default behavior, do this: 640 * 641 * <pre> 642 * $ scutil --set HostName "" 643 * $ 644 * </pre> 645 * 646 * On OSX 10.10 (Yosemite), apple seems to have turned of DNS lookup for 647 * .local addresses. 648 * 649 * https://discussions.apple.com/thread/6611817?start=13 650 * 651 * To fix this, you need to 652 * 653 * <pre> 654 * sudo vi /etc/hosts 655 * </pre> 656 * 657 * and change the line 658 * 659 * <pre> 660 * 127.0.0.1 localhost 661 * </pre> 662 * 663 * to 664 * 665 * <pre> 666 * 127.0.0.1 localhost escarole.local 667 * </pre> 668 * 669 * More robustly, the following "patch" fixes the JDI so that it uses the ip 670 * address, rather than the hostname. The code is called from 671 * 672 * <pre> 673 * com.sun.tools.jdi.SunCommandLineLauncher.launch () 674 * </pre> 675 * 676 * which calls 677 * 678 * <pre> 679 * com.sun.tools.jdi.SocketTransportService.SocketListenKey.address () 680 * </pre> 681 * 682 * Here is the patch. Just compile this and put in your classpath before 683 * tools.jar. 684 * 685 * <pre> 686 * package com.sun.tools.jdi; 687 * 688 * import java.net.*; 689 * 690 * class SocketTransportService$SocketListenKey extends com.sun.jdi.connect.spi.TransportService.ListenKey { 691 * ServerSocket ss; 692 * SocketTransportService$SocketListenKey (ServerSocket ss) { 693 * this.ss = ss; 694 * } 695 * ServerSocket socket () { 696 * return ss; 697 * } 698 * public String toString () { 699 * return address (); 700 * } 701 * 702 * // Returns the string representation of the address that this listen key represents. 703 * public String address () { 704 * InetAddress address = ss.getInetAddress (); 705 * 706 * // If bound to the wildcard address then use current local hostname. In 707 * // the event that we don't know our own hostname then assume that host 708 * // supports IPv4 and return something to represent the loopback address. 709 * if (address.isAnyLocalAddress ()) { 710 * // JWR: Only change is to comment out the lines below 711 * // try { 712 * // address = InetAddress.getLocalHost (); 713 * // } catch (UnknownHostException uhe) { 714 * byte[] loopback = { 0x7f, 0x00, 0x00, 0x01 }; 715 * try { 716 * address = InetAddress.getByAddress ("127.0.0.1", loopback); 717 * } catch (UnknownHostException x) { 718 * throw new InternalError ("unable to get local hostname"); 719 * } 720 * // } 721 * } 722 * 723 * // Now decide if we return a hostname or IP address. Where possible 724 * // return a hostname but in the case that we are bound to an address 725 * // that isn't registered in the name service then we return an address. 726 * String result; 727 * String hostname = address.getHostName (); 728 * String hostaddr = address.getHostAddress (); 729 * if (hostname.equals (hostaddr)) { 730 * if (address instanceof Inet6Address) { 731 * result = "[" + hostaddr + "]"; 732 * } else { 733 * result = hostaddr; 734 * } 735 * } else { 736 * result = hostname; 737 * } 738 * 739 * // Finally return "hostname:port", "ipv4-address:port" or "[ipv6-address]:port". 740 * return result + ":" + ss.getLocalPort (); 741 * } 742 * } 743 * </pre> 744 */ 745 private static String IN_DEBUGGER = "TraceDebuggingVMHasLaunched"; 746 private static boolean insideTestVM () { 747 return System.getProperty (IN_DEBUGGER) != null; 748 } 749 /** 750 * Prepares the args and then calls internalRun. 751 */ 752 private static void internalPrepAndRun (String mainClassName, String[] args, boolean terminateAfter) { 753 int length = PREFIX_ARGS_FOR_VM.size (); 754 String[] allArgs = new String[length + args.length + 1]; 755 for (int i = 0; i < length; i++) 756 allArgs[i] = PREFIX_ARGS_FOR_VM.get (i); 757 allArgs[length] = mainClassName; 758 System.arraycopy (args, 0, allArgs, length + 1, args.length); 759 internalRun (mainClassName, allArgs, terminateAfter); 760 } 761 /** 762 * This is the function that starts the JVM. If terminateAfter is true, then 763 * the current thread is killed after the debug JVM terminates. 764 */ 765 @SuppressWarnings("deprecation") 766 private static void internalRun (String mainClassName, String[] allArgs, boolean terminateAfter) { 767 if (insideTestVM ()) return; 768 Graphviz.setOutputDirectory (GRAPHVIZ_DIR, mainClassName); 769 VirtualMachine vm = launchConnect (allArgs); 770 monitorJVM (vm); 771 if (terminateAfter) Thread.currentThread ().stop (); 772 } 773 774 /** 775 * Prefix options for the debugger VM. By default, the classpath is set to 776 * the current classpath. Other options can be provided here. 777 */ 778 public static void addPrefixOptionsForVm (String value) { 779 PREFIX_ARGS_FOR_VM.add (value); 780 } 781 private static String BIN_CLASSPATH = "bin" + System.getProperty ("path.separator") + "."; 782 protected static ArrayList<String> PREFIX_ARGS_FOR_VM; 783 static { 784 PREFIX_ARGS_FOR_VM = new ArrayList<> (); 785 PREFIX_ARGS_FOR_VM.add ("-cp"); 786 PREFIX_ARGS_FOR_VM.add ("\"" + System.getProperty ("java.class.path") + "\""); 787 PREFIX_ARGS_FOR_VM.add ("-D" + IN_DEBUGGER + "=true"); 788 } 789 /** 790 * Turn on debugging information (default==false). Intended for developers. 791 */ 792 public static void debug (boolean value) { 793 DEBUG = value; 794 } 795 protected static boolean DEBUG = false; 796 797 /** 798 * Add an exclude pattern. Classes whose fully qualified name matches an 799 * exclude pattern are ignored by the debugger. Regular expressions are 800 * limited to exact matches and patterns that begin with '*' or end with 801 * '*'; for example, "*.Foo" or "java.*". This limitation is inherited from 802 * <code>com.sun.jdi.request.WatchpointRequest</code>. The default exclude 803 * patterns include: 804 * 805 * <pre> 806 * "*$$Lambda$*" "java.*" "jdk.*" "sun.*" "com.*" "org.*" "javax.*" "apple.*" "Jama.*" "qs.*" 807 * "stdlib.A*" "stdlib.B*" "stdlib.C*" "stdlib.D*" "stdlib.E*" "stdlib.F*" "stdlib.G*" "stdlib.H*" 808 * "stdlib.I*" "stdlib.J*" "stdlib.K*" "stdlib.L*" "stdlib.M*" "stdlib.N*" "stdlib.O*" "stdlib.P*" 809 * "stdlib.Q*" "stdlib.R*" "stdlib.S*" 810 * "stdlib.U*" "stdlib.V*" "stdlib.W*" "stdlib.X*" "stdlib.Y*" 811 * </pre> 812 * 813 * The JDI excludes classes, but does not allow exceptions. This is the 814 * reason for the unusual number of excludes for <code>stdlib</code>. It is important that 815 * <code>stdlib.Trace</code> not be excluded --- if it were, then callBacks 816 * to <code>Trace.draw</code> would not function. As a result, all classes in <code>stdlib</code> that start with a letter other than <code>T</code> are excluded. 817 * Be careful when adding classes to <code>stdlib</code>. 818 * 819 * Exclude patterns must include 820 * 821 * <pre> 822 * "*$$Lambda$*" "java.*" "jdk.*" "sun.*" 823 * </pre> 824 * 825 * otherwise the Trace code itself will fail to run. 826 */ 827 public static void addExcludePattern (String value) { 828 EXCLUDE_GLOBS.add (value); 829 } 830 /** 831 * Remove an exclude pattern. 832 * 833 * @see addExcludePattern 834 */ 835 public static void removeExcludePattern (String value) { 836 EXCLUDE_GLOBS.remove (value); 837 } 838 protected static HashSet<String> EXCLUDE_GLOBS; 839 static { 840 EXCLUDE_GLOBS = new HashSet<> (); 841 EXCLUDE_GLOBS.add ("*$$Lambda$*"); 842 EXCLUDE_GLOBS.add ("java.*"); 843 EXCLUDE_GLOBS.add ("jdk.*"); 844 EXCLUDE_GLOBS.add ("sun.*"); 845 EXCLUDE_GLOBS.add ("com.*"); 846 EXCLUDE_GLOBS.add ("org.*"); 847 EXCLUDE_GLOBS.add ("javax.*"); 848 EXCLUDE_GLOBS.add ("apple.*"); 849 EXCLUDE_GLOBS.add ("Jama.*"); 850 EXCLUDE_GLOBS.add ("qs.*"); 851 EXCLUDE_GLOBS.add ("stdlib.A*"); 852 EXCLUDE_GLOBS.add ("stdlib.B*"); 853 EXCLUDE_GLOBS.add ("stdlib.C*"); 854 EXCLUDE_GLOBS.add ("stdlib.D*"); 855 EXCLUDE_GLOBS.add ("stdlib.E*"); 856 EXCLUDE_GLOBS.add ("stdlib.F*"); 857 EXCLUDE_GLOBS.add ("stdlib.G*"); 858 EXCLUDE_GLOBS.add ("stdlib.H*"); 859 EXCLUDE_GLOBS.add ("stdlib.I*"); 860 EXCLUDE_GLOBS.add ("stdlib.J*"); 861 EXCLUDE_GLOBS.add ("stdlib.K*"); 862 EXCLUDE_GLOBS.add ("stdlib.L*"); 863 EXCLUDE_GLOBS.add ("stdlib.M*"); 864 EXCLUDE_GLOBS.add ("stdlib.N*"); 865 EXCLUDE_GLOBS.add ("stdlib.O*"); 866 EXCLUDE_GLOBS.add ("stdlib.P*"); 867 EXCLUDE_GLOBS.add ("stdlib.Q*"); 868 EXCLUDE_GLOBS.add ("stdlib.R*"); 869 EXCLUDE_GLOBS.add ("stdlib.S*"); 870 EXCLUDE_GLOBS.add ("stdlib.U*"); 871 EXCLUDE_GLOBS.add ("stdlib.V*"); 872 EXCLUDE_GLOBS.add ("stdlib.W*"); 873 EXCLUDE_GLOBS.add ("stdlib.X*"); 874 EXCLUDE_GLOBS.add ("stdlib.Y*"); 875 //EXCLUDE_GLOBS.add ("stdlib.Z*"); 876 } 877 /** 878 * Add an include pattern for drawing. These are classes that should be shown in drawing and console logs, which would otherwise be excluded. 879 * The default is: 880 * 881 * <pre> 882 * "java.util.*" 883 * </pre> 884 */ 885 public static void addDrawingIncludePattern (String value) { 886 DRAWING_INCLUDE_GLOBS.add (value); 887 } 888 /** 889 * Add an include pattern. 890 * 891 * @see addDrawingIncludePattern 892 */ 893 public static void removeDrawingIncludePattern (String value) { 894 DRAWING_INCLUDE_GLOBS.remove (value); 895 } 896 protected static HashSet<String> DRAWING_INCLUDE_GLOBS; 897 static { 898 DRAWING_INCLUDE_GLOBS = new HashSet<> (); 899 DRAWING_INCLUDE_GLOBS.add ("java.util.*"); 900 } 901 /** 902 * When the debugged program ends, create a graphviz file showing the call tree (default==false). 903 */ 904 public static void drawCallTree (boolean value) { SHOW_CALL_TREE = value; } 905 protected static boolean SHOW_CALL_TREE = false; 906 /** 907 * Graphviz style for a call tree node. 908 */ 909 public static void graphvizCallTreeBoxAttributes (String value) { 910 String v = (value == null || "".equals (value)) ? "" : "," + value; 911 GRAPHVIZ_ARRAY_BOX_ATTRIBUTES = v; 912 } 913 protected static String GRAPHVIZ_CALL_TREE_BOX_ATTRIBUTES = ",shape=record"; 914 /** 915 * Graphviz style for a call tree arrow. 916 */ 917 public static void graphvizCallTreeArrowAttributes (String value) { 918 String v = (value == null || "".equals (value)) ? "" : "," + value; 919 GRAPHVIZ_ARRAY_ARROW_ATTRIBUTES = v; 920 } 921 protected static String GRAPHVIZ_CALL_TREE_ARROW_ATTRIBUTES = ",fontsize=12"; 922 /** 923 * Graphviz style for an array. 924 */ 925 public static void graphvizArrayBoxAttributes (String value) { 926 String v = (value == null || "".equals (value)) ? "" : "," + value; 927 GRAPHVIZ_ARRAY_BOX_ATTRIBUTES = v; 928 } 929 protected static String GRAPHVIZ_ARRAY_BOX_ATTRIBUTES = ",shape=record,color=blue"; 930 /** 931 * Graphviz style for an arrow from an array to an Object. 932 */ 933 public static void graphvizArrayArrowAttributes (String value) { 934 String v = (value == null || "".equals (value)) ? "" : "," + value; 935 GRAPHVIZ_ARRAY_ARROW_ATTRIBUTES = v; 936 } 937 protected static String GRAPHVIZ_ARRAY_ARROW_ATTRIBUTES = ",fontsize=12,color=blue,arrowtail=dot,dir=both,tailclip=false"; 938 /** 939 * Graphviz style for a frame. 940 */ 941 public static void graphvizFrameBoxAttributes (String value) { 942 String v = (value == null || "".equals (value)) ? "" : "," + value; 943 GRAPHVIZ_FRAME_BOX_ATTRIBUTES = v; 944 } 945 protected static String GRAPHVIZ_FRAME_BOX_ATTRIBUTES = ",shape=record,color=red"; 946 /** 947 * Graphviz style for an arrow from a frame to an Object. 948 */ 949 public static void graphvizFrameObjectArrowAttributes (String value) { 950 String v = (value == null || "".equals (value)) ? "" : "," + value; 951 GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES = v; 952 } 953 protected static String GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES = ",fontsize=12,color=red"; 954 /** 955 * Graphviz style for an arrow from a return value to a frame. 956 */ 957 public static void graphvizFrameReturnAttributes (String value) { 958 String v = (value == null || "".equals (value)) ? "" : "," + value; 959 GRAPHVIZ_FRAME_RETURN_ATTRIBUTES = v; 960 } 961 protected static String GRAPHVIZ_FRAME_RETURN_ATTRIBUTES = ",color=red"; 962 /** 963 * Graphviz style for an arrow from an exception to a frame. 964 */ 965 public static void graphvizFrameExceptionAttributes (String value) { 966 String v = (value == null || "".equals (value)) ? "" : "," + value; 967 GRAPHVIZ_FRAME_EXCEPTION_ATTRIBUTES = v; 968 } 969 protected static String GRAPHVIZ_FRAME_EXCEPTION_ATTRIBUTES = ",color=red"; 970 /** 971 * Graphviz style for an arrow from a frame to another frame. 972 */ 973 public static void graphvizFrameFrameArrowAttributes (String value) { 974 String v = (value == null || "".equals (value)) ? "" : "," + value; 975 GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES = v; 976 } 977 protected static String GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES = ",color=red,style=dashed"; 978 /** 979 * Graphviz style for an object (non-array). 980 */ 981 public static void graphvizObjectBoxAttributes (String value) { 982 String v = (value == null || "".equals (value)) ? "" : "," + value; 983 GRAPHVIZ_OBJECT_BOX_ATTRIBUTES = v; 984 } 985 protected static String GRAPHVIZ_OBJECT_BOX_ATTRIBUTES = ",shape=record,color=purple"; 986 /** 987 * Graphviz style for a wrapper object (in simple form). 988 */ 989 public static void graphvizWrapperBoxAttributes (String value) { 990 String v = (value == null || "".equals (value)) ? "" : "," + value; 991 GRAPHVIZ_WRAPPER_BOX_ATTRIBUTES = v; 992 } 993 protected static String GRAPHVIZ_WRAPPER_BOX_ATTRIBUTES = ",shape=ellipse,color=purple"; 994 /** 995 * Graphviz style for an arrow from an object to an object. 996 */ 997 public static void graphvizObjectArrowAttributes (String value) { 998 String v = (value == null || "".equals (value)) ? "" : "," + value; 999 GRAPHVIZ_OBJECT_ARROW_ATTRIBUTES = v; 1000 } 1001 protected static String GRAPHVIZ_OBJECT_ARROW_ATTRIBUTES = ",fontsize=12,color=purple"; 1002 /** 1003 * Graphviz style for a static class. 1004 */ 1005 public static void graphvizStaticClassBoxAttributes (String value) { 1006 String v = (value == null || "".equals (value)) ? "" : "," + value; 1007 GRAPHVIZ_STATIC_CLASS_BOX_ATTRIBUTES = v; 1008 } 1009 protected static String GRAPHVIZ_STATIC_CLASS_BOX_ATTRIBUTES = ",shape=record,color=orange"; 1010 /** 1011 * Graphviz style for an arrow from a static class to an object. 1012 */ 1013 public static void graphvizStaticClassArrowAttributes (String value) { 1014 String v = (value == null || "".equals (value)) ? "" : "," + value; 1015 GRAPHVIZ_STATIC_CLASS_ARROW_ATTRIBUTES = v; 1016 } 1017 protected static String GRAPHVIZ_STATIC_CLASS_ARROW_ATTRIBUTES = ",fontsize=12,color=orange"; 1018 /** 1019 * Graphviz style for box labels. 1020 */ 1021 public static void graphvizLabelBoxAttributes (String value) { 1022 String v = (value == null || "".equals (value)) ? "" : "," + value; 1023 GRAPHVIZ_LABEL_BOX_ATTRIBUTES = v; 1024 } 1025 protected static String GRAPHVIZ_LABEL_BOX_ATTRIBUTES = ",shape=none,color=black"; 1026 /** 1027 * Graphviz style for arrow labels. 1028 */ 1029 public static void graphvizLabelArrowAttributes (String value) { 1030 String v = (value == null || "".equals (value)) ? "" : "," + value; 1031 GRAPHVIZ_LABEL_ARROW_ATTRIBUTES = v; 1032 } 1033 protected static String GRAPHVIZ_LABEL_ARROW_ATTRIBUTES = ",color=black"; 1034 1035 // Graphiz execution 1036 /** 1037 * Add a filesystem location to search for the dot executable that comes 1038 * with graphviz. The default is system dependent. 1039 */ 1040 public static void graphvizAddPossibleDotLocation (String value) { 1041 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add (value); 1042 } 1043 protected static ArrayList<String> GRAPHVIZ_POSSIBLE_DOT_LOCATIONS; 1044 static { 1045 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS = new ArrayList<> (); 1046 String os = System.getProperty ("os.name").toLowerCase (); 1047 if (os.startsWith ("win")) { 1048 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("c:/Program Files (x86)/Graphviz2.38/bin/dot.exe"); 1049 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add (System.getProperty ("user.dir") + "/lib/graphviz-windows/bin/dot.exe"); 1050 } else if (os.startsWith ("mac")) { 1051 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/usr/local/bin/dot"); 1052 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/usr/bin/dot"); 1053 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add (System.getProperty ("user.dir") + "/lib/graphviz-mac/bin/dot"); 1054 } else { 1055 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/usr/local/bin/dot"); 1056 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/usr/bin/dot"); 1057 } 1058 } 1059 /** 1060 * Remove graphviz files for which graphic files have been successfully 1061 * generated. 1062 */ 1063 public static void graphvizRemoveGvFiles (boolean value) { 1064 GRAPHVIZ_REMOVE_GV_FILES = value; 1065 } 1066 protected static boolean GRAPHVIZ_REMOVE_GV_FILES = true; 1067 1068 /** 1069 * Show fully qualified class names (default==false). If 1070 * showPackageInClassName is true, then showOuterClassInClassName is ignored 1071 * (taken to be true). 1072 */ 1073 public static void showPackageInClassName (boolean value) { 1074 SHOW_PACKAGE_IN_CLASS_NAME = value; 1075 } 1076 protected static boolean SHOW_PACKAGE_IN_CLASS_NAME = false; 1077 /** 1078 * Include fully qualified class names (default==false). 1079 */ 1080 public static void showOuterClassInClassName (boolean value) { 1081 SHOW_OUTER_CLASS_IN_CLASS_NAME = value; 1082 } 1083 protected static boolean SHOW_OUTER_CLASS_IN_CLASS_NAME = false; 1084 /** 1085 * Show the object type in addition to its id (default==false). 1086 */ 1087 public static void consoleShowTypeInObjectName (boolean value) { 1088 CONSOLE_SHOW_TYPE_IN_OBJECT_NAME = value; 1089 } 1090 protected static boolean CONSOLE_SHOW_TYPE_IN_OBJECT_NAME = false; 1091 /** 1092 * The maximum number of displayed fields when printing an object on the 1093 * console (default==8). 1094 */ 1095 public static void consoleMaxFields (int value) { 1096 CONSOLE_MAX_FIELDS = value; 1097 } 1098 protected static int CONSOLE_MAX_FIELDS = 8; 1099 /** 1100 * The maximum number of displayed elements when printing a primitive array 1101 * on the console (default==15). 1102 */ 1103 public static void consoleMaxArrayElementsPrimitive (int value) { 1104 CONSOLE_MAX_ARRAY_ELEMENTS_PRIMITIVE = value; 1105 } 1106 protected static int CONSOLE_MAX_ARRAY_ELEMENTS_PRIMITIVE = 15; 1107 /** 1108 * The maximum number of displayed elements when printing an object array on 1109 * the console (default==8). 1110 */ 1111 public static void consoleMaxArrayElementsObject (int value) { 1112 CONSOLE_MAX_ARRAY_ELEMENTS_OBJECT = value; 1113 } 1114 protected static int CONSOLE_MAX_ARRAY_ELEMENTS_OBJECT = 8; 1115 /** 1116 * Show object ids inside multidimensional arrays (default==false). 1117 */ 1118 public static void consoleShowNestedArrayIds (boolean value) { 1119 CONSOLE_SHOW_NESTED_ARRAY_IDS = value; 1120 } 1121 protected static boolean CONSOLE_SHOW_NESTED_ARRAY_IDS = false; 1122 /** 1123 * Show fields introduced by the compiler (default==true); 1124 */ 1125 public static void showSyntheticFields (boolean value) { 1126 SHOW_SYNTHETIC_FIELDS = value; 1127 } 1128 protected static boolean SHOW_SYNTHETIC_FIELDS = true; 1129 /** 1130 * Show methods introduced by the compiler (default==true); 1131 */ 1132 public static void showSyntheticMethods (boolean value) { 1133 SHOW_SYNTHETIC_METHODS = value; 1134 } 1135 protected static boolean SHOW_SYNTHETIC_METHODS = true; 1136 /** 1137 * Show file names on the console (default==false). 1138 */ 1139 public static void showFilenamesOnConsole (boolean value) { 1140 GRAPHVIZ_SHOW_FILENAMES_ON_CONSOLE = value; 1141 } 1142 protected static boolean GRAPHVIZ_SHOW_FILENAMES_ON_CONSOLE = false; 1143 /** 1144 * In graphviz, show field name in the label of an object (default==true). 1145 */ 1146 public static void showFieldNamesInLabels (boolean value) { 1147 GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS = value; 1148 } 1149 protected static boolean GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS = true; 1150 /** 1151 * In graphviz, show object ids (default==false). 1152 */ 1153 public static void showObjectIds (boolean value) { 1154 GRAPHVIZ_SHOW_OBJECT_IDS = value; 1155 } 1156 protected static boolean GRAPHVIZ_SHOW_OBJECT_IDS = false; 1157 /** 1158 * In graphviz, show stack frame numbers (default==true). 1159 */ 1160 public static void showFrameNumbers (boolean value) { 1161 GRAPHVIZ_SHOW_FRAME_NUMBERS = value; 1162 } 1163 protected static boolean GRAPHVIZ_SHOW_FRAME_NUMBERS = true; 1164 /** 1165 * In graphviz, show null fields (default==true). 1166 */ 1167 public static void showNullFields (boolean value) { 1168 GRAPHVIZ_SHOW_NULL_FIELDS = value; 1169 } 1170 protected static boolean GRAPHVIZ_SHOW_NULL_FIELDS = true; 1171 /** 1172 * In graphviz, show null variabels (default==true). 1173 */ 1174 public static void showNullVariables (boolean value) { 1175 GRAPHVIZ_SHOW_NULL_VARIABLES = value; 1176 } 1177 protected static boolean GRAPHVIZ_SHOW_NULL_VARIABLES = true; 1178 /** 1179 * Include line number in graphviz filename (default==true). 1180 */ 1181 public static void graphvizPutLineNumberInFilename (boolean value) { 1182 GRAPHVIZ_PUT_LINE_NUMBER_IN_FILENAME = value; 1183 } 1184 protected static boolean GRAPHVIZ_PUT_LINE_NUMBER_IN_FILENAME = true; 1185 /** 1186 * Do not display any fields with this name (default includes only 1187 * "$assertionsDisabled"). 1188 */ 1189 public static void addGraphvizIgnoredFields (String value) { 1190 GRAPHVIZ_IGNORED_FIELDS.add (value); 1191 } 1192 protected static ArrayList<String> GRAPHVIZ_IGNORED_FIELDS; 1193 static { 1194 GRAPHVIZ_IGNORED_FIELDS = new ArrayList<> (); 1195 GRAPHVIZ_IGNORED_FIELDS.add ("$assertionsDisabled"); 1196 } 1197 /** 1198 * Set the graphviz attributes for objects of the given class. 1199 */ 1200 public static void graphvizSetObjectAttribute (Class<?> cz, String attrib) { 1201 Graphviz.objectAttributeMap.put (cz.getName (), attrib); 1202 } 1203 /** 1204 * Set the graphviz attributes for objects of the given class. 1205 */ 1206 public static void graphvizSetStaticClassAttribute (Class<?> cz, String attrib) { 1207 Graphviz.staticClassAttributeMap.put (cz.getName (), attrib); 1208 } 1209 /** 1210 * Set the graphviz attributes for frames of the given class. 1211 */ 1212 public static void graphvizSetFrameAttribute (Class<?> cz, String attrib) { 1213 Graphviz.frameAttributeMap.put (cz.getName (), attrib); 1214 } 1215 /** 1216 * Set the graphviz attributes for all fields with the given name. 1217 */ 1218 public static void graphvizSetFieldAttribute (String field, String attrib) { 1219 Graphviz.fieldAttributeMap.put (field, attrib); 1220 } 1221 protected static String BAD_ERROR_MESSAGE = "\n!!!! This shouldn't happen! \n!!!! Please contact your instructor or the author of " + Trace.class.getCanonicalName (); 1222 1223 // ---------------------- Launch the JVM ---------------------------------- 1224 1225 // Set up a launching connection to the JVM 1226 private static VirtualMachine launchConnect (String[] args) { 1227 VirtualMachine vm = null; 1228 LaunchingConnector conn = getCommandLineConnector (); 1229 Map<String, Connector.Argument> connArgs = setMainArgs (conn, args); 1230 1231 try { 1232 vm = conn.launch (connArgs); // launch the JVM and connect to it 1233 } catch (IOException e) { 1234 throw new Error ("\n!!!! Unable to launch JVM: " + e); 1235 } catch (IllegalConnectorArgumentsException e) { 1236 throw new Error ("\n!!!! Internal error: " + e); 1237 } catch (VMStartException e) { 1238 throw new Error ("\n!!!! JVM failed to start: " + e.getMessage ()); 1239 } 1240 1241 return vm; 1242 } 1243 1244 // find a command line launch connector 1245 private static LaunchingConnector getCommandLineConnector () { 1246 List<Connector> conns = Bootstrap.virtualMachineManager ().allConnectors (); 1247 1248 for (Connector conn : conns) { 1249 if (conn.name ().equals ("com.sun.jdi.CommandLineLaunch")) return (LaunchingConnector) conn; 1250 } 1251 throw new Error ("\n!!!! No launching connector found"); 1252 } 1253 1254 // make the tracer's input arguments the program's main() arguments 1255 private static Map<String, Connector.Argument> setMainArgs (LaunchingConnector conn, String[] args) { 1256 // get the connector argument for the program's main() method 1257 Map<String, Connector.Argument> connArgs = conn.defaultArguments (); 1258 Connector.Argument mArgs = connArgs.get ("main"); 1259 if (mArgs == null) throw new Error ("\n!!!! Bad launching connector"); 1260 1261 // concatenate all the tracer's input arguments into a single string 1262 StringBuffer sb = new StringBuffer (); 1263 for (int i = 0; i < args.length; i++) 1264 sb.append (args[i] + " "); 1265 1266 mArgs.setValue (sb.toString ()); // assign input args to application's main() 1267 return connArgs; 1268 } 1269 1270 // monitor the JVM running the application 1271 private static void monitorJVM (VirtualMachine vm) { 1272 // start JDI event handler which displays trace info 1273 JDIEventMonitor watcher = new JDIEventMonitor (vm); 1274 watcher.start (); 1275 1276 // redirect VM's output and error streams to the system output and error streams 1277 Process process = vm.process (); 1278 Thread errRedirect = new StreamRedirecter ("error reader", process.getErrorStream (), System.err); 1279 Thread outRedirect = new StreamRedirecter ("output reader", process.getInputStream (), System.out); 1280 errRedirect.start (); 1281 outRedirect.start (); 1282 1283 vm.resume (); // start the application 1284 1285 try { 1286 watcher.join (); // Wait. Shutdown begins when the JDI watcher terminates 1287 errRedirect.join (); // make sure all the stream outputs have been forwarded before we exit 1288 outRedirect.join (); 1289 } catch (InterruptedException e) {} 1290 } 1291} 1292 1293/** 1294 * StreamRedirecter is a thread which copies it's input to it's output and 1295 * terminates when it completes. 1296 * 1297 * @author Robert Field, September 2005 1298 * @author Andrew Davison, March 2009, [email protected] 1299 */ 1300/* private static */class StreamRedirecter extends Thread { 1301 private static final int BUFFER_SIZE = 2048; 1302 private final Reader in; 1303 private final Writer out; 1304 1305 public StreamRedirecter (String name, InputStream in, OutputStream out) { 1306 super (name); 1307 this.in = new InputStreamReader (in); // stream to copy from 1308 this.out = new OutputStreamWriter (out); // stream to copy to 1309 setPriority (Thread.MAX_PRIORITY - 1); 1310 } 1311 1312 // copy BUFFER_SIZE chars at a time 1313 public void run () { 1314 try { 1315 char[] cbuf = new char[BUFFER_SIZE]; 1316 int count; 1317 while ((count = in.read (cbuf, 0, BUFFER_SIZE)) >= 0) 1318 out.write (cbuf, 0, count); 1319 out.flush (); 1320 } catch (IOException e) { 1321 System.err.println ("StreamRedirecter: " + e); 1322 } 1323 } 1324 1325} 1326 1327/** 1328 * Monitor incoming JDI events for a program running in the JVM and print out 1329 * trace/debugging information. 1330 * 1331 * This is a simplified version of EventThread.java from the Trace.java example 1332 * in the demo/jpda/examples.jar file in the JDK. 1333 * 1334 * Andrew Davison: The main addition is the use of the ShowCodes and ShowLines 1335 * classes to list the line being currently executed. 1336 * 1337 * James Riely: See comments in class Trace. 1338 * 1339 * @author Robert Field and Minoru Terada, September 2005 1340 * @author Iman_S, June 2008 1341 * @author Andrew Davison, [email protected], March 2009 1342 * @author James Riely, [email protected], August 2014 1343 */ 1344/* private static */class JDIEventMonitor extends Thread { 1345 // exclude events generated for these classes 1346 private final VirtualMachine vm; // the JVM 1347 private boolean connected = true; // connected to VM? 1348 private boolean vmDied; // has VM death occurred? 1349 private final JDIEventHandler printer = new Printer (); 1350 1351 public JDIEventMonitor (VirtualMachine jvm) { 1352 super ("JDIEventMonitor"); 1353 vm = jvm; 1354 setEventRequests (); 1355 } 1356 1357 // Create and enable the event requests for the events we want to monitor in 1358 // the running program. 1359 // 1360 // Created here: 1361 // 1362 // createThreadStartRequest() 1363 // createThreadDeathRequest() 1364 // createClassPrepareRequest() 1365 // createClassUnloadRequest() 1366 // createMethodEntryRequest() 1367 // createMethodExitRequest() 1368 // createExceptionRequest(ReferenceType refType, boolean notifyCaught, boolean notifyUncaught) 1369 // createMonitorContendedEnterRequest() 1370 // createMonitorContendedEnteredRequest() 1371 // createMonitorWaitRequest() 1372 // createMonitorWaitedRequest() 1373 // 1374 // Created when class is loaded: 1375 // 1376 // createModificationWatchpointRequest(Field field) 1377 // 1378 // Created when thread is started: 1379 // 1380 // createStepRequest(ThreadReference thread, int size, int depth) 1381 // 1382 // Unused: 1383 // 1384 // createAccessWatchpointRequest(Field field) 1385 // createBreakpointRequest(Location location) 1386 // 1387 // Unnecessary: 1388 // 1389 // createVMDeathRequest() // these happen even without being requested 1390 // 1391 private void setEventRequests () { 1392 EventRequestManager mgr = vm.eventRequestManager (); 1393 { 1394 ThreadStartRequest x = mgr.createThreadStartRequest (); // report thread starts 1395 x.enable (); 1396 } 1397 { 1398 ThreadDeathRequest x = mgr.createThreadDeathRequest (); // report thread deaths 1399 x.enable (); 1400 } 1401 { 1402 ClassPrepareRequest x = mgr.createClassPrepareRequest (); // report class loads 1403 for (String s : Trace.EXCLUDE_GLOBS) 1404 x.addClassExclusionFilter (s); 1405 // x.setSuspendPolicy(EventRequest.SUSPEND_ALL); 1406 x.enable (); 1407 } 1408 { 1409 ClassUnloadRequest x = mgr.createClassUnloadRequest (); // report class unloads 1410 for (String s : Trace.EXCLUDE_GLOBS) 1411 x.addClassExclusionFilter (s); 1412 // x.setSuspendPolicy(EventRequest.SUSPEND_ALL); 1413 x.enable (); 1414 } 1415 { 1416 MethodEntryRequest x = mgr.createMethodEntryRequest (); // report method entries 1417 for (String s : Trace.EXCLUDE_GLOBS) 1418 x.addClassExclusionFilter (s); 1419 x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD); 1420 x.enable (); 1421 } 1422 { 1423 MethodExitRequest x = mgr.createMethodExitRequest (); // report method exits 1424 for (String s : Trace.EXCLUDE_GLOBS) 1425 x.addClassExclusionFilter (s); 1426 x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD); 1427 x.enable (); 1428 } 1429 { 1430 ExceptionRequest x = mgr.createExceptionRequest (null, true, true); // report all exceptions, caught and uncaught 1431 for (String s : Trace.EXCLUDE_GLOBS) 1432 x.addClassExclusionFilter (s); 1433 x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD); 1434 x.enable (); 1435 } 1436 { 1437 MonitorContendedEnterRequest x = mgr.createMonitorContendedEnterRequest (); 1438 for (String s : Trace.EXCLUDE_GLOBS) 1439 x.addClassExclusionFilter (s); 1440 x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD); 1441 x.enable (); 1442 } 1443 { 1444 MonitorContendedEnteredRequest x = mgr.createMonitorContendedEnteredRequest (); 1445 for (String s : Trace.EXCLUDE_GLOBS) 1446 x.addClassExclusionFilter (s); 1447 x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD); 1448 x.enable (); 1449 } 1450 { 1451 MonitorWaitRequest x = mgr.createMonitorWaitRequest (); 1452 for (String s : Trace.EXCLUDE_GLOBS) 1453 x.addClassExclusionFilter (s); 1454 x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD); 1455 x.enable (); 1456 } 1457 { 1458 MonitorWaitedRequest x = mgr.createMonitorWaitedRequest (); 1459 for (String s : Trace.EXCLUDE_GLOBS) 1460 x.addClassExclusionFilter (s); 1461 x.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD); 1462 x.enable (); 1463 } 1464 } 1465 1466 // process JDI events as they arrive on the event queue 1467 public void run () { 1468 EventQueue queue = vm.eventQueue (); 1469 while (connected) { 1470 try { 1471 EventSet eventSet = queue.remove (); 1472 for (Event event : eventSet) 1473 handleEvent (event); 1474 eventSet.resume (); 1475 } catch (InterruptedException e) { 1476 // Ignore 1477 } catch (VMDisconnectedException discExc) { 1478 handleDisconnectedException (); 1479 break; 1480 } 1481 } 1482 printer.printCallTree (); 1483 } 1484 1485 // process a JDI event 1486 private void handleEvent (Event event) { 1487 if (Trace.DEBUG) System.err.print (event.getClass ().getSimpleName ().replace ("EventImpl", "")); 1488 1489 // step event -- a line of code is about to be executed 1490 if (event instanceof StepEvent) { 1491 stepEvent ((StepEvent) event); 1492 return; 1493 } 1494 1495 // modified field event -- a field is about to be changed 1496 if (event instanceof ModificationWatchpointEvent) { 1497 modificationWatchpointEvent ((ModificationWatchpointEvent) event); 1498 return; 1499 } 1500 1501 // method events 1502 if (event instanceof MethodEntryEvent) { 1503 methodEntryEvent ((MethodEntryEvent) event); 1504 return; 1505 } 1506 if (event instanceof MethodExitEvent) { 1507 methodExitEvent ((MethodExitEvent) event); 1508 return; 1509 } 1510 if (event instanceof ExceptionEvent) { 1511 exceptionEvent ((ExceptionEvent) event); 1512 return; 1513 } 1514 1515 // monitor events 1516 if (event instanceof MonitorContendedEnterEvent) { 1517 monitorContendedEnterEvent ((MonitorContendedEnterEvent) event); 1518 return; 1519 } 1520 if (event instanceof MonitorContendedEnteredEvent) { 1521 monitorContendedEnteredEvent ((MonitorContendedEnteredEvent) event); 1522 return; 1523 } 1524 if (event instanceof MonitorWaitEvent) { 1525 monitorWaitEvent ((MonitorWaitEvent) event); 1526 return; 1527 } 1528 if (event instanceof MonitorWaitedEvent) { 1529 monitorWaitedEvent ((MonitorWaitedEvent) event); 1530 return; 1531 } 1532 1533 // class events 1534 if (event instanceof ClassPrepareEvent) { 1535 classPrepareEvent ((ClassPrepareEvent) event); 1536 return; 1537 } 1538 if (event instanceof ClassUnloadEvent) { 1539 classUnloadEvent ((ClassUnloadEvent) event); 1540 return; 1541 } 1542 1543 // thread events 1544 if (event instanceof ThreadStartEvent) { 1545 threadStartEvent ((ThreadStartEvent) event); 1546 return; 1547 } 1548 if (event instanceof ThreadDeathEvent) { 1549 threadDeathEvent ((ThreadDeathEvent) event); 1550 return; 1551 } 1552 1553 // VM events 1554 if (event instanceof VMStartEvent) { 1555 vmStartEvent ((VMStartEvent) event); 1556 return; 1557 } 1558 if (event instanceof VMDeathEvent) { 1559 vmDeathEvent ((VMDeathEvent) event); 1560 return; 1561 } 1562 if (event instanceof VMDisconnectEvent) { 1563 vmDisconnectEvent ((VMDisconnectEvent) event); 1564 return; 1565 } 1566 1567 throw new Error ("\n!!!! Unexpected event type: " + event.getClass ().getCanonicalName ()); 1568 } 1569 1570 // A VMDisconnectedException has occurred while dealing with another event. 1571 // Flush the event queue, dealing only with exit events (VMDeath, 1572 // VMDisconnect) so that things terminate correctly. 1573 private synchronized void handleDisconnectedException () { 1574 EventQueue queue = vm.eventQueue (); 1575 while (connected) { 1576 try { 1577 EventSet eventSet = queue.remove (); 1578 for (Event event : eventSet) { 1579 if (event instanceof VMDeathEvent) vmDeathEvent ((VMDeathEvent) event); 1580 else if (event instanceof VMDisconnectEvent) vmDisconnectEvent ((VMDisconnectEvent) event); 1581 } 1582 eventSet.resume (); // resume the VM 1583 } catch (InterruptedException e) { 1584 // ignore 1585 } catch (VMDisconnectedException e) { 1586 // ignore 1587 } 1588 } 1589 } 1590 1591 // ---------------------- VM event handling ---------------------------------- 1592 1593 // Notification of initialization of a target VM. This event is received 1594 // before the main thread is started and before any application code has 1595 // been executed. 1596 private void vmStartEvent (VMStartEvent event) { 1597 vmDied = false; 1598 printer.vmStartEvent (event); 1599 } 1600 1601 // Notification of VM termination 1602 private void vmDeathEvent (VMDeathEvent event) { 1603 vmDied = true; 1604 printer.vmDeathEvent (event); 1605 } 1606 1607 // Notification of disconnection from the VM, either through normal 1608 // termination or because of an exception/error. 1609 private void vmDisconnectEvent (VMDisconnectEvent event) { 1610 connected = false; 1611 if (!vmDied) printer.vmDisconnectEvent (event); 1612 } 1613 1614 // -------------------- class event handling --------------- 1615 1616 // a new class has been loaded 1617 private void classPrepareEvent (ClassPrepareEvent event) { 1618 ReferenceType type = event.referenceType (); 1619 String typeName = type.name (); 1620 if (Trace.CALLBACK_CLASS_NAME.equals (typeName) || Trace.GRAPHVIZ_CLASS_NAME.equals (typeName)) return; 1621 List<Field> fields = type.fields (); 1622 1623 // register field modification events 1624 EventRequestManager mgr = vm.eventRequestManager (); 1625 for (Field field : fields) { 1626 ModificationWatchpointRequest req = mgr.createModificationWatchpointRequest (field); 1627 for (String s : Trace.EXCLUDE_GLOBS) 1628 req.addClassExclusionFilter (s); 1629 req.setSuspendPolicy (EventRequest.SUSPEND_NONE); 1630 req.enable (); 1631 } 1632 printer.classPrepareEvent (event); 1633 1634 } 1635 // a class has been unloaded 1636 private void classUnloadEvent (ClassUnloadEvent event) { 1637 if (!vmDied) printer.classUnloadEvent (event); 1638 } 1639 1640 // -------------------- thread event handling --------------- 1641 1642 // a new thread has started running -- switch on single stepping 1643 private void threadStartEvent (ThreadStartEvent event) { 1644 ThreadReference thr = event.thread (); 1645 if (Format.ignoreThread (thr)) return; 1646 EventRequestManager mgr = vm.eventRequestManager (); 1647 1648 StepRequest sr = mgr.createStepRequest (thr, StepRequest.STEP_LINE, StepRequest.STEP_INTO); 1649 sr.setSuspendPolicy (EventRequest.SUSPEND_EVENT_THREAD); 1650 1651 for (String s : Trace.EXCLUDE_GLOBS) 1652 sr.addClassExclusionFilter (s); 1653 sr.enable (); 1654 printer.threadStartEvent (event); 1655 } 1656 1657 // the thread is about to terminate 1658 private void threadDeathEvent (ThreadDeathEvent event) { 1659 ThreadReference thr = event.thread (); 1660 if (Format.ignoreThread (thr)) return; 1661 printer.threadDeathEvent (event); 1662 } 1663 1664 // -------------------- delegated -------------------------------- 1665 1666 private void methodEntryEvent (MethodEntryEvent event) { 1667 printer.methodEntryEvent (event); 1668 } 1669 private void methodExitEvent (MethodExitEvent event) { 1670 printer.methodExitEvent (event); 1671 } 1672 private void exceptionEvent (ExceptionEvent event) { 1673 printer.exceptionEvent (event); 1674 } 1675 private void stepEvent (StepEvent event) { 1676 printer.stepEvent (event); 1677 } 1678 private void modificationWatchpointEvent (ModificationWatchpointEvent event) { 1679 printer.modificationWatchpointEvent (event); 1680 } 1681 private void monitorContendedEnterEvent (MonitorContendedEnterEvent event) { 1682 printer.monitorContendedEnterEvent (event); 1683 } 1684 private void monitorContendedEnteredEvent (MonitorContendedEnteredEvent event) { 1685 printer.monitorContendedEnteredEvent (event); 1686 } 1687 private void monitorWaitEvent (MonitorWaitEvent event) { 1688 printer.monitorWaitEvent (event); 1689 } 1690 private void monitorWaitedEvent (MonitorWaitedEvent event) { 1691 printer.monitorWaitedEvent (event); 1692 } 1693} 1694 1695/** 1696 * Printer for events. Prints and updates the ValueMap. Handles Graphviz drawing 1697 * requests. 1698 * 1699 * @author James Riely, [email protected], August 2014 1700 */ 1701/* private static */interface IndentPrinter { 1702 public void println (ThreadReference thr, String string); 1703} 1704 1705/* private static */interface JDIEventHandler { 1706 public void printCallTree (); 1707 /** Notification of target VM termination. */ 1708 public void vmDeathEvent (VMDeathEvent event); 1709 /** Notification of disconnection from target VM. */ 1710 public void vmDisconnectEvent (VMDisconnectEvent event); 1711 /** Notification of initialization of a target VM. */ 1712 public void vmStartEvent (VMStartEvent event); 1713 /** Notification of a new running thread in the target VM. */ 1714 public void threadStartEvent (ThreadStartEvent event); 1715 /** Notification of a completed thread in the target VM. */ 1716 public void threadDeathEvent (ThreadDeathEvent event); 1717 /** Notification of a class prepare in the target VM. */ 1718 public void classPrepareEvent (ClassPrepareEvent event); 1719 /** Notification of a class unload in the target VM. */ 1720 public void classUnloadEvent (ClassUnloadEvent event); 1721 /** Notification of a field access in the target VM. */ 1722 //public void accessWatchpointEvent (AccessWatchpointEvent event); 1723 /** Notification of a field modification in the target VM. */ 1724 public void modificationWatchpointEvent (ModificationWatchpointEvent event); 1725 /** Notification of a method invocation in the target VM. */ 1726 public void methodEntryEvent (MethodEntryEvent event); 1727 /** Notification of a method return in the target VM. */ 1728 public void methodExitEvent (MethodExitEvent event); 1729 /** Notification of an exception in the target VM. */ 1730 public void exceptionEvent (ExceptionEvent event); 1731 /** Notification of step completion in the target VM. */ 1732 public void stepEvent (StepEvent event); 1733 /** Notification of a breakpoint in the target VM. */ 1734 //public void breakpointEvent (BreakpointEvent event); 1735 /** 1736 * Notification that a thread in the target VM is attempting to enter a 1737 * monitor that is already acquired by another thread. 1738 */ 1739 public void monitorContendedEnterEvent (MonitorContendedEnterEvent event); 1740 /** 1741 * Notification that a thread in the target VM is entering a monitor after 1742 * waiting for it to be released by another thread. 1743 */ 1744 public void monitorContendedEnteredEvent (MonitorContendedEnteredEvent event); 1745 /** 1746 * Notification that a thread in the target VM is about to wait on a monitor 1747 * object. 1748 */ 1749 public void monitorWaitEvent (MonitorWaitEvent event); 1750 /** 1751 * Notification that a thread in the target VM has finished waiting on an 1752 * monitor object. 1753 */ 1754 public void monitorWaitedEvent (MonitorWaitedEvent event); 1755} 1756 1757/* private static */class Printer implements IndentPrinter, JDIEventHandler { 1758 private final Set<ReferenceType> staticClasses = new HashSet<> (); 1759 private final Map<ThreadReference, Value> returnValues = new HashMap<> (); 1760 private final Map<ThreadReference, Value> exceptionsMap = new HashMap<> (); 1761 private final ValueMap values = new ValueMap (); 1762 private final CodeMap codeMap = new CodeMap (); 1763 private final InsideIgnoredMethodMap boolMap = new InsideIgnoredMethodMap (); 1764 1765 public void monitorContendedEnterEvent (MonitorContendedEnterEvent event) {} 1766 public void monitorContendedEnteredEvent (MonitorContendedEnteredEvent event) {} 1767 public void monitorWaitEvent (MonitorWaitEvent event) {} 1768 public void monitorWaitedEvent (MonitorWaitedEvent event) {} 1769 1770 public void vmStartEvent (VMStartEvent event) { 1771 if (Trace.CONSOLE_SHOW_THREADS) println ("|||| VM Started"); 1772 } 1773 public void vmDeathEvent (VMDeathEvent event) { 1774 if (Trace.CONSOLE_SHOW_THREADS) println ("|||| VM Stopped"); 1775 } 1776 public void vmDisconnectEvent (VMDisconnectEvent event) { 1777 if (Trace.CONSOLE_SHOW_THREADS) println ("|||| VM Disconnected application"); 1778 } 1779 public void threadStartEvent (ThreadStartEvent event) { 1780 ThreadReference thr = event.thread (); 1781 values.stackCreate (thr); 1782 boolMap.addThread (thr); 1783 if (Trace.CONSOLE_SHOW_THREADS) println ("|||| thread started: " + thr.name ()); 1784 } 1785 public void threadDeathEvent (ThreadDeathEvent event) { 1786 ThreadReference thr = event.thread (); 1787 values.stackDestroy (thr); 1788 boolMap.removeThread (thr); 1789 if (Trace.CONSOLE_SHOW_THREADS) println ("|||| thread stopped: " + thr.name ()); 1790 } 1791 public void classPrepareEvent (ClassPrepareEvent event) { 1792 1793 ReferenceType ref = event.referenceType (); 1794 1795 List<Field> fields = ref.fields (); 1796 List<Method> methods = ref.methods (); 1797 1798 String filename; 1799 try { 1800 filename = ref.sourcePaths (null).get (0); // get filename of the class 1801 codeMap.addFile (filename); 1802 } catch (AbsentInformationException e) { 1803 filename = "??"; 1804 } 1805 1806 boolean hasConstructors = false; 1807 boolean hasObjectMethods = false; 1808 boolean hasClassMethods = false; 1809 boolean hasClassFields = false; 1810 boolean hasObjectFields = false; 1811 for (Method m : methods) { 1812 if (Format.isConstructor (m)) hasConstructors = true; 1813 if (Format.isObjectMethod (m)) hasObjectMethods = true; 1814 if (Format.isClassMethod (m)) hasClassMethods = true; 1815 } 1816 for (Field f : fields) { 1817 if (Format.isStaticField (f)) hasClassFields = true; 1818 if (Format.isObjectField (f)) hasObjectFields = true; 1819 } 1820 1821 if (hasClassFields) { 1822 staticClasses.add (ref); 1823 } 1824 if (Trace.CONSOLE_SHOW_CLASSES) { 1825 println ("|||| loaded class: " + ref.name () + " from " + filename); 1826 if (hasClassFields) { 1827 println ("|||| class fields: "); 1828 for (Field f : fields) 1829 if (Format.isStaticField (f)) println ("|||| " + Format.fieldToString (f)); 1830 } 1831 if (hasClassMethods) { 1832 println ("|||| class methods: "); 1833 for (Method m : methods) 1834 if (Format.isClassMethod (m)) println ("|||| " + Format.methodToString (m, false)); 1835 } 1836 if (hasConstructors) { 1837 println ("|||| constructors: "); 1838 for (Method m : methods) 1839 if (Format.isConstructor (m)) println ("|||| " + Format.methodToString (m, false)); 1840 } 1841 if (hasObjectFields) { 1842 println ("|||| object fields: "); 1843 for (Field f : fields) 1844 if (Format.isObjectField (f)) println ("|||| " + Format.fieldToString (f)); 1845 } 1846 if (hasObjectMethods) { 1847 println ("|||| object methods: "); 1848 for (Method m : methods) 1849 if (Format.isObjectMethod (m)) println ("|||| " + Format.methodToString (m, false)); 1850 } 1851 } 1852 } 1853 public void classUnloadEvent (ClassUnloadEvent event) { 1854 if (Trace.CONSOLE_SHOW_CLASSES) println ("|||| unloaded class: " + event.className ()); 1855 } 1856 1857 public void methodEntryEvent (MethodEntryEvent event) { 1858 Method meth = event.method (); 1859 ThreadReference thr = event.thread (); 1860 String calledMethodClassname = meth.declaringType ().name (); 1861 //System.err.println (calledMethodClassname); 1862 if (Format.matchesExcludePrefix (calledMethodClassname)) return; 1863 if (!Trace.SHOW_SYNTHETIC_METHODS && meth.isSynthetic ()) return; 1864 if (Trace.GRAPHVIZ_CLASS_NAME.equals (calledMethodClassname)) return; 1865 1866 if (!Trace.CALLBACK_CLASS_NAME.equals (calledMethodClassname)) { 1867 StackFrame currFrame = Format.getFrame (meth, thr); 1868 values.stackPushFrame (currFrame, thr); 1869 if (Trace.CONSOLE_SHOW_STEPS || Trace.CONSOLE_SHOW_CALLS) { 1870 println (thr, ">>>> " + Format.methodToString (meth, true)); // + "[" + thr.name () + "]"); 1871 printLocals (currFrame, thr); 1872 } 1873 } else { 1874 // COPY PASTE HORRORS HERE 1875 boolMap.enteringIgnoredMethod (thr); 1876 String name = meth.name (); 1877 if (Trace.CALLBACK_CLEAR_CALL_TREE.equals (name)) { 1878 values.clearCallTree(); 1879 } else if (Trace.CALLBACK_DRAW_STEPS_OF_METHOD.equals (name)) { 1880 List<StackFrame> frames; 1881 try { 1882 frames = thr.frames (); 1883 } catch (IncompatibleThreadStateException e) { 1884 throw new Error (Trace.BAD_ERROR_MESSAGE); 1885 } 1886 StackFrame currFrame = Format.getFrame (meth, thr); 1887 List<LocalVariable> locals; 1888 try { 1889 locals = currFrame.visibleVariables (); 1890 } catch (AbsentInformationException e) { 1891 return; 1892 } 1893 StringReference obj = (StringReference) currFrame.getValue (locals.get (0)); 1894 Trace.drawStepsOfMethodBegin (obj.value()); 1895 returnValues.put (thr, null); 1896 } else if (Trace.CALLBACK_DRAW_STEPS_OF_METHODS.equals (name)) { 1897 List<StackFrame> frames; 1898 try { 1899 frames = thr.frames (); 1900 } catch (IncompatibleThreadStateException e) { 1901 throw new Error (Trace.BAD_ERROR_MESSAGE); 1902 } 1903 StackFrame currFrame = Format.getFrame (meth, thr); 1904 List<LocalVariable> locals; 1905 try { 1906 locals = currFrame.visibleVariables (); 1907 } catch (AbsentInformationException e) { 1908 return; 1909 } 1910 ArrayReference arr = (ArrayReference) currFrame.getValue (locals.get (0)); 1911 for (int i = arr.length() - 1; i >= 0; i--) { 1912 StringReference obj = (StringReference) arr.getValue (i); 1913 Trace.drawStepsOfMethodBegin (obj.value()); 1914 } 1915 returnValues.put (thr, null); 1916 } else if (Trace.CALLBACK_DRAW_STEPS_BEGIN.equals (name)) { 1917 Trace.GRAPHVIZ_SHOW_STEPS = true; 1918 returnValues.put (thr, null); 1919 } else if (Trace.CALLBACK_DRAW_STEPS_END.equals (name)) { 1920 Trace.drawStepsOfMethodEnd (); 1921 } else if (Trace.CALLBACKS.contains (name)) { 1922 if (!Trace.GRAPHVIZ_SHOW_STEPS) { 1923 //System.err.println (calledMethodClassname + ":" + Trace.SPECIAL_METHOD_NAME + ":" + meth.name ()); 1924 StackFrame frame; 1925 try { 1926 frame = thr.frame (1); 1927 } catch (IncompatibleThreadStateException e) { 1928 throw new Error (Trace.BAD_ERROR_MESSAGE); 1929 } 1930 Location loc = frame.location (); 1931 String label = Format.methodToString (loc.method (), true, false, "_") + "_" + Integer.toString (loc.lineNumber ()); 1932 drawGraph (label, thr, meth); 1933 if (Trace.GRAPHVIZ_SHOW_FILENAMES_ON_CONSOLE && (Trace.CONSOLE_SHOW_STEPS)) printDrawEvent (thr, Graphviz.peekFilename ()); 1934 } 1935 } 1936 } 1937 } 1938 public void methodExitEvent (MethodExitEvent event) { 1939 ThreadReference thr = event.thread (); 1940 Method meth = event.method (); 1941 String calledMethodClassname = meth.declaringType ().name (); 1942 if (Format.matchesExcludePrefix (calledMethodClassname)) return; 1943 if (!Trace.SHOW_SYNTHETIC_METHODS && meth.isSynthetic ()) return; 1944 if (Trace.GRAPHVIZ_CLASS_NAME.equals (calledMethodClassname)) return; 1945 if (boolMap.leavingIgnoredMethod (thr)) return; 1946 if (Trace.CONSOLE_SHOW_STEPS || Trace.CONSOLE_SHOW_CALLS) { 1947 Type returnType; 1948 try { 1949 returnType = meth.returnType (); 1950 } catch (ClassNotLoadedException e) { 1951 returnType = null; 1952 } 1953 if (returnType instanceof VoidType) { 1954 println (thr, "<<<< " + Format.methodToString (meth, true)); 1955 } else { 1956 println (thr, "<<<< " + Format.methodToString (meth, true) + " : " + Format.valueToString (event.returnValue ())); 1957 } 1958 } 1959 values.stackPopFrame (thr); 1960 StackFrame currFrame = Format.getFrame (meth, thr); 1961 if (meth.isConstructor ()) { 1962 returnValues.put (thr, currFrame.thisObject ()); 1963 } else { 1964 returnValues.put (thr, event.returnValue ()); 1965 } 1966 } 1967 public void exceptionEvent (ExceptionEvent event) { 1968 ThreadReference thr = event.thread (); 1969 try { 1970 StackFrame currentFrame = thr.frame (0); 1971 } catch (IncompatibleThreadStateException e) { 1972 throw new Error (Trace.BAD_ERROR_MESSAGE); 1973 } 1974 ObjectReference exception = event.exception (); 1975 Location catchLocation = event.catchLocation (); 1976 //String name = Format.objectToStringLong (exception); 1977 String message = "()"; 1978 Field messageField = exception.referenceType ().fieldByName ("detailMessage"); 1979 if (messageField != null) { 1980 Value value = exception.getValue (messageField); 1981 if (value != null) { 1982 message = "(" + value.toString () + ")"; 1983 } 1984 } 1985 String name = Format.shortenFullyQualifiedName (exception.referenceType ().name ()) + message; 1986 1987 if (catchLocation == null) { 1988 // uncaught exception 1989 if (Trace.CONSOLE_SHOW_STEPS) println (thr, "!!!! UNCAUGHT EXCEPTION: " + name); 1990 if (Trace.GRAPHVIZ_SHOW_STEPS) Graphviz.drawFramesCheck (null, null, event.exception (), null, staticClasses); 1991 } else { 1992 if (Trace.CONSOLE_SHOW_STEPS) println (thr, "!!!! EXCEPTION: " + name); 1993 if (Trace.GRAPHVIZ_SHOW_STEPS) exceptionsMap.put (thr, event.exception ()); 1994 } 1995 } 1996 public void stepEvent (StepEvent event) { 1997 ThreadReference thr = event.thread (); 1998 if (boolMap.insideIgnoredMethod (thr)) { 1999 //System.err.println ("ignored"); 2000 return; 2001 } 2002 values.maybeAdjustAfterException (thr); 2003 2004 Location loc = event.location (); 2005 String filename; 2006 try { 2007 filename = loc.sourcePath (); 2008 } catch (AbsentInformationException e) { 2009 return; 2010 } 2011 if (Trace.CONSOLE_SHOW_STEPS) { 2012 values.stackUpdateFrame (event.location ().method (), thr, this); 2013 int lineNumber = loc.lineNumber (); 2014 if (Trace.CONSOLE_SHOW_STEPS_VERBOSE) { 2015 println (thr, Format.shortenFilename (filename) + ":" + lineNumber + codeMap.show (filename, lineNumber)); 2016 } else { 2017 printLineNum (thr, lineNumber); 2018 } 2019 } 2020 if (Trace.GRAPHVIZ_SHOW_STEPS) { 2021 try { 2022 Graphviz.drawFramesCheck (Format.methodToString (loc.method (), true, false, "_") + "_" + Integer.toString (loc.lineNumber ()), returnValues.get (thr), 2023 exceptionsMap.get (thr), thr.frames (), staticClasses); 2024 if (Trace.GRAPHVIZ_SHOW_FILENAMES_ON_CONSOLE && (Trace.CONSOLE_SHOW_STEPS)) printDrawEvent (thr, Graphviz.peekFilename ()); 2025 returnValues.put (thr, null); 2026 exceptionsMap.put (thr, null); 2027 } catch (IncompatibleThreadStateException e) { 2028 throw new Error (Trace.BAD_ERROR_MESSAGE); 2029 } 2030 } 2031 2032 } 2033 public void modificationWatchpointEvent (ModificationWatchpointEvent event) { 2034 ThreadReference thr = event.thread (); 2035 if (boolMap.insideIgnoredMethod (thr)) return; 2036 if (!Trace.CONSOLE_SHOW_STEPS) return; 2037 Field f = event.field (); 2038 Value value = event.valueToBe (); // value that _will_ be assigned 2039 String debug = Trace.DEBUG ? "#5" + "[" + thr.name () + "]" : ""; 2040 Type type; 2041 try { 2042 type = f.type (); 2043 } catch (ClassNotLoadedException e) { 2044 type = null; // waiting for class to load 2045 } 2046 2047 if (value instanceof ArrayReference) { 2048 if (f.isStatic ()) { 2049 String name = Format.shortenFullyQualifiedName (f.declaringType ().name ()) + "." + f.name (); 2050 if (values.registerStaticArray ((ArrayReference) value, name)) { 2051 println (thr, " " + debug + "> " + name + " = " + Format.valueToString (value)); 2052 } 2053 } 2054 return; // array types are handled separately -- this avoids redundant printing 2055 } 2056 ObjectReference objRef = event.object (); 2057 if (objRef == null) { 2058 println (thr, " " + debug + "> " + Format.shortenFullyQualifiedName (f.declaringType ().name ()) + "." + f.name () + " = " + Format.valueToString (value)); 2059 } else { 2060 // changes to array references are printed by updateFrame 2061 if (Format.tooManyFields (objRef)) { 2062 println (thr, " " + debug + "> " + Format.objectToStringShort (objRef) + "." + f.name () + " = " + Format.valueToString (value)); 2063 } else { 2064 println (thr, " " + debug + "> this = " + Format.objectToStringLong (objRef)); 2065 } 2066 } 2067 2068 } 2069 2070 public void printCallTree () { values.printCallTree(); } 2071 private void drawGraph (String loc, ThreadReference thr, Method meth) { 2072 List<StackFrame> frames; 2073 try { 2074 frames = thr.frames (); 2075 } catch (IncompatibleThreadStateException e) { 2076 throw new Error (Trace.BAD_ERROR_MESSAGE); 2077 } 2078 //setDrawPrefixFromParameter (Format.getFrame (meth, thr), meth); 2079 StackFrame currFrame = Format.getFrame (meth, thr); 2080 List<LocalVariable> locals; 2081 try { 2082 locals = currFrame.visibleVariables (); 2083 } catch (AbsentInformationException e) { 2084 return; 2085 } 2086 String name = meth.name (); 2087 if (Trace.CALLBACK_DRAW_THIS_FRAME.equals (name)) { 2088 List<StackFrame> thisFrame = new ArrayList<> (); 2089 try { 2090 thisFrame.add (thr.frame (1)); 2091 } catch (IncompatibleThreadStateException e) { 2092 throw new Error (Trace.BAD_ERROR_MESSAGE); 2093 } 2094 Graphviz.drawFrames (0, loc, null, null, thisFrame, null); 2095 } else if (Trace.CALLBACK_DRAW_ALL_FRAMES.equals (name) || locals.size () == 0) { 2096 Graphviz.drawFrames (1, loc, null, null, frames, staticClasses); 2097 } else if (Trace.CALLBACK_DRAW_OBJECT.equals (name)) { 2098 ObjectReference obj = (ObjectReference) currFrame.getValue (locals.get (0)); 2099 Map<String, ObjectReference> objects = new HashMap<> (); 2100 objects.put (Graphviz.PREFIX_UNUSED_LABEL, obj); 2101 Graphviz.drawObjects (loc, objects); 2102 } else if (Trace.CALLBACK_DRAW_OBJECT_NAMED.equals (name)) { 2103 StringReference str = (StringReference) currFrame.getValue (locals.get (0)); 2104 ObjectReference obj = (ObjectReference) currFrame.getValue (locals.get (1)); 2105 Map<String, ObjectReference> objects = new HashMap<> (); 2106 objects.put (str.value (), obj); 2107 Graphviz.drawObjects (loc, objects); 2108 } else if (Trace.CALLBACK_DRAW_OBJECTS_NAMED.equals (name)) { 2109 ArrayReference args = (ArrayReference) currFrame.getValue (locals.get (0)); 2110 Map<String, ObjectReference> objects = new HashMap<> (); 2111 int n = args.length (); 2112 if (n % 2 != 0) throw new Error ("\n!!!! " + Trace.CALLBACK_DRAW_OBJECTS_NAMED + " requires an even number of parameters, alternating strings and objects."); 2113 for (int i = 0; i < n; i += 2) { 2114 Value str = args.getValue (i); 2115 if (!(str instanceof StringReference)) throw new Error ("\n!!!! " + Trace.CALLBACK_DRAW_OBJECTS_NAMED 2116 + " requires an even number of parameters, alternating strings and objects."); 2117 objects.put (((StringReference) str).value (), (ObjectReference) args.getValue (i + 1)); 2118 } 2119 Graphviz.drawObjects (loc, objects); 2120 } else { 2121 ArrayReference args = (ArrayReference) currFrame.getValue (locals.get (0)); 2122 Map<String, ObjectReference> objects = new HashMap<> (); 2123 int n = args.length (); 2124 for (int i = 0; i < n; i++) { 2125 objects.put (Graphviz.PREFIX_UNUSED_LABEL + i, (ObjectReference) args.getValue (i)); 2126 } 2127 Graphviz.drawObjects (loc, objects); 2128 } 2129 } 2130 // This method was used to set the filename from the parameter of the draw method. 2131 // Not using this any more. 2132// private void setDrawPrefixFromParameter (StackFrame currFrame, Method meth) { 2133// String prefix = null; 2134// List<LocalVariable> locals; 2135// try { 2136// locals = currFrame.visibleVariables (); 2137// } catch (AbsentInformationException e) { 2138// return; 2139// } 2140// if (locals.size () >= 1) { 2141// Value v = currFrame.getValue (locals.get (0)); 2142// if (!(v instanceof StringReference)) throw new Error ("\n!!!! " + meth.name () + " must have at most a single parameter." 2143// + "\n!!!! The parameter must be of type String"); 2144// prefix = ((StringReference) v).value (); 2145// if (prefix != null) { 2146// Graphviz.setOutputFilenamePrefix (prefix); 2147// } 2148// } 2149// } 2150 2151 // ---------------------- print locals ---------------------------------- 2152 2153 private void printLocals (StackFrame currFrame, ThreadReference thr) { 2154 List<LocalVariable> locals; 2155 try { 2156 locals = currFrame.visibleVariables (); 2157 } catch (AbsentInformationException e) { 2158 return; 2159 } 2160 String debug = Trace.DEBUG ? "#3" : ""; 2161 2162 ObjectReference objRef = currFrame.thisObject (); // get 'this' object 2163 if (objRef != null) { 2164 if (Format.tooManyFields (objRef)) { 2165 println (thr, " " + debug + "this: " + Format.objectToStringShort (objRef)); 2166 ReferenceType type = objRef.referenceType (); // get type (class) of object 2167 List<Field> fields; // use allFields() to include inherited fields 2168 try { 2169 fields = type.fields (); 2170 } catch (ClassNotPreparedException e) { 2171 throw new Error (Trace.BAD_ERROR_MESSAGE); 2172 } 2173 2174 //println (thr, " fields: "); 2175 for (Field f : fields) { 2176 if (!Format.isObjectField (f)) continue; 2177 println (thr, " " + debug + "| " + Format.objectToStringShort (objRef) + "." + f.name () + " = " + Format.valueToString (objRef.getValue (f))); 2178 } 2179 if (locals.size () > 0) println (thr, " locals: "); 2180 } else { 2181 println (thr, " " + debug + "| this = " + Format.objectToStringLong (objRef)); 2182 } 2183 } 2184 for (LocalVariable l : locals) 2185 println (thr, " " + debug + "| " + l.name () + " = " + Format.valueToString (currFrame.getValue (l))); 2186 } 2187 2188 // ---------------------- indented printing ---------------------------------- 2189 2190 private boolean atNewLine = true; 2191 private static PrintStream out = System.out; 2192 public static void setFilename (String s) { 2193 try { 2194 Printer.out = new PrintStream (s); 2195 } catch (FileNotFoundException e) { 2196 System.err.println ("Attempting setFilename \"" + s + "\""); 2197 System.err.println ("Cannot open file \"" + s + "\" for writing; using the console for output."); 2198 } 2199 } 2200 public static void setFilename () { 2201 Printer.out = System.out; 2202 } 2203 public void println (String string) { 2204 if (!atNewLine) { 2205 atNewLine = true; 2206 Printer.out.println (); 2207 } 2208 Printer.out.println (string); 2209 } 2210 public void println (ThreadReference thr, String string) { 2211 if (!atNewLine) { 2212 atNewLine = true; 2213 Printer.out.println (); 2214 } 2215 if (values.numThreads () > 1) Printer.out.format ("%-9s: ", thr.name ()); 2216 int numFrames = (Trace.CONSOLE_SHOW_CALLS || Trace.CONSOLE_SHOW_STEPS) ? values.numFrames (thr) : 0; 2217 for (int i = 1; i < numFrames; i++) 2218 Printer.out.print (" "); 2219 Printer.out.println (string); 2220 } 2221 private void printLinePrefix (ThreadReference thr, boolean showLinePrompt) { 2222 if (atNewLine) { 2223 atNewLine = false; 2224 if (values.numThreads () > 1) Printer.out.format ("%-9s: ", thr.name ()); 2225 int numFrames = (Trace.CONSOLE_SHOW_CALLS || Trace.CONSOLE_SHOW_STEPS) ? values.numFrames (thr) : 0; 2226 for (int i = 1; i < numFrames; i++) 2227 Printer.out.print (" "); 2228 if (showLinePrompt) Printer.out.print (" Line: "); 2229 } 2230 } 2231 public void printLineNum (ThreadReference thr, int lineNumber) { 2232 printLinePrefix (thr, true); 2233 Printer.out.print (lineNumber + " "); 2234 } 2235 public void printDrawEvent (ThreadReference thr, String filename) { 2236 printLinePrefix (thr, false); 2237 Printer.out.print ("#" + filename + "# "); 2238 } 2239} 2240 2241/** 2242 * Code for formatting values and other static utilities. 2243 * 2244 * @author James Riely, [email protected], August 2014 2245 */ 2246/* private static */class Format { 2247 private Format () {}; // noninstantiable class 2248 2249 public static StackFrame getFrame (Method meth, ThreadReference thr) { 2250 Type methDeclaredType = meth.declaringType (); 2251 int frameNumber = -1; 2252 StackFrame currFrame; 2253 try { 2254 do { 2255 frameNumber++; 2256 currFrame = thr.frame (frameNumber); 2257 } while (methDeclaredType != currFrame.location ().declaringType ()); 2258 } catch (IncompatibleThreadStateException e) { 2259 throw new Error (Trace.BAD_ERROR_MESSAGE); 2260 } 2261 return currFrame; 2262 } 2263 // http://stackoverflow.com/questions/1247772/is-there-an-equivalent-of-java-util-regex-for-glob-type-patterns 2264 public static String glob2regex (String glob) { 2265 StringBuilder regex = new StringBuilder ("^"); 2266 for(int i = 0; i < glob.length(); ++i) { 2267 final char c = glob.charAt(i); 2268 switch(c) { 2269 case '*': regex.append (".*"); break; 2270 case '.': regex.append ("\\."); break; 2271 case '$': regex.append ("\\$"); break; 2272 default: regex.append (c); 2273 } 2274 } 2275 regex.append ('$'); 2276 return regex.toString (); 2277 } 2278 private static final ArrayList<String> EXCLUDE_REGEX; 2279 private static final ArrayList<String> DRAWING_INCLUDE_REGEX; 2280 static { 2281 EXCLUDE_REGEX = new ArrayList<> (); 2282 for (String s : Trace.EXCLUDE_GLOBS) { 2283 //System.err.println (glob2regex (s)); 2284 EXCLUDE_REGEX.add (glob2regex (s)); 2285 } 2286 DRAWING_INCLUDE_REGEX = new ArrayList<> (); 2287 for (String s : Trace.DRAWING_INCLUDE_GLOBS) { 2288 DRAWING_INCLUDE_REGEX.add (glob2regex (s)); 2289 } 2290 } 2291 public static boolean matchesExcludePrefix (String typeName) { 2292 //System.err.println (typeName + ":" + Trace.class.getName()); 2293 if (!Trace.SHOW_STRINGS_AS_PRIMITIVE && "java.lang.String".equals (typeName)) return false; 2294 // don't explore objects on the exclude list 2295 for (String regex : Format.EXCLUDE_REGEX) 2296 if (typeName.matches (regex)) return true; 2297 return false; 2298 } 2299 public static boolean matchesExcludePrefixShow (String typeName) { 2300 for (String regex : Format.DRAWING_INCLUDE_REGEX) 2301 if (typeName.matches (regex)) return false; 2302 return matchesExcludePrefix (typeName); 2303 } 2304 public static String valueToString (Value value) { 2305 return valueToString (false, new HashSet<> (), value); 2306 } 2307 private static String valueToString (boolean inArray, Set<Value> visited, Value value) { 2308 if (value == null) return "null"; 2309 if (value instanceof PrimitiveValue) return value.toString (); 2310 if (Trace.SHOW_STRINGS_AS_PRIMITIVE && value instanceof StringReference) return value.toString (); 2311 if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && isWrapper (value.type ())) return wrapperToString ((ObjectReference) value); 2312 return objectToStringLong (inArray, visited, (ObjectReference) value); 2313 } 2314 public static String valueToStringShort (Value value) { 2315 if (value == null) return "null"; 2316 if (value instanceof PrimitiveValue) return value.toString (); 2317 if (Trace.SHOW_STRINGS_AS_PRIMITIVE && value instanceof StringReference) return value.toString (); 2318 if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && isWrapper (value.type ())) return wrapperToString ((ObjectReference) value); 2319 return objectToStringShort ((ObjectReference) value); 2320 } 2321 public static boolean isWrapper (Type type) { 2322 if (!(type instanceof ReferenceType)) return false; 2323 if (type instanceof ArrayType) return false; 2324 String fqn = type.name (); 2325 if (!fqn.startsWith ("java.lang.")) return false; 2326 String className = fqn.substring (10); 2327 if (className.equals ("String")) return false; 2328 return (className.equals ("Integer") || className.equals ("Double") || className.equals ("Float") || className.equals ("Long") || className.equals ("Character") 2329 || className.equals ("Short") || className.equals ("Byte") || className.equals ("Boolean")); 2330 } 2331 public static String wrapperToString (ObjectReference obj) { 2332 Object xObject; 2333 if (obj == null) return "null"; 2334 ReferenceType cz = (ReferenceType) obj.type (); 2335 String fqn = cz.name (); 2336 String className = fqn.substring (10); 2337 Field field = cz.fieldByName ("value"); 2338 return obj.getValue (field).toString (); 2339 } 2340 public static String objectToStringShort (ObjectReference objRef) { 2341 if (Trace.CONSOLE_SHOW_TYPE_IN_OBJECT_NAME) return shortenFullyQualifiedName (objRef.type ().name ()) + "@" + objRef.uniqueID (); 2342 else return "@" + objRef.uniqueID (); 2343 } 2344 private static String emptyArrayToStringShort (ArrayReference arrayRef, int length) { 2345 if (Trace.CONSOLE_SHOW_TYPE_IN_OBJECT_NAME) { 2346 String classname = shortenFullyQualifiedName (arrayRef.type ().name ()); 2347 return classname.substring (0, classname.indexOf ("[")) + "[" + length + "]@" + arrayRef.uniqueID (); 2348 } else { 2349 return "@" + arrayRef.uniqueID (); 2350 } 2351 } 2352 private static String nonemptyArrayToStringShort (ArrayReference arrayRef, int length) { 2353 if (Trace.CONSOLE_SHOW_TYPE_IN_OBJECT_NAME) return shortenFullyQualifiedName (arrayRef.getValue (0).type ().name ()) + "[" + length + "]@" + arrayRef.uniqueID (); 2354 else return "@" + arrayRef.uniqueID (); 2355 } 2356 2357 public static String objectToStringLong (ObjectReference objRef) { 2358 return objectToStringLong (false, new HashSet<> (), objRef); 2359 } 2360 private static String objectToStringLong (boolean inArray, Set<Value> visited, ObjectReference objRef) { 2361 if (!visited.add (objRef)) return objectToStringShort (objRef); 2362 StringBuilder result = new StringBuilder (); 2363 if (objRef == null) { 2364 return "null"; 2365 } else if (objRef instanceof ArrayReference) { 2366 ArrayReference arrayRef = (ArrayReference) objRef; 2367 int length = arrayRef.length (); 2368 if (length == 0 || arrayRef.getValue (0) == null) { 2369 if (!inArray || Trace.CONSOLE_SHOW_NESTED_ARRAY_IDS) { 2370 result.append (emptyArrayToStringShort (arrayRef, length)); 2371 result.append (" "); 2372 } 2373 result.append ("[ ] "); 2374 } else { 2375 if (!inArray || Trace.CONSOLE_SHOW_NESTED_ARRAY_IDS) { 2376 result.append (nonemptyArrayToStringShort (arrayRef, length)); 2377 result.append (" "); 2378 } 2379 result.append ("[ "); 2380 int max = (arrayRef.getValue (0) instanceof PrimitiveValue) ? Trace.CONSOLE_MAX_ARRAY_ELEMENTS_PRIMITIVE : Trace.CONSOLE_MAX_ARRAY_ELEMENTS_OBJECT; 2381 int i = 0; 2382 while (i < length && i < max) { 2383 result.append (valueToString (true, visited, arrayRef.getValue (i))); 2384 i++; 2385 if (i < length) result.append (", "); 2386 } 2387 if (i < length) result.append ("..."); 2388 result.append (" ]"); 2389 } 2390 } else { 2391 result.append (objectToStringShort (objRef)); 2392 ReferenceType type = objRef.referenceType (); // get type (class) of object 2393 2394 // don't explore objects on the exclude list 2395 if (!Format.matchesExcludePrefixShow (type.name ())) { 2396 Iterator<Field> fields; // use allFields() to include inherited fields 2397 try { 2398 fields = type.fields ().iterator (); 2399 } catch (ClassNotPreparedException e) { 2400 throw new Error (Trace.BAD_ERROR_MESSAGE); 2401 } 2402 if (fields.hasNext ()) { 2403 result.append (" { "); 2404 int i = 0; 2405 while (fields.hasNext () && i < Trace.CONSOLE_MAX_FIELDS) { 2406 Field f = fields.next (); 2407 if (!isObjectField (f)) continue; 2408 if (i != 0) result.append (", "); 2409 result.append (f.name ()); 2410 result.append ("="); 2411 result.append (valueToString (inArray, visited, objRef.getValue (f))); 2412 i++; 2413 } 2414 if (fields.hasNext ()) result.append ("..."); 2415 result.append (" }"); 2416 } 2417 } 2418 } 2419 return result.toString (); 2420 } 2421 2422 // ---------------------- static utilities ---------------------------------- 2423 2424 public static boolean ignoreThread (ThreadReference thr) { 2425 if (thr.name ().equals ("Signal Dispatcher") || thr.name ().equals ("DestroyJavaVM") || thr.name ().startsWith ("AWT-")) return true; // ignore AWT threads 2426 if (thr.threadGroup ().name ().equals ("system")) return true; // ignore system threads 2427 return false; 2428 } 2429 2430 public static boolean isStaticField (Field f) { 2431 if (!Trace.SHOW_SYNTHETIC_FIELDS && f.isSynthetic ()) return false; 2432 return f.isStatic (); 2433 } 2434 public static boolean isObjectField (Field f) { 2435 if (!Trace.SHOW_SYNTHETIC_FIELDS && f.isSynthetic ()) return false; 2436 return !f.isStatic (); 2437 } 2438 public static boolean isConstructor (Method m) { 2439 if (!Trace.SHOW_SYNTHETIC_METHODS && m.isSynthetic ()) return false; 2440 return m.isConstructor (); 2441 } 2442 public static boolean isObjectMethod (Method m) { 2443 if (!Trace.SHOW_SYNTHETIC_METHODS && m.isSynthetic ()) return false; 2444 return !m.isConstructor () && !m.isStatic (); 2445 } 2446 public static boolean isClassMethod (Method m) { 2447 if (!Trace.SHOW_SYNTHETIC_METHODS && m.isSynthetic ()) return false; 2448 return m.isStatic (); 2449 } 2450 public static boolean tooManyFields (ObjectReference objRef) { 2451 int count = 0; 2452 ReferenceType type = ((ReferenceType) objRef.type ()); 2453 for (Field field : type.fields ()) 2454 if (isObjectField (field)) count++; 2455 return count > Trace.CONSOLE_MAX_FIELDS; 2456 } 2457 public static String shortenFullyQualifiedName (String fqn) { 2458 if (Trace.SHOW_PACKAGE_IN_CLASS_NAME || !fqn.contains (".")) return fqn; 2459 String className = fqn.substring (1 + fqn.lastIndexOf (".")); 2460 if (Trace.SHOW_OUTER_CLASS_IN_CLASS_NAME || !className.contains ("$")) return className; 2461 return className.substring (1 + className.lastIndexOf ("$")); 2462 } 2463 public static String shortenFilename (String fn) { 2464 if (!fn.contains ("/")) return fn; 2465 return fn.substring (1 + fn.lastIndexOf ("/")); 2466 } 2467 public static String fieldToString (Field f) { 2468 StringBuilder result = new StringBuilder (); 2469 if (f.isPrivate ()) result.append ("- "); 2470 if (f.isPublic ()) result.append ("+ "); 2471 if (f.isPackagePrivate ()) result.append ("~ "); 2472 if (f.isProtected ()) result.append ("# "); 2473 result.append (shortenFullyQualifiedName (f.name ())); 2474 result.append (" : "); 2475 result.append (shortenFullyQualifiedName (f.typeName ())); 2476 return result.toString (); 2477 } 2478 public static String methodToString (Method m, boolean showClass) { 2479 return methodToString (m, showClass, true, "."); 2480 } 2481 public static String methodToString (Method m, boolean showClass, boolean showParameters, String dotCharacter) { 2482 String className = shortenFullyQualifiedName (m.declaringType ().name ()); 2483 StringBuilder result = new StringBuilder (); 2484 if (!showClass && showParameters) { 2485 if (m.isPrivate ()) result.append ("- "); 2486 if (m.isPublic ()) result.append ("+ "); 2487 if (m.isPackagePrivate ()) result.append ("~ "); 2488 if (m.isProtected ()) result.append ("# "); 2489 } 2490 if (m.isConstructor ()) { 2491 result.append (className); 2492 } else if (m.isStaticInitializer ()) { 2493 result.append (className); 2494 result.append (".CLASS_INITIALIZER"); 2495 return result.toString (); 2496 } else { 2497 if (showClass) { 2498 result.append (className); 2499 result.append (dotCharacter); 2500 } 2501 result.append (shortenFullyQualifiedName (m.name ())); 2502 } 2503 if (showParameters) { 2504 result.append ("("); 2505 Iterator<LocalVariable> vars; 2506 try { 2507 vars = m.arguments ().iterator (); 2508 while (vars.hasNext ()) { 2509 result.append (shortenFullyQualifiedName (vars.next ().typeName ())); 2510 if (vars.hasNext ()) result.append (", "); 2511 } 2512 } catch (AbsentInformationException e) { 2513 result.append ("??"); 2514 } 2515 result.append (")"); 2516 } 2517 //result.append (" from "); 2518 //result.append (m.declaringType ()); 2519 return result.toString (); 2520 } 2521} 2522 2523/** 2524 * A map from filenames to file contents. Allows lines to be printed. 2525 * 2526 * changes: Riely inlined the "ShowLines" class. 2527 * 2528 * @author Andrew Davison, March 2009, [email protected] 2529 * @author James Riely 2530 **/ 2531/* private static */class CodeMap { 2532 private TreeMap<String, ArrayList<String>> listings = new TreeMap<> (); 2533 2534 // add filename-ShowLines pair to map 2535 public void addFile (String filename) { 2536 if (listings.containsKey (filename)) { 2537 //System.err.println (filename + "already listed"); 2538 return; 2539 } 2540 2541 ArrayList<String> code = new ArrayList<> (); 2542 BufferedReader in = null; 2543 try { 2544 in = new BufferedReader (new FileReader ("src/" + filename)); 2545 String line; 2546 while ((line = in.readLine ()) != null) 2547 code.add (line); 2548 } catch (IOException ex) { 2549 System.err.println ("\n!!!! Problem reading " + filename); 2550 } finally { 2551 try { 2552 if (in != null) in.close (); 2553 } catch (IOException e) { 2554 throw new Error ("\n!!!! Problem with " + filename); 2555 } 2556 } 2557 listings.put (filename, code); 2558 //println (filename + " added to listings"); 2559 } 2560 2561 // return the specified line from filename 2562 public String show (String filename, int lineNumber) { 2563 ArrayList<String> code = listings.get (filename); 2564 if (code == null) return (filename + " not listed"); 2565 if ((lineNumber < 1) || (lineNumber > code.size ())) return "Line no. out of range"; 2566 return (code.get (lineNumber - 1)); 2567 } 2568 2569} 2570 2571/** 2572 * Map from threads to booleans. 2573 * 2574 * @author James Riely, [email protected], August 2014 2575 */ 2576/* private static */class InsideIgnoredMethodMap { 2577 // Stack is probably unnecessary here. A single boolean would do. 2578 private HashMap<ThreadReference, Stack<Boolean>> map = new HashMap<> (); 2579 public void removeThread (ThreadReference thr) { 2580 map.remove (thr); 2581 } 2582 public void addThread (ThreadReference thr) { 2583 Stack<Boolean> st = new Stack<> (); 2584 st.push (false); 2585 map.put (thr, st); 2586 } 2587 public void enteringIgnoredMethod (ThreadReference thr) { 2588 map.get (thr).push (true); 2589 } 2590 public boolean leavingIgnoredMethod (ThreadReference thr) { 2591 Stack<Boolean> insideStack = map.get (thr); 2592 boolean result = insideStack.peek (); 2593 if (result) insideStack.pop (); 2594 return result; 2595 } 2596 public boolean insideIgnoredMethod (ThreadReference thr) { 2597 return map.get (thr).peek (); 2598 } 2599} 2600 2601/** From sedgewick and wayne */ 2602/* private static */class Stack<T> { 2603 private int N; 2604 private Node<T> first; 2605 private static class Node<T> { 2606 T item; 2607 Node<T> next; 2608 } 2609 public Stack () { 2610 first = null; 2611 N = 0; 2612 } 2613 public boolean isEmpty () { 2614 return first == null; 2615 } 2616 public int size () { 2617 return N; 2618 } 2619 public void push (T item) { 2620 Node<T> oldfirst = first; 2621 first = new Node<> (); 2622 first.item = item; 2623 first.next = oldfirst; 2624 N++; 2625 } 2626 public T pop () { 2627 if (isEmpty ()) throw new NoSuchElementException ("Stack underflow"); 2628 T item = first.item; 2629 first = first.next; 2630 N--; 2631 return item; 2632 } 2633 public void pop (int n) { 2634 for (int i=n; i>0; i--) 2635 pop (); 2636 } 2637 public T peek () { 2638 if (isEmpty ()) throw new NoSuchElementException ("Stack underflow"); 2639 return first.item; 2640 } 2641} 2642 2643/** 2644 * Keeps track of values in order to spot changes. This keeps copies of stack 2645 * variables (frames) and arrays. Does not store objects, since direct changes 2646 * to fields can be trapped by the JDI. 2647 * 2648 * @author James Riely, [email protected], August 2014 2649 */ 2650/* private static */class ValueMap { 2651 private HashMap<ThreadReference, Stack<HashMap<LocalVariable, Value>>> stacks = new HashMap<> (); 2652 private HashMap<ArrayReference, Object[]> arrays = new HashMap<> (); 2653 private HashMap<ArrayReference, Object[]> staticArrays = new HashMap<> (); 2654 private HashMap<ArrayReference, String> staticArrayNames = new HashMap<> (); 2655 private CallTree callTree = new CallTree (); 2656 public int numThreads () { 2657 return stacks.size (); 2658 } 2659 public void clearCallTree () { 2660 callTree = new CallTree (); 2661 } 2662 public void printCallTree () { 2663 callTree.output (); 2664 } 2665 private static class CallTree { 2666 private HashMap<ThreadReference, Stack<String>> frameIdsMap = new HashMap<> (); 2667 private HashMap<ThreadReference, List<String>> gvStringsMap = new HashMap<> (); 2668 private int frameNumber = 0; 2669 2670 public void output () { 2671 if (!Trace.SHOW_CALL_TREE) return; 2672 Graphviz.drawStuff ("callTree", (out) -> { 2673 out.println ("rankdir=LR;"); 2674 for (List<String> gvStrings : gvStringsMap.values ()) 2675 for (String s : gvStrings) { 2676 out.println (s); 2677 } 2678 }); 2679 } 2680 public void pop (ThreadReference thr) { 2681 if (!Trace.SHOW_CALL_TREE) return; 2682 if (!Trace.drawStepsOfInternal (thr)) return; 2683 2684 Stack<String> frameIds = frameIdsMap.get(thr); 2685 if (!frameIds.isEmpty ()) 2686 frameIds.pop (); 2687 } 2688 public void push (StackFrame currFrame, ThreadReference thr) { 2689 if (!Trace.SHOW_CALL_TREE) return; 2690 if (!Trace.drawStepsOfInternal (thr)) return; 2691 2692 Stack<String> frameIds = frameIdsMap.get(thr); 2693 if (frameIds==null) { frameIds = new Stack<> (); frameIdsMap.put (thr, frameIds); } 2694 List<String> gvStrings = gvStringsMap.get (thr); 2695 if (gvStrings==null) { gvStrings = new LinkedList<> (); gvStringsMap.put (thr, gvStrings); } 2696 2697 String currentFrameId = "f" + frameNumber++; 2698 StringBuilder sb = new StringBuilder (); 2699 Method method = currFrame.location ().method (); 2700 sb.append (currentFrameId); 2701 sb.append ("[label=\""); 2702 if (method.isSynthetic ()) sb.append ("!"); 2703 if (!method.isStatic ()) { 2704 sb.append (Graphviz.quote (Format.valueToStringShort (currFrame.thisObject ()))); 2705 sb.append (":"); 2706 } 2707 sb.append (Graphviz.quote (Format.methodToString (method, true, false, "."))); 2708 //sb.append (Graphviz.quote (method.name ())); 2709 sb.append ("("); 2710 List<LocalVariable> locals = null; 2711 try { locals = currFrame.visibleVariables (); } catch (AbsentInformationException e) { } 2712 if (locals != null) { 2713 boolean first = true; 2714 for (LocalVariable l : locals) 2715 if (l.isArgument ()) { 2716 if (!first) sb.append (", "); 2717 else first = false; 2718 // TODO working here (but I don't remember what I was doing...) 2719 String valString = Format.valueToString (currFrame.getValue (l)); 2720 //Value val = currFrame.getValue (l); 2721 //String valString = val==null ? "null" : val.toString (); 2722 sb.append (Graphviz.quote (valString)); 2723 } 2724 } 2725 sb.append (")\""); 2726 sb.append (Trace.GRAPHVIZ_CALL_TREE_BOX_ATTRIBUTES); 2727 sb.append ("];"); 2728 gvStrings.add (sb.toString ()); 2729 if (!frameIds.isEmpty ()) { 2730 gvStrings.add (frameIds.peek () + " -> " + currentFrameId + "[label=\"\"" + Trace.GRAPHVIZ_CALL_TREE_ARROW_ATTRIBUTES + "];"); 2731 } 2732 frameIds.push (currentFrameId); 2733 } 2734 } 2735 2736 public boolean maybeAdjustAfterException (ThreadReference thr) { 2737 Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr); 2738 2739 // count the number of frames left 2740 int oldCount = stack.size (); 2741 int currentCount = 0; 2742 List<StackFrame> frames; 2743 try { 2744 frames = thr.frames (); 2745 } catch (IncompatibleThreadStateException e) { 2746 throw new Error (Trace.BAD_ERROR_MESSAGE); 2747 } 2748 2749 for (StackFrame frame : frames) { 2750 String calledMethodClassname = frame.location ().declaringType ().name (); 2751 if (!Format.matchesExcludePrefix (calledMethodClassname)) currentCount++; 2752 } 2753 2754 if (oldCount > currentCount) { 2755 for (int i = oldCount - currentCount; i > 0; i--) { 2756 stack.pop (); 2757 callTree.pop (thr); 2758 } 2759 return true; 2760 } 2761 return false; 2762 } 2763 public int numFrames (ThreadReference thr) { 2764 return stacks.get (thr).size (); 2765 } 2766 public void stackCreate (ThreadReference thr) { 2767 stacks.put (thr, new Stack<> ()); 2768 } 2769 public void stackDestroy (ThreadReference thr) { 2770 stacks.remove (thr); 2771 } 2772 public void stackPushFrame (StackFrame currFrame, ThreadReference thr) { 2773 if (!Trace.CONSOLE_SHOW_VARIABLES) return; 2774 callTree.push (currFrame, thr); 2775 List<LocalVariable> locals; 2776 try { 2777 locals = currFrame.visibleVariables (); 2778 } catch (AbsentInformationException e) { 2779 return; 2780 } 2781 2782 Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr); 2783 HashMap<LocalVariable, Value> frame = new HashMap<> (); 2784 stack.push (frame); 2785 2786 for (LocalVariable l : locals) { 2787 Value v = currFrame.getValue (l); 2788 frame.put (l, v); 2789 if (v instanceof ArrayReference) registerArray ((ArrayReference) v); 2790 } 2791 } 2792 2793 public void stackPopFrame (ThreadReference thr) { 2794 if (!Trace.CONSOLE_SHOW_VARIABLES) return; 2795 callTree.pop (thr); 2796 Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr); 2797 stack.pop (); 2798 // space leak in arrays HashMap: arrays never removed 2799 } 2800 2801 public void stackUpdateFrame (Method meth, ThreadReference thr, IndentPrinter printer) { 2802 if (!Trace.CONSOLE_SHOW_VARIABLES) return; 2803 StackFrame currFrame = Format.getFrame (meth, thr); 2804 List<LocalVariable> locals; 2805 try { 2806 locals = currFrame.visibleVariables (); 2807 } catch (AbsentInformationException e) { 2808 return; 2809 } 2810 Stack<HashMap<LocalVariable, Value>> stack = stacks.get (thr); 2811 if (stack.isEmpty ()) { 2812 throw new Error ("\n!!!! Frame empty: " + meth + " : " + thr); 2813 } 2814 HashMap<LocalVariable, Value> frame = stack.peek (); 2815 2816 String debug = Trace.DEBUG ? "#1" : ""; 2817 for (LocalVariable l : locals) { 2818 Value oldValue = frame.get (l); 2819 Value newValue = currFrame.getValue (l); 2820 if (valueHasChanged (oldValue, newValue)) { 2821 frame.put (l, newValue); 2822 if (newValue instanceof ArrayReference) registerArray ((ArrayReference) newValue); 2823 String change = (oldValue == null) ? "|" : ">"; 2824 printer.println (thr, " " + debug + change + " " + l.name () + " = " + Format.valueToString (newValue)); 2825 } 2826 } 2827 2828 ObjectReference thisObj = currFrame.thisObject (); 2829 if (thisObj != null) { 2830 boolean show = Format.tooManyFields (thisObj); 2831 if (arrayFieldHasChanged (show, thr, thisObj, printer) && !show) printer.println (thr, " " + debug + "> this = " + Format.objectToStringLong (thisObj)); 2832 } 2833 arrayStaticFieldHasChanged (true, thr, printer); 2834 } 2835 2836 public void registerArray (ArrayReference val) { 2837 if (!arrays.containsKey (val)) { 2838 arrays.put (val, copyArray (val)); 2839 } 2840 } 2841 public boolean registerStaticArray (ArrayReference val, String name) { 2842 if (!staticArrays.containsKey (val)) { 2843 staticArrays.put (val, copyArray (val)); 2844 staticArrayNames.put (val, name); 2845 return true; 2846 } 2847 return false; 2848 } 2849 private static Object[] copyArray (ArrayReference oldArrayReference) { 2850 Object[] newArray = new Object[oldArrayReference.length ()]; 2851 for (int i = 0; i < newArray.length; i++) { 2852 Value val = oldArrayReference.getValue (i); 2853 if (val instanceof ArrayReference) newArray[i] = copyArray ((ArrayReference) val); 2854 else newArray[i] = val; 2855 } 2856 return newArray; 2857 } 2858 2859 private boolean valueHasChanged (Value oldValue, Value newValue) { 2860 if (oldValue == null && newValue == null) return false; 2861 if (oldValue == null && newValue != null) return true; 2862 if (oldValue != null && newValue == null) return true; 2863 if (!oldValue.equals (newValue)) return true; 2864 if (!(oldValue instanceof ArrayReference)) return false; 2865 return arrayValueHasChanged ((ArrayReference) oldValue, (ArrayReference) newValue); 2866 } 2867 private boolean arrayStaticFieldHasChanged (Boolean show, ThreadReference thr, IndentPrinter printer) { 2868 boolean result = false; 2869 boolean print = false; 2870 String debug = Trace.DEBUG ? "#7" : ""; 2871 String change = ">"; 2872 for (ArrayReference a : staticArrays.keySet ()) { 2873 Object[] objArray = staticArrays.get (a); 2874 if (arrayValueHasChangedHelper (objArray, a)) { 2875 result = true; 2876 print = true; 2877 } 2878 if (show && print) { 2879 printer.println (thr, " " + debug + change + " " + staticArrayNames.get (a) + " = " + Format.valueToString (a)); 2880 } 2881 } 2882 return result; 2883 } 2884 private boolean arrayFieldHasChanged (Boolean show, ThreadReference thr, ObjectReference objRef, IndentPrinter printer) { 2885 ReferenceType type = objRef.referenceType (); // get type (class) of object 2886 List<Field> fields; // use allFields() to include inherited fields 2887 try { 2888 fields = type.fields (); 2889 } catch (ClassNotPreparedException e) { 2890 throw new Error (Trace.BAD_ERROR_MESSAGE); 2891 } 2892 boolean result = false; 2893 String debug = Trace.DEBUG ? "#2" : ""; 2894 String change = ">"; 2895 for (Field f : fields) { 2896 Boolean print = false; 2897 Value v = objRef.getValue (f); 2898 if (!(v instanceof ArrayReference)) continue; 2899 ArrayReference a = (ArrayReference) v; 2900 if (!arrays.containsKey (a)) { 2901 registerArray (a); 2902 change = "|"; 2903 result = true; 2904 print = true; 2905 } else { 2906 Object[] objArray = arrays.get (a); 2907 if (arrayValueHasChangedHelper (objArray, a)) { 2908 result = true; 2909 print = true; 2910 } 2911 } 2912 if (show && print) { 2913 printer.println (thr, " " + debug + change + " " + Format.objectToStringShort (objRef) + "." + f.name () + " = " + Format.valueToString (objRef.getValue (f))); 2914 } 2915 } 2916 return result; 2917 } 2918 private boolean arrayValueHasChanged (ArrayReference oldArray, ArrayReference newArray) { 2919 if (oldArray.length () != newArray.length ()) return true; 2920 int len = oldArray.length (); 2921 if (!arrays.containsKey (newArray)) { 2922 return true; 2923 } 2924 Object[] oldObjArray = arrays.get (newArray); 2925 // if (oldObjArray.length != len) 2926 // throw new Error (Trace.BAD_ERROR_MESSAGE); 2927 return arrayValueHasChangedHelper (oldObjArray, newArray); 2928 } 2929 private boolean arrayValueHasChangedHelper (Object[] oldObjArray, ArrayReference newArray) { 2930 int len = oldObjArray.length; 2931 boolean hasChanged = false; 2932 for (int i = 0; i < len; i++) { 2933 Object oldObject = oldObjArray[i]; 2934 Value newVal = newArray.getValue (i); 2935 if (oldObject == null && newVal != null) { 2936 oldObjArray[i] = newVal; 2937 hasChanged = true; 2938 } 2939 if (oldObject instanceof Value && valueHasChanged ((Value) oldObject, newVal)) { 2940 //System.out.println ("BOB:" + i + ":" + oldObject + ":" + newVal); 2941 oldObjArray[i] = newVal; 2942 hasChanged = true; 2943 } 2944 if (oldObject instanceof Object[]) { 2945 //if (!(newVal instanceof ArrayReference)) throw new Error (Trace.BAD_ERROR_MESSAGE); 2946 if (arrayValueHasChangedHelper ((Object[]) oldObject, (ArrayReference) newVal)) { 2947 hasChanged = true; 2948 } 2949 } 2950 } 2951 return hasChanged; 2952 } 2953} 2954 2955/* private static */class Graphviz { 2956 private Graphviz () {} // noninstantiable class 2957 //- This code is based on LJV: 2958 // LJV.java --- Generate a graph of an object, using Graphviz 2959 // The Lightweight Java Visualizer (LJV) 2960 // https://www.cs.auckland.ac.nz/~j-hamer/ 2961 2962 //- Author: John Hamer <[email protected]> 2963 //- Created: Sat May 10 15:27:48 2003 2964 //- Time-stamp: <2004-08-23 12:47:06 jham005> 2965 2966 //- Copyright (C) 2004 John Hamer, University of Auckland 2967 //- 2968 //- This program is free software; you can redistribute it and/or 2969 //- modify it under the terms of the GNU General Public License 2970 //- as published by the Free Software Foundation; either version 2 2971 //- of the License, or (at your option) any later version. 2972 //- 2973 //- This program is distributed in the hope that it will be useful, 2974 //- but WITHOUT ANY WARRANTY; without even the implied warranty of 2975 //- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 2976 //- GNU General Public License for more details. 2977 //- 2978 //- You should have received a copy of the GNU General Public License along 2979 //- with this program; if not, write to the Free Software Foundation, Inc., 2980 //- 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. 2981 2982 //- $Id: LJV.java,v 1.1 2004/07/14 02:03:45 jham005 Exp $ 2983 // 2984 2985 /** 2986 * Graphics files are saved in directory dirName/mainClassName. 2987 * dirName directory is created if it does not already exist. 2988 * If dirName/mainClassName exists, then numbers are appended to the directory name: 2989 * "dirName/mainClassName 1", "dirName/mainClassName 2", etc. 2990 */ 2991 public static void setOutputDirectory (String dirName, String mainClassName) { 2992 if (dirName == null || mainClassName == null) { 2993 throw new Error ("\n!!!! no nulls please"); 2994 } 2995 Graphviz.dirName = dirName; 2996 Graphviz.mainClassName = mainClassName; 2997 } 2998 private static String dirName; 2999 private static String mainClassName; 3000 3001 /** 3002 * The name of the output file is derived from <code>baseFilename</code> by 3003 * appending successive integers. 3004 */ 3005 public static String peekFilename () { 3006 return String.format ("%03d", nextGraphNumber); 3007 } 3008 private static String nextFilename () { 3009 if (baseFilename == null) setBaseFilename (); 3010 ++nextGraphNumber; 3011 return baseFilename + peekFilename (); 3012 } 3013 private static int nextGraphNumber = -1; 3014 private static String baseFilename = null; 3015 private static void setBaseFilename () { 3016 if (dirName == null || mainClassName == null) { 3017 throw new Error ("\n!!!! no call to setOutputDirectory"); 3018 } 3019 // create dir 3020 File dir = new File (dirName); 3021 if (!dir.isAbsolute ()) { 3022 dirName = System.getProperty ("user.home") + File.separator + dirName; 3023 dir = new File (dirName); 3024 } 3025 if (dir.exists ()) { 3026 if (!dir.isDirectory ()) 3027 throw new Error ("\n!!!! \"" + dir + "\" is not a directory"); 3028 if (!dir.canWrite ()) 3029 throw new Error ("\n!!!! Unable to write directory: \"" + dir + "\""); 3030 } else { 3031 dir.mkdirs (); 3032 } 3033 3034 // create newDir 3035 String prefix = dirName + File.separator; 3036 String[] mainClassPath = mainClassName.split ("\\."); 3037 mainClassName = mainClassPath[mainClassPath.length-1]; 3038 File newDir = new File (prefix + mainClassName); 3039 int suffix = 0; 3040 while (newDir.exists()) { 3041 suffix++; 3042 newDir = new File(prefix + mainClassName + " " + suffix); 3043 } 3044 newDir.mkdir (); 3045 3046 if (!newDir.isDirectory () || !newDir.canWrite ()) 3047 throw new Error ("Failed setOutputDirectory \"" + newDir + "\""); 3048 baseFilename = newDir + File.separator; 3049 nextGraphNumber = -1; 3050 } 3051// /** @deprecated */ 3052// private static void setOutputFilenamePrefix (String s) { 3053// File f = new File (s); 3054// String fCanonical; 3055// try { 3056// fCanonical = f.getCanonicalPath (); 3057// } catch (IOException e) { 3058// throw new Error ("Failed setBaseFilename \"" + f + "\""); 3059// } 3060// 3061// String newBaseFilename; 3062// if (f.isDirectory ()) { 3063// if (f.canWrite ()) { 3064// newBaseFilename = fCanonical + "/trace-"; 3065// } else { 3066// throw new Error ("Failed setBaseFilename \"" + f + "\""); 3067// } 3068// } else { 3069// File parent = (f == null) ? null : f.getParentFile (); 3070// if (parent == null || parent.canWrite ()) { 3071// newBaseFilename = fCanonical; 3072// } else { 3073// System.err.println ("Cannot open directory \"" + f.getParent () + "\" for writing; using the current directory for graphziv output."); 3074// throw new Error ("Failed setBaseFilename \"" + f + "\""); 3075// } 3076// } 3077// if (!newBaseFilename.equals (baseFilename)) { 3078// baseFilename = newBaseFilename; 3079// nextGraphNumber = -1; 3080// } 3081// } 3082 3083 public static final HashMap<String, String> objectAttributeMap = new HashMap<> (); 3084 public static final HashMap<String, String> staticClassAttributeMap = new HashMap<> (); 3085 public static final HashMap<String, String> frameAttributeMap = new HashMap<> (); 3086 public static final HashMap<String, String> fieldAttributeMap = new HashMap<> (); 3087 3088 // ----------------------------------- utilities ----------------------------------------------- 3089 3090 private static boolean canTreatAsPrimitive (Value v) { 3091 if (v == null || v instanceof PrimitiveValue) return true; 3092 if (Trace.SHOW_STRINGS_AS_PRIMITIVE && v instanceof StringReference) return true; 3093 if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && Format.isWrapper (v.type ())) return true; 3094 return false; 3095 } 3096 private static boolean looksLikePrimitiveArray (ArrayReference obj) { 3097 try { 3098 if (((ArrayType) obj.type ()).componentType () instanceof PrimitiveType) return true; 3099 } catch (ClassNotLoadedException e) { 3100 return false; 3101 } 3102 3103 for (int i = 0, len = obj.length (); i < len; i++) 3104 if (!canTreatAsPrimitive (obj.getValue (i))) return false; 3105 return true; 3106 } 3107 private static boolean canIgnoreObjectField (Field field) { 3108 if (!Format.isObjectField (field)) return true; 3109 for (String ignoredField : Trace.GRAPHVIZ_IGNORED_FIELDS) 3110 if (ignoredField.equals (field.name ())) return true; 3111 return false; 3112 } 3113 private static boolean canIgnoreStaticField (Field field) { 3114 if (!Format.isStaticField (field)) return true; 3115 for (String ignoredField : Trace.GRAPHVIZ_IGNORED_FIELDS) 3116 if (ignoredField.equals (field.name ())) return true; 3117 return false; 3118 } 3119 3120 //private static final String canAppearUnquotedInLabelChars = " /$&*@#!-+()^%;_[],;.="; 3121 private static boolean canAppearUnquotedInLabel (char c) { 3122 return true; 3123 //return canAppearUnquotedInLabelChars.indexOf (c) != -1 || Character.isLetter (c) || Character.isDigit (c); 3124 } 3125 private static final String quotable = "\\\"<>{}|"; 3126 protected static String quote (String s) { 3127 s = unescapeJavaString (s); 3128 StringBuffer sb = new StringBuffer (); 3129 for (int i = 0, n = s.length (); i < n; i++) { 3130 char c = s.charAt (i); 3131 if (quotable.indexOf (c) != -1) sb.append ('\\').append (c); 3132 else if (canAppearUnquotedInLabel (c)) sb.append (c); 3133 else sb.append ("\\\\u").append (Integer.toHexString (c)); 3134 } 3135 return sb.toString (); 3136 } 3137 /** 3138 * Unescapes a string that contains standard Java escape sequences. 3139 * <ul> 3140 * <li><strong>\\b \\f \\n \\r \\t \\" \\'</strong> : 3141 * BS, FF, NL, CR, TAB, double and single quote.</li> 3142 * <li><strong>\\N \\NN \\NNN</strong> : Octal character 3143 * specification (0 - 377, 0x00 - 0xFF).</li> 3144 * <li><strong>\\uNNNN</strong> : Hexadecimal based Unicode character.</li> 3145 * </ul> 3146 * 3147 * @param st 3148 * A string optionally containing standard java escape sequences. 3149 * @return The translated string. 3150 */ 3151 // from http://udojava.com/2013/09/28/unescape-a-string-that-contains-standard-java-escape-sequences/ 3152 private static String unescapeJavaString(String st) { 3153 StringBuilder sb = new StringBuilder(st.length()); 3154 for (int i = 0; i < st.length(); i++) { 3155 char ch = st.charAt(i); 3156 if (ch == '\\') { 3157 char nextChar = (i == st.length() - 1) ? '\\' : st.charAt(i + 1); 3158 // Octal escape? 3159 if (nextChar >= '0' && nextChar <= '7') { 3160 String code = "" + nextChar; 3161 i++; 3162 if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') { 3163 code += st.charAt(i + 1); 3164 i++; 3165 if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') { 3166 code += st.charAt(i + 1); 3167 i++; 3168 } 3169 } 3170 sb.append((char) Integer.parseInt(code, 8)); 3171 continue; 3172 } 3173 switch (nextChar) { 3174 case '\\': ch = '\\'; break; 3175 case 'b': ch = '\b'; break; 3176 case 'f': ch = '\f'; break; 3177 case 'n': ch = '\n'; break; 3178 case 'r': ch = '\r'; break; 3179 case 't': ch = '\t'; break; 3180 case '\"': ch = '\"'; break; 3181 case '\'': ch = '\''; break; 3182 // Hex Unicode: u???? 3183 case 'u': 3184 if (i >= st.length() - 5) { ch = 'u'; break; } 3185 int code = Integer.parseInt(st.substring (i+2,i+6), 16); 3186 sb.append(Character.toChars(code)); 3187 i += 5; 3188 continue; 3189 } 3190 i++; 3191 } 3192 sb.append(ch); 3193 } 3194 return sb.toString(); 3195 } 3196 3197 3198 3199 // ----------------------------------- values ----------------------------------------------- 3200 3201 protected static final String PREFIX_UNUSED_LABEL = "_____"; 3202 private static final String PREFIX_LABEL = "L"; 3203 private static final String PREFIX_ARRAY = "A"; 3204 private static final String PREFIX_OBJECT = "N"; 3205 private static final String PREFIX_STATIC = "S"; 3206 private static final String PREFIX_FRAME = "F"; 3207 private static final String PREFIX_RETURN = "returnValue"; 3208 private static final String PREFIX_EXCEPTION = "exception"; 3209 3210 private static void processPrimitiveArray (ArrayReference obj, PrintWriter out) { 3211 out.print (objectGvName (obj) + "[label=\""); 3212 for (int i = 0, len = obj.length (); i < len; i++) { 3213 if (i != 0) out.print ("|"); 3214 Value v = obj.getValue (i); 3215 if (v != null) processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, "", v, out); 3216 } 3217 out.println ("\"" + Trace.GRAPHVIZ_ARRAY_BOX_ATTRIBUTES + "];"); 3218 } 3219 private static void processObjectArray (ArrayReference obj, PrintWriter out, Set<ObjectReference> visited) { 3220 out.print (objectGvName (obj) + "[label=\""); 3221 int len = obj.length (); 3222 for (int i = 0; i < len; i++) { 3223 if (i != 0) out.print ("|"); 3224 out.print ("<" + PREFIX_ARRAY + i + ">"); 3225 } 3226 out.println ("\"" + Trace.GRAPHVIZ_ARRAY_BOX_ATTRIBUTES + "];"); 3227 for (int i = 0; i < len; i++) { 3228 ObjectReference ref = (ObjectReference) obj.getValue (i); 3229 if (ref == null) continue; 3230 out.println (objectGvName (obj) + ":" + PREFIX_ARRAY + i + ":c -> " + objectGvName (ref) + "[label=\"" + i + "\"" + Trace.GRAPHVIZ_ARRAY_ARROW_ATTRIBUTES + "];"); 3231 processObject (ref, out, visited); 3232 } 3233 } 3234 private static void processValueStandalone (String gvSource, String arrowAttributes, String fieldName, Value val, PrintWriter out, Set<ObjectReference> visited) { 3235 if (canTreatAsPrimitive (val)) throw new Error (Trace.BAD_ERROR_MESSAGE); 3236 ObjectReference objRef = (ObjectReference) val; 3237 String GvName = objectGvName (objRef); 3238 out.println (gvSource + " -> " + GvName + "[label=\"" + fieldName + "\"" + arrowAttributes + "];"); 3239 processObject (objRef, out, visited); 3240 } 3241 private static boolean processValueInline (boolean showNull, String prefix, Value val, PrintWriter out) { 3242 if (!canTreatAsPrimitive (val)) return false; 3243 if (val == null && !showNull) 3244 return false; 3245 out.print (prefix); 3246 if (val == null) { 3247 out.print (quote ("null")); 3248 } else if (val instanceof PrimitiveValue) { 3249 out.print (quote (val.toString ())); 3250 } else if (Trace.SHOW_STRINGS_AS_PRIMITIVE && val instanceof StringReference) { 3251 out.print (quote (val.toString ())); 3252 } else if (Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && Format.isWrapper (val.type ())) { 3253 out.print (quote (Format.wrapperToString ((ObjectReference) val))); 3254 } 3255 return true; 3256 } 3257 // val must be primitive, wrapper or string 3258 private static void processWrapperAsSimple (String gvName, Value val, PrintWriter out, Set<ObjectReference> visited) { 3259 String cabs = null; 3260 out.print (gvName + "[label=\""); 3261 if (val instanceof PrimitiveValue) { 3262 out.print (quote (val.toString ())); 3263 } else if (val instanceof StringReference) { 3264 out.print (quote (val.toString ())); 3265 } else { 3266 out.print (quote (Format.wrapperToString ((ObjectReference) val))); 3267 } 3268 out.println ("\"" + Trace.GRAPHVIZ_WRAPPER_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];"); 3269 } 3270 3271 // ----------------------------------- objects ----------------------------------------------- 3272 3273 private static String objectName (ObjectReference obj) { 3274 if (obj == null) return ""; 3275 String objString = (Trace.GRAPHVIZ_SHOW_OBJECT_IDS) ? "@" + obj.uniqueID () + " : " : ""; 3276 return objString + Format.shortenFullyQualifiedName (obj.type ().name ()); 3277 } 3278 private static String objectGvName (ObjectReference obj) { 3279 return PREFIX_OBJECT + obj.uniqueID (); 3280 } 3281 private static boolean objectHasPrimitives (List<Field> fs, ObjectReference obj) { 3282 for (Field f : fs) { 3283 if (canIgnoreObjectField (f)) continue; 3284 if (canTreatAsPrimitive (obj.getValue (f))) return true; 3285 } 3286 return false; 3287 } 3288 private static void labelObjectWithNoPrimitiveFields (ObjectReference obj, PrintWriter out) { 3289 String cabs = objectAttributeMap.get (obj.type ().name ()); 3290 out.println (objectGvName (obj) + "[label=\"" + objectName (obj) + "\"" + Trace.GRAPHVIZ_OBJECT_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];"); 3291 } 3292 private static void labelObjectWithSomePrimitiveFields (ObjectReference obj, List<Field> fs, PrintWriter out) { 3293 out.print (objectGvName (obj) + "[label=\"" + objectName (obj) + "|{"); 3294 String sep = ""; 3295 for (Field field : fs) { 3296 if (!canIgnoreObjectField (field)) { 3297 String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS ? field.name () + " = " : ""; 3298 if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, sep + name, obj.getValue (field), out)) sep = "|"; 3299 } 3300 } 3301 String cabs = objectAttributeMap.get (obj.type ().name ()); 3302 out.println ("}\"" + Trace.GRAPHVIZ_OBJECT_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];"); 3303 } 3304 private static void processObjectWithLabel (String label, ObjectReference obj, PrintWriter out, Set<ObjectReference> visited) { 3305 processObject (obj, out, visited); 3306 if (!label.startsWith (PREFIX_UNUSED_LABEL)) { 3307 String gvObjName = objectGvName (obj); 3308 String gvLabelName = PREFIX_LABEL + label; 3309 out.println (gvLabelName + "[label=\"" + label + "\"" + Trace.GRAPHVIZ_LABEL_BOX_ATTRIBUTES + "];"); 3310 out.println (gvLabelName + " -> " + gvObjName + "[label=\"\"" + Trace.GRAPHVIZ_LABEL_ARROW_ATTRIBUTES + "];"); 3311 } 3312 } 3313 private static Value valueByFieldname (ObjectReference obj, String fieldName) { 3314 ReferenceType type = (ReferenceType) obj.type (); 3315 Field field = type.fieldByName (fieldName); 3316 return obj.getValue (field); 3317 } 3318 private static void processObject (ObjectReference obj, PrintWriter out, Set<ObjectReference> visited) { 3319 if (visited.add (obj)) { 3320 Type type = obj.type (); 3321 String typeName = type.name (); 3322 3323 if (!Trace.SHOW_BOXED_PRIMITIVES_AS_PRIMITIVE && Trace.GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY && Format.isWrapper (type)) { 3324 processWrapperAsSimple (objectGvName (obj), obj, out, visited); 3325 } else if (!Trace.SHOW_STRINGS_AS_PRIMITIVE && Trace.GRAPHVIZ_SHOW_BOXED_PRIMITIVES_SIMPLY && obj instanceof StringReference) { 3326 processWrapperAsSimple (objectGvName (obj), obj, out, visited); 3327 } else if (obj instanceof ArrayReference) { 3328 ArrayReference arr = (ArrayReference) obj; 3329 if (looksLikePrimitiveArray (arr)) processPrimitiveArray (arr, out); 3330 else processObjectArray (arr, out, visited); 3331 } else { 3332 List<Field> fs = ((ReferenceType) type).fields (); 3333 if (objectHasPrimitives (fs, obj)) labelObjectWithSomePrimitiveFields (obj, fs, out); 3334 else labelObjectWithNoPrimitiveFields (obj, out); 3335 if (!Format.matchesExcludePrefixShow (typeName)) { 3336 //System.err.println (typeName); 3337 String source = objectGvName (obj); 3338 for (Field f : fs) { 3339 Value value = obj.getValue (f); 3340 if ((!canIgnoreObjectField (f)) && (!canTreatAsPrimitive (value))) { 3341 processValueStandalone (source, Trace.GRAPHVIZ_OBJECT_ARROW_ATTRIBUTES, f.name (), value, out, visited); 3342 } 3343 } 3344 } 3345 } 3346 } 3347 } 3348 3349 // ----------------------------------- static classes ----------------------------------------------- 3350 3351 private static String staticClassName (ReferenceType type) { 3352 return Format.shortenFullyQualifiedName (type.name ()); 3353 } 3354 private static String staticClassGvName (ReferenceType type) { 3355 return PREFIX_STATIC + type.classObject ().uniqueID (); 3356 } 3357 private static boolean staticClassHasFields (List<Field> fs) { 3358 for (Field f : fs) { 3359 if (!canIgnoreStaticField (f)) return true; 3360 } 3361 return false; 3362 } 3363 private static boolean staticClassHasPrimitives (List<Field> fs, ReferenceType staticClass) { 3364 for (Field f : fs) { 3365 if (canIgnoreStaticField (f)) continue; 3366 if (canTreatAsPrimitive (staticClass.getValue (f))) return true; 3367 } 3368 return false; 3369 } 3370 private static void labelStaticClassWithNoPrimitiveFields (ReferenceType type, PrintWriter out) { 3371 String cabs = staticClassAttributeMap.get (type.name ()); 3372 out.println (staticClassGvName (type) + "[label=\"" + staticClassName (type) + "\"" + Trace.GRAPHVIZ_STATIC_CLASS_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];"); 3373 } 3374 private static void labelStaticClassWithSomePrimitiveFields (ReferenceType type, List<Field> fs, PrintWriter out) { 3375 out.print (staticClassGvName (type) + "[label=\"" + staticClassName (type) + "|{"); 3376 String sep = ""; 3377 for (Field field : fs) { 3378 if (!canIgnoreStaticField (field)) { 3379 String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS ? field.name () + " = " : ""; 3380 if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_FIELDS, sep + name, type.getValue (field), out)) sep = "|"; 3381 } 3382 } 3383 String cabs = staticClassAttributeMap.get (type.name ()); 3384 out.println ("}\"" + Trace.GRAPHVIZ_STATIC_CLASS_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];"); 3385 } 3386 private static void processStaticClass (ReferenceType type, PrintWriter out, Set<ObjectReference> visited) { 3387 String typeName = type.name (); 3388 List<Field> fs = type.fields (); 3389 if (!staticClassHasFields (fs)) { 3390 return; 3391 } 3392 if (staticClassHasPrimitives (fs, type)) { 3393 labelStaticClassWithSomePrimitiveFields (type, fs, out); 3394 } else { 3395 labelStaticClassWithNoPrimitiveFields (type, out); 3396 } 3397 if (!Format.matchesExcludePrefixShow (type.name ())) { 3398 String source = staticClassGvName (type); 3399 for (Field f : fs) { 3400 if (f.isStatic ()) { 3401 Value value = type.getValue (f); 3402 if ((!canIgnoreStaticField (f)) && (!canTreatAsPrimitive (value))) { 3403 String name = f.name (); 3404 processValueStandalone (source, Trace.GRAPHVIZ_STATIC_CLASS_ARROW_ATTRIBUTES, name, value, out, visited); 3405 } 3406 } 3407 } 3408 } 3409 } 3410 3411 // ----------------------------------- frames ----------------------------------------------- 3412 private static String frameName (int frameNumber, StackFrame frame, Method method, int lineNumber) { 3413 String objString = (Trace.GRAPHVIZ_SHOW_FRAME_NUMBERS) ? "@" + frameNumber + " : " : ""; 3414 return objString + Format.methodToString (method, true, false, ".") + " # " + lineNumber; 3415 } 3416 private static String frameGvName (int frameNumber) { 3417 return PREFIX_FRAME + frameNumber; 3418 } 3419 private static boolean frameHasPrimitives (Map<LocalVariable, Value> ls) { 3420 for (LocalVariable lv : ls.keySet ()) { 3421 Value v = ls.get (lv); 3422 if (canTreatAsPrimitive (v)) return true; 3423 } 3424 return false; 3425 } 3426 private static void labelFrameWithNoPrimitiveLocals (int frameNumber, StackFrame frame, PrintWriter out) { 3427 Location location = frame.location (); 3428 ReferenceType type = location.declaringType (); 3429 Method method = location.method (); 3430 String attributes = frameAttributeMap.get (type.name ()); 3431 out.println (frameGvName (frameNumber) + "[label=\"" + frameName (frameNumber, frame, method, location.lineNumber ()) + "\"" + Trace.GRAPHVIZ_FRAME_BOX_ATTRIBUTES 3432 + (attributes == null ? "" : "," + attributes) + "];"); 3433 } 3434 private static void labelFrameWithSomePrimitiveLocals (int frameNumber, StackFrame frame, Map<LocalVariable, Value> ls, PrintWriter out) { 3435 Location location = frame.location (); 3436 ReferenceType type = location.declaringType (); 3437 Method method = location.method (); 3438 out.print (frameGvName (frameNumber) + "[label=\"" + frameName (frameNumber, frame, method, location.lineNumber ()) + "|{"); 3439 String sep = ""; 3440 for (LocalVariable lv : ls.keySet ()) { 3441 String name = Trace.GRAPHVIZ_SHOW_FIELD_NAMES_IN_LABELS ? lv.name () + " = " : ""; 3442 if (processValueInline (Trace.GRAPHVIZ_SHOW_NULL_VARIABLES, sep + name, ls.get (lv), out)) sep = "|"; 3443 } 3444 String cabs = frameAttributeMap.get (type.name ()); 3445 out.println ("}\"" + Trace.GRAPHVIZ_FRAME_BOX_ATTRIBUTES + (cabs == null ? "" : "," + cabs) + "];"); 3446 } 3447 private static boolean processFrame (int frameNumber, StackFrame frame, PrintWriter out, Set<ObjectReference> visited) { 3448 Location location = frame.location (); 3449 ReferenceType type = location.declaringType (); 3450 Method method = location.method (); 3451 if (Format.matchesExcludePrefixShow (type.name ())) return false; 3452 3453 Map<LocalVariable, Value> ls; 3454 try { 3455 ls = frame.getValues (frame.visibleVariables ()); 3456 } catch (AbsentInformationException e) { 3457 return false; 3458 } 3459 if (frameHasPrimitives (ls)) { 3460 labelFrameWithSomePrimitiveLocals (frameNumber, frame, ls, out); 3461 } else { 3462 labelFrameWithNoPrimitiveLocals (frameNumber, frame, out); 3463 } 3464 ObjectReference thisObject = frame.thisObject (); 3465 if (thisObject != null) processValueStandalone (frameGvName (frameNumber), Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, "this", thisObject, out, visited); 3466 for (LocalVariable lv : ls.keySet ()) { 3467 Value value = ls.get (lv); 3468 if (!canTreatAsPrimitive (value)) { 3469 processValueStandalone (frameGvName (frameNumber), Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, lv.name (), value, out, visited); 3470 } 3471 } 3472 return true; 3473 } 3474 3475 // ----------------------------------- top level ----------------------------------------------- 3476 3477 public static void drawFramesCheck (String loc, Value returnVal, Value exnVal, List<StackFrame> frames, Set<ReferenceType> staticClasses) { 3478 if (Trace.drawStepsOfInternal (frames, returnVal)) 3479 drawFrames (0, loc, returnVal, exnVal, frames, staticClasses); 3480 } 3481 public static void drawFrames (int start, String loc, Value returnVal, Value exnVal, List<StackFrame> frames, Set<ReferenceType> staticClasses) { 3482 drawStuff (loc, (out) -> { 3483 Set<ObjectReference> visited = new HashSet<> (); 3484 if (staticClasses != null) { 3485 for (ReferenceType staticClass : staticClasses) { 3486 processStaticClass (staticClass, out, visited); 3487 } 3488 } 3489 int len = 0; 3490 if (frames != null) { 3491 len = frames.size (); 3492 for (int i = len - 1, prev = i; i >= start; i--) { 3493 StackFrame currentFrame = frames.get (i); 3494 Method meth = currentFrame.location ().method (); 3495 if (!Trace.SHOW_SYNTHETIC_METHODS && meth.isSynthetic ()) continue; 3496 if (processFrame (len - i, currentFrame, out, visited)) { 3497 if (prev != i) { 3498 out.println (frameGvName (len - i) + " -> " + frameGvName (len - prev) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];"); 3499 prev = i; 3500 } 3501 } 3502 } 3503 // show the return value -- without this, it mysteriously disappears when drawing all steps 3504 if (returnVal != null && !(returnVal instanceof VoidValue)) { 3505 String objString = (Trace.GRAPHVIZ_SHOW_FRAME_NUMBERS) ? "@" + (len + 1) + " : " : ""; 3506 if (canTreatAsPrimitive (returnVal)) { 3507 out.print (PREFIX_RETURN + " [label=\"" + objString + "returnValue = "); 3508 processValueInline (true, "", returnVal, out); 3509 out.println ("\"" + Trace.GRAPHVIZ_FRAME_RETURN_ATTRIBUTES + "];"); 3510 out.println (PREFIX_RETURN + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];"); 3511 } else { 3512 out.println (PREFIX_RETURN + " [label=\"" + objString + "returnValue\"" + Trace.GRAPHVIZ_FRAME_RETURN_ATTRIBUTES + "];"); 3513 processValueStandalone (PREFIX_RETURN, Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, "", returnVal, out, visited); 3514 out.println (PREFIX_RETURN + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];"); 3515 } 3516 } 3517 } 3518 // show the exception value 3519 if (exnVal != null && !(exnVal instanceof VoidValue)) { 3520 if (canTreatAsPrimitive (exnVal)) { 3521 out.print (PREFIX_EXCEPTION + " [label=\"exception = "); 3522 processValueInline (true, "", exnVal, out); 3523 out.println ("\"" + Trace.GRAPHVIZ_FRAME_EXCEPTION_ATTRIBUTES + "];"); 3524 if (len != 0) out.println (PREFIX_EXCEPTION + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];"); 3525 } else { 3526 out.println (PREFIX_EXCEPTION + " [label=\"exception\"" + Trace.GRAPHVIZ_FRAME_EXCEPTION_ATTRIBUTES + "];"); 3527 processValueStandalone (PREFIX_EXCEPTION, Trace.GRAPHVIZ_FRAME_OBJECT_ARROW_ATTRIBUTES, "", exnVal, out, visited); 3528 if (len != 0) out.println (PREFIX_EXCEPTION + " -> " + frameGvName (len) + "[label=\"\"" + Trace.GRAPHVIZ_FRAME_FRAME_ARROW_ATTRIBUTES + "];"); 3529 } 3530 } 3531 }); 3532 } 3533 public static void drawObjects (String loc, Map<String, ObjectReference> objects) { 3534 drawStuff (loc, (out) -> { 3535 Set<ObjectReference> visited = new HashSet<> (); 3536 for (String key : objects.keySet ()) { 3537 processObjectWithLabel (key, objects.get (key), out, visited); 3538 } 3539 }); 3540 } 3541 protected static void drawStuff (String loc, Consumer<PrintWriter> consumer) { 3542 String filenamePrefix = nextFilename (); 3543 String theLoc = (loc != null && Trace.GRAPHVIZ_PUT_LINE_NUMBER_IN_FILENAME) ? "-" + loc : ""; 3544 File gvFile = new File (filenamePrefix + theLoc + ".gv"); 3545 PrintWriter out; 3546 try { 3547 out = new PrintWriter (new FileWriter (gvFile)); 3548 } catch (IOException e) { 3549 throw new Error ("\n!!!! Cannot open " + gvFile + "for writing"); 3550 } 3551 out.println ("digraph Java {"); 3552 consumer.accept (out); 3553 out.println ("}"); 3554 out.close (); 3555 //System.err.println (gvFile); 3556 if (Trace.GRAPHVIZ_RUN_GRAPHVIZ) { 3557 String executable = null; 3558 for (String s : Trace.GRAPHVIZ_POSSIBLE_DOT_LOCATIONS) { 3559 if (new File (s).canExecute ()) executable = s; 3560 } 3561 if (executable != null) { 3562 ProcessBuilder pb = new ProcessBuilder (executable, "-T", Trace.GRAPHVIZ_OUTPUT_FORMAT); 3563 File outFile = new File (filenamePrefix + theLoc + "." + Trace.GRAPHVIZ_OUTPUT_FORMAT); 3564 pb.redirectInput (gvFile); 3565 pb.redirectOutput (outFile); 3566 int result = -1; 3567 try { 3568 result = pb.start ().waitFor (); 3569 } catch (IOException e) { 3570 throw new Error ("\n!!!! Cannot execute " + executable + "\n!!!! Make sure you have installed http://www.graphviz.org/" 3571 + "\n!!!! Check the value of GRAPHVIZ_POSSIBLE_DOT_LOCATIONS in " + Trace.class.getCanonicalName ()); 3572 } catch (InterruptedException e) { 3573 throw new Error ("\n!!!! Execution of " + executable + "interrupted"); 3574 } 3575 if (result == 0) { 3576 if (Trace.GRAPHVIZ_REMOVE_GV_FILES) { 3577 gvFile.delete (); 3578 } 3579 } else { 3580 outFile.delete (); 3581 } 3582 } 3583 } 3584 } 3585 3586 // public static void drawFrames (int start, String loc, Value returnVal, Value exnVal, List<StackFrame> frames, Set<ReferenceType> staticClasses, Map<String, ObjectReference> objects) { 3587 // String filenamePrefix = nextFilename (); 3588 // String theLoc = (loc != null && Trace.GRAPHVIZ_PUT_LINE_NUMBER_IN_FILENAME) ? "-" + loc : ""; 3589 // File gvFile = new File (filenamePrefix + theLoc + ".gv"); 3590 // PrintWriter out; 3591 // try { 3592 // out = new PrintWriter (new FileWriter (gvFile)); 3593 // } catch (IOException e) { 3594 // throw new Error ("\n!!!! Cannot open " + gvFile + "for writing"); 3595 // } 3596 // processFrames (start, returnVal, exnVal, frames, staticClasses, objects, out); 3597 // out.close (); 3598 // //System.err.println (gvFile); 3599 // if (Trace.GRAPHVIZ_RUN_DOT) { 3600 // String executable = null; 3601 // for (String s : Trace.GRAPHVIZ_POSSIBLE_DOT_LOCATIONS) { 3602 // if (new File (s).canExecute ()) 3603 // executable = s; 3604 // } 3605 // if (executable != null) { 3606 // ProcessBuilder pb = new ProcessBuilder (executable, "-T", Trace.GRAPHVIZ_DOT_OUTPUT_FORMAT); 3607 // File outFile = new File (filenamePrefix + theLoc + "." + Trace.GRAPHVIZ_DOT_OUTPUT_FORMAT); 3608 // pb.redirectInput (gvFile); 3609 // pb.redirectOutput(outFile); 3610 // int result = -1; 3611 // try { 3612 // result = pb.start ().waitFor (); 3613 // } catch (IOException e) { 3614 // throw new Error ("\n!!!! Cannot execute " + executable + 3615 // "\n!!!! Make sure you have installed http://www.graphviz.org/" + 3616 // "\n!!!! Check the value of GRAPHVIZ_DOT_COMMAND in " + Trace.class.getCanonicalName ()); 3617 // } catch (InterruptedException e) { 3618 // throw new Error ("\n!!!! Execution of " + executable + "interrupted"); 3619 // } 3620 // if (result == 0) { 3621 // if (Trace.GRAPHVIZ_REMOVE_GV_FILES) { 3622 // gvFile.delete (); 3623 // } 3624 // } else { 3625 // outFile.delete (); 3626 // } 3627 // } 3628 // } 3629 // } 3630}