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