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