1 /* 2 * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import com.sun.jdi.*; 25 import com.sun.jdi.request.*; 26 import com.sun.jdi.event.*; 27 import java.util.*; 28 import java.io.*; 29 30 31 /** 32 * Framework used by all JDI regression tests 33 */ 34 abstract public class TestScaffold extends TargetAdapter { 35 private boolean shouldTrace = false; 36 private VMConnection connection; 37 private VirtualMachine vm; 38 private EventRequestManager requestManager; 39 private List listeners = Collections.synchronizedList(new LinkedList()); 40 private boolean redefineAtStart = false; 41 private boolean redefineAtEvents = false; 42 private boolean redefineAsynchronously = false; 43 private ReferenceType mainStartClass = null; 44 45 // Set true by tests that need the debug agent to be run with includevirtualthreads=y. 46 private boolean includeVThreads = false; 47 48 ThreadReference mainThread; 49 /** 50 * We create a VMDeathRequest, SUSPEND_ALL, to sync the BE and FE. 51 */ 52 private VMDeathRequest ourVMDeathRequest = null; 53 54 /** 55 * We create an ExceptionRequest, SUSPEND_NONE so that we can 56 * catch it and output a msg if an exception occurs in the 57 * debuggee. 58 */ 59 private ExceptionRequest ourExceptionRequest = null; 60 61 /** 62 * If we do catch an uncaught exception, we set this true 63 * so the testcase can find out if it wants to. 64 */ 65 private boolean exceptionCaught = false; 66 ThreadReference vmStartThread = null; 67 boolean vmDied = false; 68 boolean vmDisconnected = false; 69 final String[] args; 70 protected boolean testFailed = false; 71 protected long startTime; 72 73 static private class ArgInfo { 74 String targetVMArgs = ""; 75 String targetAppCommandLine = ""; 76 String connectorSpec = "com.sun.jdi.CommandLineLaunch:"; 77 int traceFlags = 0; 78 } 79 80 /** 81 * An easy way to sleep for awhile 82 */ 83 public void mySleep(int millis) { 84 try { 85 Thread.sleep(millis); 86 } catch (InterruptedException ee) { 87 } 88 } 89 90 void enableIncludeVirtualthreads() { 91 includeVThreads = true; 92 } 93 94 boolean getExceptionCaught() { 95 return exceptionCaught; 96 } 97 98 void setExceptionCaught(boolean value) { 99 exceptionCaught = value; 100 } 101 102 /** 103 * Return true if eventSet contains the VMDeathEvent for the request in 104 * the ourVMDeathRequest ivar. 105 */ 106 private boolean containsOurVMDeathRequest(EventSet eventSet) { 107 if (ourVMDeathRequest != null) { 108 Iterator myIter = eventSet.iterator(); 109 while (myIter.hasNext()) { 110 Event myEvent = (Event)myIter.next(); 111 if (!(myEvent instanceof VMDeathEvent)) { 112 // We assume that an EventSet contains only VMDeathEvents 113 // or no VMDeathEvents. 114 break; 115 } 116 if (ourVMDeathRequest.equals(myEvent.request())) { 117 return true; 118 } 119 } 120 } 121 return false; 122 } 123 124 /************************************************************************ 125 * The following methods override those in our base class, TargetAdapter. 126 *************************************************************************/ 127 128 /** 129 * Events handled directly by scaffold always resume (well, almost always) 130 */ 131 public void eventSetComplete(EventSet set) { 132 // The listener in connect(..) resumes after receiving our 133 // special VMDeathEvent. We can't also do the resume 134 // here or we will probably get a VMDisconnectedException 135 if (!containsOurVMDeathRequest(set)) { 136 traceln("TS: set.resume() called"); 137 set.resume(); 138 } 139 } 140 141 /** 142 * This method sets up default requests. 143 * Testcases can override this to change default behavior. 144 */ 145 protected void createDefaultEventRequests() { 146 createDefaultVMDeathRequest(); 147 createDefaultExceptionRequest(); 148 } 149 150 /** 151 * We want the BE to stop when it issues a VMDeathEvent in order to 152 * give the FE time to complete handling events that occured before 153 * the VMDeath. When we get the VMDeathEvent for this request in 154 * the listener in connect(), we will do a resume. 155 * If a testcase wants to do something special with VMDeathEvent's, 156 * then it should override this method with an empty method or 157 * whatever in order to suppress the automatic resume. The testcase 158 * will then be responsible for the handling of VMDeathEvents. It 159 * has to be sure that it does a resume if it gets a VMDeathEvent 160 * with SUSPEND_ALL, and it has to be sure that it doesn't do a 161 * resume after getting a VMDeath with SUSPEND_NONE (the automatically 162 * generated VMDeathEvent.) 163 */ 164 protected void createDefaultVMDeathRequest() { 165 ourVMDeathRequest = requestManager.createVMDeathRequest(); 166 ourVMDeathRequest.setSuspendPolicy(EventRequest.SUSPEND_ALL); 167 ourVMDeathRequest.enable(); 168 } 169 170 /** 171 * This will allow us to print a warning if a debuggee gets an 172 * unexpected exception. The unexpected exception will be handled in 173 * the exceptionThrown method in the listener created in the connect() 174 * method. 175 * If a testcase does not want an uncaught exception to cause a 176 * msg, it must override this method. 177 */ 178 protected void createDefaultExceptionRequest() { 179 ourExceptionRequest = requestManager.createExceptionRequest(null, 180 false, true); 181 182 // We can't afford to make this be other than SUSPEND_NONE. Otherwise, 183 // it would have to be resumed. If our connect() listener resumes it, 184 // what about the case where the EventSet contains other events with 185 // SUSPEND_ALL and there are other listeners who expect the BE to still 186 // be suspended when their handlers get called? 187 ourExceptionRequest.setSuspendPolicy(EventRequest.SUSPEND_NONE); 188 ourExceptionRequest.enable(); 189 } 190 191 private class EventHandler implements Runnable { 192 EventHandler() { 193 Thread thread = new Thread(this); 194 thread.setDaemon(true); 195 thread.start(); 196 } 197 198 private void notifyEvent(TargetListener listener, Event event) { 199 if (event instanceof BreakpointEvent) { 200 listener.breakpointReached((BreakpointEvent)event); 201 } else if (event instanceof ExceptionEvent) { 202 listener.exceptionThrown((ExceptionEvent)event); 203 } else if (event instanceof StepEvent) { 204 listener.stepCompleted((StepEvent)event); 205 } else if (event instanceof ClassPrepareEvent) { 206 listener.classPrepared((ClassPrepareEvent)event); 207 } else if (event instanceof ClassUnloadEvent) { 208 listener.classUnloaded((ClassUnloadEvent)event); 209 } else if (event instanceof MethodEntryEvent) { 210 listener.methodEntered((MethodEntryEvent)event); 211 } else if (event instanceof MethodExitEvent) { 212 listener.methodExited((MethodExitEvent)event); 213 } else if (event instanceof MonitorContendedEnterEvent) { 214 listener.monitorContendedEnter((MonitorContendedEnterEvent)event); 215 } else if (event instanceof MonitorContendedEnteredEvent) { 216 listener.monitorContendedEntered((MonitorContendedEnteredEvent)event); 217 } else if (event instanceof MonitorWaitEvent) { 218 listener.monitorWait((MonitorWaitEvent)event); 219 } else if (event instanceof MonitorWaitedEvent) { 220 listener.monitorWaited((MonitorWaitedEvent)event); 221 } else if (event instanceof AccessWatchpointEvent) { 222 listener.fieldAccessed((AccessWatchpointEvent)event); 223 } else if (event instanceof ModificationWatchpointEvent) { 224 listener.fieldModified((ModificationWatchpointEvent)event); 225 } else if (event instanceof ThreadStartEvent) { 226 listener.threadStarted((ThreadStartEvent)event); 227 } else if (event instanceof ThreadDeathEvent) { 228 listener.threadDied((ThreadDeathEvent)event); 229 } else if (event instanceof VMStartEvent) { 230 listener.vmStarted((VMStartEvent)event); 231 } else if (event instanceof VMDeathEvent) { 232 listener.vmDied((VMDeathEvent)event); 233 } else if (event instanceof VMDisconnectEvent) { 234 listener.vmDisconnected((VMDisconnectEvent)event); 235 } else { 236 throw new InternalError("Unknown event type: " + event.getClass()); 237 } 238 } 239 240 private void traceSuspendPolicy(int policy) { 241 if (shouldTrace) { 242 switch (policy) { 243 case EventRequest.SUSPEND_NONE: 244 traceln("TS: eventHandler: suspend = SUSPEND_NONE"); 245 break; 246 case EventRequest.SUSPEND_ALL: 247 traceln("TS: eventHandler: suspend = SUSPEND_ALL"); 248 break; 249 case EventRequest.SUSPEND_EVENT_THREAD: 250 traceln("TS: eventHandler: suspend = SUSPEND_EVENT_THREAD"); 251 break; 252 } 253 } 254 } 255 256 public void run() { 257 boolean connected = true; 258 do { 259 try { 260 EventSet set = vm.eventQueue().remove(); 261 traceSuspendPolicy(set.suspendPolicy()); 262 synchronized (listeners) { 263 ListIterator iter = listeners.listIterator(); 264 while (iter.hasNext()) { 265 TargetListener listener = (TargetListener)iter.next(); 266 traceln("TS: eventHandler: listener = " + listener); 267 listener.eventSetReceived(set); 268 if (listener.shouldRemoveListener()) { 269 iter.remove(); 270 } else { 271 Iterator jter = set.iterator(); 272 while (jter.hasNext()) { 273 Event event = (Event)jter.next(); 274 traceln("TS: eventHandler: event = " + event.getClass()); 275 276 if (event instanceof VMDisconnectEvent) { 277 connected = false; 278 } 279 listener.eventReceived(event); 280 if (listener.shouldRemoveListener()) { 281 iter.remove(); 282 break; 283 } 284 notifyEvent(listener, event); 285 if (listener.shouldRemoveListener()) { 286 iter.remove(); 287 break; 288 } 289 } 290 traceln("TS: eventHandler: end of events loop"); 291 if (!listener.shouldRemoveListener()) { 292 traceln("TS: eventHandler: calling ESC"); 293 listener.eventSetComplete(set); 294 if (listener.shouldRemoveListener()) { 295 iter.remove(); 296 } 297 } 298 } 299 traceln("TS: eventHandler: end of listeners loop"); 300 } 301 } 302 } catch (InterruptedException e) { 303 traceln("TS: eventHandler: InterruptedException"); 304 } catch (Exception e) { 305 failure("FAILED: Exception occured in eventHandler: " + e); 306 e.printStackTrace(); 307 connected = false; 308 synchronized(TestScaffold.this) { 309 // This will make the waiters such as waitForVMDisconnect 310 // exit their wait loops. 311 vmDisconnected = true; 312 TestScaffold.this.notifyAll(); 313 } 314 } 315 traceln("TS: eventHandler: End of outerloop"); 316 } while (connected); 317 traceln("TS: eventHandler: finished"); 318 } 319 } 320 321 /** 322 * Constructor 323 */ 324 public TestScaffold(String[] args) { 325 this.args = args; 326 } 327 328 public void enableScaffoldTrace() { 329 this.shouldTrace = true; 330 } 331 332 public void disableScaffoldTrace() { 333 this.shouldTrace = false; 334 } 335 336 /** 337 * Helper for the redefine method. Build the map 338 * needed for a redefine. 339 */ 340 protected Map makeRedefineMap(ReferenceType rt) throws Exception { 341 String className = rt.name(); 342 File path = new File(System.getProperty("test.classes", ".")); 343 className = className.replace('.', File.separatorChar); 344 File phyl = new File(path, className + ".class"); 345 byte[] bytes = new byte[(int)phyl.length()]; 346 InputStream in = new FileInputStream(phyl); 347 in.read(bytes); 348 in.close(); 349 350 Map map = new HashMap(); 351 map.put(rt, bytes); 352 353 return map; 354 } 355 356 /** 357 * Redefine a class - HotSwap it 358 */ 359 protected void redefine(ReferenceType rt) { 360 try { 361 println("Redefining " + rt); 362 vm().redefineClasses(makeRedefineMap(rt)); 363 } catch (Exception exc) { 364 failure("FAIL: redefine - unexpected exception: " + exc); 365 } 366 } 367 368 protected void startUp(String targetName) { 369 /* 370 * args[] contains all VM arguments followed by the app arguments. 371 * We need to insert targetName between the two types of arguments. 372 */ 373 boolean expectSecondArg = false; 374 boolean foundFirstAppArg = false; 375 List<String> argList = new ArrayList(); 376 for (int i = 0; i < args.length; i++) { 377 String arg = args[i].trim(); 378 if (foundFirstAppArg) { 379 argList.add(arg); 380 continue; 381 } 382 if (expectSecondArg) { 383 expectSecondArg = false; 384 argList.add(arg); 385 continue; 386 } 387 if (doubleWordArgs.contains(arg)) { 388 expectSecondArg = true; 389 argList.add(arg); 390 continue; 391 } 392 if (arg.startsWith("-")) { 393 argList.add(arg); 394 continue; 395 } 396 // We reached the first app argument. 397 argList.add(targetName); 398 argList.add(arg); 399 foundFirstAppArg = true; 400 } 401 402 if (!foundFirstAppArg) { 403 // Add the target since we didn't do that in the above loop. 404 argList.add(targetName); 405 } 406 407 println("run args: " + argList); 408 connect(argList.toArray(args)); 409 waitForVMStart(); 410 } 411 412 protected BreakpointEvent startToMain(String targetName) { 413 return startTo(targetName, "main", "([Ljava/lang/String;)V"); 414 } 415 416 protected BreakpointEvent startTo(String targetName, 417 String methodName, String signature) { 418 startUp(targetName); 419 traceln("TS: back from startUp"); 420 421 BreakpointEvent bpr = resumeTo(targetName, methodName, 422 signature); 423 Location loc = bpr.location(); 424 mainStartClass = loc.declaringType(); 425 if (redefineAtStart) { 426 redefine(mainStartClass); 427 } 428 if (redefineAsynchronously) { 429 Thread asyncDaemon = new Thread("Async Redefine") { 430 public void run() { 431 try { 432 Map redefMap = makeRedefineMap(mainStartClass); 433 434 while (true) { 435 println("Redefining " + mainStartClass); 436 vm().redefineClasses(redefMap); 437 Thread.sleep(100); 438 } 439 } catch (VMDisconnectedException vmde) { 440 println("async redefine - VM disconnected"); 441 } catch (Exception exc) { 442 failure("FAIL: async redefine - unexpected exception: " + exc); 443 } 444 } 445 }; 446 asyncDaemon.setDaemon(true); 447 asyncDaemon.start(); 448 } 449 450 if (System.getProperty("jpda.wait") != null) { 451 waitForInput(); 452 } 453 return bpr; 454 } 455 456 protected void waitForInput() { 457 try { 458 System.err.println("Press <enter> to continue"); 459 System.in.read(); 460 System.err.println("running..."); 461 462 } catch(Exception e) { 463 } 464 } 465 466 /* 467 * Test cases should implement tests in runTests and should 468 * initiate testing by calling run(). 469 */ 470 abstract protected void runTests() throws Exception; 471 472 final public void startTests() throws Exception { 473 startTime = System.currentTimeMillis(); 474 try { 475 runTests(); 476 } finally { 477 shutdown(); 478 } 479 } 480 481 protected void println(String str) { 482 long elapsed = System.currentTimeMillis() - startTime; 483 System.err.println("[" + elapsed + "ms] " + str); 484 } 485 486 protected void print(String str) { 487 System.err.print(str); 488 } 489 490 protected void traceln(String str) { 491 if (shouldTrace) { 492 println(str); 493 } 494 } 495 496 protected void failure(String str) { 497 println(str); 498 StackTraceElement[] trace = Thread.currentThread().getStackTrace(); 499 for (StackTraceElement traceElement : trace) { 500 System.err.println("\tat " + traceElement); 501 } 502 testFailed = true; 503 } 504 505 final List<String> doubleWordArgs = List.of( 506 "-connect", "-trace", // special TestScaffold args 507 "-cp", "-classpath", "--add-opens", "--class-path", 508 "--upgrade-module-path", "--add-modules", "-d", "--add-exports", 509 "--patch-module", "--module-path"); 510 511 private ArgInfo parseArgs(String args[]) { 512 ArgInfo argInfo = new ArgInfo(); 513 // Parse arguments, like java/j* tools command-line arguments. 514 // The first argument not-starting with '-' is treated as a classname. 515 // The other arguments are split to targetVMArgs targetAppCommandLine correspondingly. 516 // The example of args for line '@run driver Frames2Test -Xss4M' is '-Xss4M' 'Frames2Targ'. 517 // The result without any wrapper enabled: 518 // argInfo.targetAppCommandLine : Frames2Targ 519 // argInfo.targetVMArgs : -Xss4M 520 // The result with wrapper enabled: 521 // argInfo.targetAppCommandLine : DebuggeeWrapper Frames2Targ 522 // argInfo.targetVMArgs : -Xss4M -Dtest.thread.factory=Virtual 523 boolean classNameParsed = false; 524 for (int i = 0; i < args.length; i++) { 525 String arg = args[i].trim(); 526 if (classNameParsed) { 527 // once classname is read, treat any other arguments as app arguments 528 argInfo.targetAppCommandLine += (arg + ' '); 529 continue; 530 } 531 if (arg.equals("-connect")) { 532 i++; 533 argInfo.connectorSpec = args[i]; 534 } else if (arg.equals("-trace")) { 535 i++; 536 argInfo.traceFlags = Integer.decode(args[i]).intValue(); 537 } else if (arg.equals("-redefstart")) { 538 redefineAtStart = true; 539 } else if (arg.equals("-redefevent")) { 540 redefineAtEvents = true; 541 } else if (arg.equals("-redefasync")) { 542 redefineAsynchronously = true; 543 } else if (arg.startsWith("-J")) { 544 throw new RuntimeException("-J-option format is not supported. Incorrect arg: " + arg); 545 } else if (arg.startsWith("-")) { 546 argInfo.targetVMArgs += (arg + ' '); 547 if (doubleWordArgs.contains(arg)) { 548 i++; 549 argInfo.targetVMArgs += (args[i] + ' '); 550 } 551 } else { 552 classNameParsed = true; 553 argInfo.targetAppCommandLine += (arg + ' '); 554 } 555 } 556 557 // Need to change args to run wrapper using command like 'DebuggeeWrapper <app-name>' 558 // and set property 'test.thread.factory' so test could use DebuggeeWrapper.isVirtual() method 559 String testThreadFactoryName = DebuggeeWrapper.getTestThreadFactoryName(); 560 if (testThreadFactoryName != null && !argInfo.targetAppCommandLine.isEmpty()) { 561 argInfo.targetVMArgs += "-D" + DebuggeeWrapper.PROPERTY_NAME + "=" + testThreadFactoryName; 562 argInfo.targetAppCommandLine = DebuggeeWrapper.class.getName() + ' ' + argInfo.targetAppCommandLine; 563 } else if ("true".equals(System.getProperty("test.enable.preview"))) { 564 // the test specified @enablePreview. 565 argInfo.targetVMArgs += "--enable-preview "; 566 } 567 return argInfo; 568 } 569 570 /** 571 * This is called to connect to a debuggee VM. It starts the VM and 572 * installs a listener to catch VMStartEvent, our default events, and 573 * VMDisconnectedEvent. When these events appear, that is remembered 574 * and waiters are notified. 575 * This is normally called in the main thread of the test case. 576 * It starts up an EventHandler thread that gets events coming in 577 * from the debuggee and distributes them to listeners. That thread 578 * keeps running until a VMDisconnectedEvent occurs or some exception 579 * occurs during its processing. 580 * 581 * The 'listenUntilVMDisconnect' method adds 'this' as a listener. 582 * This means that 'this's vmDied method will get called. This has a 583 * default impl in TargetAdapter.java which can be overridden in the 584 * testcase. 585 * 586 * waitForRequestedEvent also adds an adaptor listener that listens 587 * for the particular event it is supposed to wait for (and it also 588 * catches VMDisconnectEvents.) This listener is removed once 589 * its eventReceived method is called. 590 * waitForRequestedEvent is called by most of the methods to do bkpts, 591 * etc. 592 */ 593 public void connect(String args[]) { 594 ArgInfo argInfo = parseArgs(args); 595 596 argInfo.targetVMArgs = VMConnection.getDebuggeeVMOptions() + " " + argInfo.targetVMArgs; 597 connection = new VMConnection(argInfo.connectorSpec, 598 argInfo.traceFlags, 599 includeVThreads); 600 601 addListener(new TargetAdapter() { 602 public void eventSetComplete(EventSet set) { 603 if (TestScaffold.this.containsOurVMDeathRequest(set)) { 604 traceln("TS: connect: set.resume() called"); 605 set.resume(); 606 607 // Note that we want to do the above resume before 608 // waking up any sleepers. 609 synchronized(TestScaffold.this) { 610 TestScaffold.this.notifyAll(); 611 } 612 } 613 } 614 public void eventReceived(Event event) { 615 if (redefineAtEvents && event instanceof Locatable) { 616 Location loc = ((Locatable)event).location(); 617 ReferenceType rt = loc.declaringType(); 618 String name = rt.name(); 619 if (name.startsWith("java.") 620 || name.startsWith("sun.") 621 || name.startsWith("com.") 622 || name.startsWith("jdk.")) { 623 if (mainStartClass != null) { 624 redefine(mainStartClass); 625 } 626 } else { 627 redefine(rt); 628 } 629 } 630 } 631 632 public void vmStarted(VMStartEvent event) { 633 synchronized(TestScaffold.this) { 634 vmStartThread = event.thread(); 635 TestScaffold.this.notifyAll(); 636 } 637 } 638 /** 639 * By default, we catch uncaught exceptions and print a msg. 640 * The testcase must override the createDefaultExceptionRequest 641 * method if it doesn't want this behavior. 642 */ 643 public void exceptionThrown(ExceptionEvent event) { 644 if (TestScaffold.this.ourExceptionRequest != null && 645 TestScaffold.this.ourExceptionRequest.equals( 646 event.request())) { 647 /* 648 * See 649 * 5038723: com/sun/jdi/sde/TemperatureTableTest.java: 650 * intermittent ObjectCollectedException 651 * Since this request was SUSPEND_NONE, the debuggee 652 * could keep running and the calls below back into 653 * the debuggee might not work. That is why we 654 * have this try/catch. 655 */ 656 try { 657 println("Note: Unexpected Debuggee Exception: " + 658 event.exception().referenceType().name() + 659 " at line " + event.location().lineNumber()); 660 TestScaffold.this.exceptionCaught = true; 661 662 ObjectReference obj = event.exception(); 663 ReferenceType rtt = obj.referenceType(); 664 Field detail = rtt.fieldByName("detailMessage"); 665 Value val = obj.getValue(detail); 666 println("detailMessage = " + val); 667 668 /* 669 * This code is commented out because it needs a thread 670 * in which to do the invokeMethod and we don't have 671 * one. To enable this code change the request 672 * to be SUSPEND_ALL in createDefaultExceptionRequest, 673 * and then put this line 674 * mainThread = bpe.thread(); 675 * in the testcase after the line 676 * BreakpointEvent bpe = startToMain("...."); 677 */ 678 if (false) { 679 List lll = rtt.methodsByName("printStackTrace"); 680 Method mm = (Method)lll.get(0); 681 obj.invokeMethod(mainThread, mm, new ArrayList(0), 0); 682 } 683 } catch (Exception ee) { 684 println("TestScaffold Exception while handling debuggee Exception: " 685 + ee); 686 } 687 } 688 } 689 690 public void vmDied(VMDeathEvent event) { 691 vmDied = true; 692 traceln("TS: vmDied called"); 693 } 694 695 public void vmDisconnected(VMDisconnectEvent event) { 696 synchronized(TestScaffold.this) { 697 vmDisconnected = true; 698 TestScaffold.this.notifyAll(); 699 } 700 } 701 }); 702 if (connection.connector().name().equals("com.sun.jdi.CommandLineLaunch")) { 703 if (argInfo.targetVMArgs.length() > 0) { 704 if (connection.connectorArg("options").length() > 0) { 705 throw new IllegalArgumentException("VM options in two places"); 706 } 707 connection.setConnectorArg("options", argInfo.targetVMArgs); 708 } 709 if (argInfo.targetAppCommandLine.length() > 0) { 710 if (connection.connectorArg("main").length() > 0) { 711 throw new IllegalArgumentException("Command line in two places"); 712 } 713 connection.setConnectorArg("main", argInfo.targetAppCommandLine); 714 } 715 } 716 717 vm = connection.open(); 718 requestManager = vm.eventRequestManager(); 719 createDefaultEventRequests(); 720 new EventHandler(); 721 } 722 723 724 public VirtualMachine vm() { 725 return vm; 726 } 727 728 public EventRequestManager eventRequestManager() { 729 return requestManager; 730 } 731 732 public void addListener(TargetListener listener) { 733 traceln("TS: Adding listener " + listener); 734 listeners.add(listener); 735 } 736 737 public void removeListener(TargetListener listener) { 738 traceln("TS: Removing listener " + listener); 739 listeners.remove(listener); 740 } 741 742 743 protected void listenUntilVMDisconnect() { 744 try { 745 addListener (this); 746 } catch (Exception ex){ 747 ex.printStackTrace(); 748 testFailed = true; 749 } finally { 750 // Allow application to complete and shut down 751 resumeToVMDisconnect(); 752 } 753 } 754 755 public synchronized ThreadReference waitForVMStart() { 756 while ((vmStartThread == null) && !vmDisconnected) { 757 try { 758 wait(); 759 } catch (InterruptedException e) { 760 } 761 } 762 763 if (vmStartThread == null) { 764 throw new VMDisconnectedException(); 765 } 766 767 return vmStartThread; 768 } 769 770 /* 771 * Tests that expect an exitValue other than 0 or 1 will need to override this method. 772 */ 773 protected boolean allowedExitValue(int exitValue) { 774 return exitValue == 0; 775 } 776 777 public synchronized void waitForVMDisconnect() { 778 traceln("TS: waitForVMDisconnect"); 779 while (!vmDisconnected) { 780 try { 781 wait(); 782 } catch (InterruptedException e) { 783 } 784 } 785 786 // Make sure debuggee exits with no errors. Otherwise failures might go unnoticed. 787 Process p = vm.process(); 788 try { 789 p.waitFor(); 790 } catch (InterruptedException e) { 791 throw new RuntimeException(e); 792 } 793 int exitValue = p.exitValue(); 794 if (!allowedExitValue(exitValue)) { 795 throw new RuntimeException("Invalid debuggee exitValue: " + exitValue); 796 } 797 798 traceln("TS: waitForVMDisconnect: done"); 799 } 800 801 public Event waitForRequestedEvent(final EventRequest request) { 802 class EventNotification { 803 Event event; 804 boolean disconnected = false; 805 } 806 final EventNotification en = new EventNotification(); 807 808 TargetAdapter adapter = new TargetAdapter() { 809 public void eventReceived(Event event) { 810 if (request.equals(event.request())) { 811 traceln("TS:Listener2: got requested event"); 812 synchronized (en) { 813 en.event = event; 814 en.notifyAll(); 815 } 816 removeThisListener(); 817 } else if (event instanceof VMDisconnectEvent) { 818 traceln("TS:Listener2: got VMDisconnectEvent"); 819 synchronized (en) { 820 en.disconnected = true; 821 en.notifyAll(); 822 } 823 removeThisListener(); 824 } 825 } 826 }; 827 828 addListener(adapter); 829 830 try { 831 synchronized (en) { 832 traceln("TS: waitForRequestedEvent: vm.resume called"); 833 vm.resume(); 834 835 while (!en.disconnected && (en.event == null)) { 836 en.wait(); 837 } 838 } 839 } catch (InterruptedException e) { 840 return null; 841 } 842 843 if (en.disconnected) { 844 throw new RuntimeException("VM Disconnected before requested event occurred"); 845 } 846 return en.event; 847 } 848 849 private StepEvent doStep(ThreadReference thread, int gran, int depth) { 850 final StepRequest sr = 851 requestManager.createStepRequest(thread, gran, depth); 852 853 sr.addClassExclusionFilter("java.*"); 854 sr.addClassExclusionFilter("javax.*"); 855 sr.addClassExclusionFilter("sun.*"); 856 sr.addClassExclusionFilter("com.sun.*"); 857 sr.addClassExclusionFilter("com.oracle.*"); 858 sr.addClassExclusionFilter("oracle.*"); 859 sr.addClassExclusionFilter("jdk.internal.*"); 860 sr.addClassExclusionFilter("jdk.jfr.*"); 861 sr.addCountFilter(1); 862 sr.enable(); 863 StepEvent retEvent = (StepEvent)waitForRequestedEvent(sr); 864 requestManager.deleteEventRequest(sr); 865 return retEvent; 866 } 867 868 public StepEvent stepIntoInstruction(ThreadReference thread) { 869 return doStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_INTO); 870 } 871 872 public StepEvent stepIntoLine(ThreadReference thread) { 873 return doStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_INTO); 874 } 875 876 public StepEvent stepOverInstruction(ThreadReference thread) { 877 return doStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_OVER); 878 } 879 880 public StepEvent stepOverLine(ThreadReference thread) { 881 return doStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_OVER); 882 } 883 884 public StepEvent stepOut(ThreadReference thread) { 885 return doStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_OUT); 886 } 887 888 public BreakpointEvent resumeTo(Location loc) { 889 return resumeTo(loc, false); 890 } 891 892 public BreakpointEvent resumeTo(Location loc, boolean suspendThread) { 893 final BreakpointRequest request = 894 requestManager.createBreakpointRequest(loc); 895 request.addCountFilter(1); 896 if (suspendThread) { 897 request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); 898 } 899 request.enable(); 900 return (BreakpointEvent)waitForRequestedEvent(request); 901 } 902 903 public ReferenceType findReferenceType(String name) { 904 List rts = vm.classesByName(name); 905 Iterator iter = rts.iterator(); 906 while (iter.hasNext()) { 907 ReferenceType rt = (ReferenceType)iter.next(); 908 if (rt.name().equals(name)) { 909 return rt; 910 } 911 } 912 return null; 913 } 914 915 public Method findMethod(ReferenceType rt, String name, String signature) { 916 List methods = rt.methods(); 917 Iterator iter = methods.iterator(); 918 while (iter.hasNext()) { 919 Method method = (Method)iter.next(); 920 if (method.name().equals(name) && 921 method.signature().equals(signature)) { 922 return method; 923 } 924 } 925 return null; 926 } 927 928 public Location findLocation(ReferenceType rt, int lineNumber) 929 throws AbsentInformationException { 930 List locs = rt.locationsOfLine(lineNumber); 931 if (locs.size() == 0) { 932 throw new IllegalArgumentException("Bad line number"); 933 } else if (locs.size() > 1) { 934 throw new IllegalArgumentException("Line number has multiple locations"); 935 } 936 937 return (Location)locs.get(0); 938 } 939 940 public Location findMethodLocation(ReferenceType rt, String methodName, 941 String methodSignature, int methodLineNumber) 942 throws AbsentInformationException { 943 Method m = findMethod(rt, methodName, methodSignature); 944 int lineNumber = m.location().lineNumber() + methodLineNumber - 1; 945 return findLocation(rt, lineNumber); 946 } 947 948 public BreakpointEvent resumeTo(String clsName, String methodName, 949 String methodSignature) { 950 return resumeTo(clsName, methodName, methodSignature, false /* suspendThread */); 951 } 952 953 public BreakpointEvent resumeTo(String clsName, String methodName, 954 String methodSignature, 955 boolean suspendThread) { 956 ReferenceType rt = findReferenceType(clsName); 957 if (rt == null) { 958 rt = resumeToPrepareOf(clsName).referenceType(); 959 } 960 961 Method method = findMethod(rt, methodName, methodSignature); 962 if (method == null) { 963 throw new IllegalArgumentException("Bad method name/signature: " 964 + clsName + "." + methodName + ":" + methodSignature); 965 } 966 967 return resumeTo(method.location(), suspendThread); 968 } 969 970 public BreakpointEvent resumeTo(String clsName, int lineNumber) throws AbsentInformationException { 971 return resumeTo(clsName, lineNumber, false); 972 } 973 974 public BreakpointEvent resumeTo(String clsName, int lineNumber, boolean suspendThread) throws AbsentInformationException { 975 ReferenceType rt = findReferenceType(clsName); 976 if (rt == null) { 977 rt = resumeToPrepareOf(clsName).referenceType(); 978 } 979 980 return resumeTo(findLocation(rt, lineNumber), suspendThread); 981 } 982 983 public ClassPrepareEvent resumeToPrepareOf(String className) { 984 final ClassPrepareRequest request = 985 requestManager.createClassPrepareRequest(); 986 request.addClassFilter(className); 987 request.addCountFilter(1); 988 request.enable(); 989 return (ClassPrepareEvent)waitForRequestedEvent(request); 990 } 991 992 public void resumeForMsecs(long msecs) { 993 try { 994 addListener (this); 995 } catch (Exception ex){ 996 ex.printStackTrace(); 997 testFailed = true; 998 return; 999 } 1000 1001 try { 1002 vm().resume(); 1003 } catch (VMDisconnectedException e) { 1004 } 1005 1006 if (!vmDisconnected) { 1007 try { 1008 System.out.println("Sleeping for " + msecs + " milleseconds"); 1009 Thread.sleep(msecs); 1010 vm().suspend(); 1011 } catch (InterruptedException e) { 1012 } 1013 } 1014 } 1015 1016 public void resumeToVMDisconnect() { 1017 try { 1018 traceln("TS: resumeToVMDisconnect: vm.resume called"); 1019 vm.resume(); 1020 } catch (VMDisconnectedException e) { 1021 // clean up below 1022 } 1023 waitForVMDisconnect(); 1024 } 1025 1026 public void shutdown() { 1027 shutdown(null); 1028 } 1029 1030 public void shutdown(String message) { 1031 traceln("TS: shutdown: vmDied= " + vmDied + 1032 ", vmDisconnected= " + vmDisconnected + 1033 ", connection = " + connection); 1034 1035 if ((connection != null)) { 1036 try { 1037 connection.disposeVM(); 1038 } catch (VMDisconnectedException e) { 1039 // Shutting down after the VM has gone away. This is 1040 // not an error, and we just ignore it. 1041 } 1042 } else { 1043 traceln("TS: shutdown: disposeVM not called"); 1044 } 1045 if (message != null) { 1046 println(message); 1047 } 1048 1049 vmDied = true; 1050 vmDisconnected = true; 1051 } 1052 1053 }