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