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