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 } 564 if ("true".equals(System.getProperty("test.enable.preview"))) { 565 // the test specified @enablePreview. 566 argInfo.targetVMArgs += "--enable-preview "; 567 } 568 return argInfo; 569 } 570 571 /** 572 * This is called to connect to a debuggee VM. It starts the VM and 573 * installs a listener to catch VMStartEvent, our default events, and 574 * VMDisconnectedEvent. When these events appear, that is remembered 575 * and waiters are notified. 576 * This is normally called in the main thread of the test case. 577 * It starts up an EventHandler thread that gets events coming in 578 * from the debuggee and distributes them to listeners. That thread 579 * keeps running until a VMDisconnectedEvent occurs or some exception 580 * occurs during its processing. 581 * 582 * The 'listenUntilVMDisconnect' method adds 'this' as a listener. 583 * This means that 'this's vmDied method will get called. This has a 584 * default impl in TargetAdapter.java which can be overridden in the 585 * testcase. 586 * 587 * waitForRequestedEvent also adds an adaptor listener that listens 588 * for the particular event it is supposed to wait for (and it also 589 * catches VMDisconnectEvents.) This listener is removed once 590 * its eventReceived method is called. 591 * waitForRequestedEvent is called by most of the methods to do bkpts, 592 * etc. 593 */ 594 public void connect(String args[]) { 595 ArgInfo argInfo = parseArgs(args); 596 597 argInfo.targetVMArgs = VMConnection.getDebuggeeVMOptions() + " " + argInfo.targetVMArgs; 598 connection = new VMConnection(argInfo.connectorSpec, 599 argInfo.traceFlags, 600 includeVThreads); 601 602 addListener(new TargetAdapter() { 603 public void eventSetComplete(EventSet set) { 604 if (TestScaffold.this.containsOurVMDeathRequest(set)) { 605 traceln("TS: connect: set.resume() called"); 606 set.resume(); 607 608 // Note that we want to do the above resume before 609 // waking up any sleepers. 610 synchronized(TestScaffold.this) { 611 TestScaffold.this.notifyAll(); 612 } 613 } 614 } 615 public void eventReceived(Event event) { 616 if (redefineAtEvents && event instanceof Locatable) { 617 Location loc = ((Locatable)event).location(); 618 ReferenceType rt = loc.declaringType(); 619 String name = rt.name(); 620 if (name.startsWith("java.") 621 || name.startsWith("sun.") 622 || name.startsWith("com.") 623 || name.startsWith("jdk.")) { 624 if (mainStartClass != null) { 625 redefine(mainStartClass); 626 } 627 } else { 628 redefine(rt); 629 } 630 } 631 } 632 633 public void vmStarted(VMStartEvent event) { 634 synchronized(TestScaffold.this) { 635 vmStartThread = event.thread(); 636 TestScaffold.this.notifyAll(); 637 } 638 } 639 /** 640 * By default, we catch uncaught exceptions and print a msg. 641 * The testcase must override the createDefaultExceptionRequest 642 * method if it doesn't want this behavior. 643 */ 644 public void exceptionThrown(ExceptionEvent event) { 645 if (TestScaffold.this.ourExceptionRequest != null && 646 TestScaffold.this.ourExceptionRequest.equals( 647 event.request())) { 648 /* 649 * See 650 * 5038723: com/sun/jdi/sde/TemperatureTableTest.java: 651 * intermittent ObjectCollectedException 652 * Since this request was SUSPEND_NONE, the debuggee 653 * could keep running and the calls below back into 654 * the debuggee might not work. That is why we 655 * have this try/catch. 656 */ 657 try { 658 println("Note: Unexpected Debuggee Exception: " + 659 event.exception().referenceType().name() + 660 " at line " + event.location().lineNumber()); 661 TestScaffold.this.exceptionCaught = true; 662 663 ObjectReference obj = event.exception(); 664 ReferenceType rtt = obj.referenceType(); 665 Field detail = rtt.fieldByName("detailMessage"); 666 Value val = obj.getValue(detail); 667 println("detailMessage = " + val); 668 669 /* 670 * This code is commented out because it needs a thread 671 * in which to do the invokeMethod and we don't have 672 * one. To enable this code change the request 673 * to be SUSPEND_ALL in createDefaultExceptionRequest, 674 * and then put this line 675 * mainThread = bpe.thread(); 676 * in the testcase after the line 677 * BreakpointEvent bpe = startToMain("...."); 678 */ 679 if (false) { 680 List lll = rtt.methodsByName("printStackTrace"); 681 Method mm = (Method)lll.get(0); 682 obj.invokeMethod(mainThread, mm, new ArrayList(0), 0); 683 } 684 } catch (Exception ee) { 685 println("TestScaffold Exception while handling debuggee Exception: " 686 + ee); 687 } 688 } 689 } 690 691 public void vmDied(VMDeathEvent event) { 692 vmDied = true; 693 traceln("TS: vmDied called"); 694 } 695 696 public void vmDisconnected(VMDisconnectEvent event) { 697 synchronized(TestScaffold.this) { 698 vmDisconnected = true; 699 TestScaffold.this.notifyAll(); 700 } 701 } 702 }); 703 if (connection.connector().name().equals("com.sun.jdi.CommandLineLaunch")) { 704 if (argInfo.targetVMArgs.length() > 0) { 705 if (connection.connectorArg("options").length() > 0) { 706 throw new IllegalArgumentException("VM options in two places"); 707 } 708 connection.setConnectorArg("options", argInfo.targetVMArgs); 709 } 710 if (argInfo.targetAppCommandLine.length() > 0) { 711 if (connection.connectorArg("main").length() > 0) { 712 throw new IllegalArgumentException("Command line in two places"); 713 } 714 connection.setConnectorArg("main", argInfo.targetAppCommandLine); 715 } 716 } 717 718 vm = connection.open(); 719 requestManager = vm.eventRequestManager(); 720 createDefaultEventRequests(); 721 new EventHandler(); 722 } 723 724 725 public VirtualMachine vm() { 726 return vm; 727 } 728 729 public EventRequestManager eventRequestManager() { 730 return requestManager; 731 } 732 733 public void addListener(TargetListener listener) { 734 traceln("TS: Adding listener " + listener); 735 listeners.add(listener); 736 } 737 738 public void removeListener(TargetListener listener) { 739 traceln("TS: Removing listener " + listener); 740 listeners.remove(listener); 741 } 742 743 744 protected void listenUntilVMDisconnect() { 745 try { 746 addListener (this); 747 } catch (Exception ex){ 748 ex.printStackTrace(); 749 testFailed = true; 750 } finally { 751 // Allow application to complete and shut down 752 resumeToVMDisconnect(); 753 } 754 } 755 756 public synchronized ThreadReference waitForVMStart() { 757 while ((vmStartThread == null) && !vmDisconnected) { 758 try { 759 wait(); 760 } catch (InterruptedException e) { 761 } 762 } 763 764 if (vmStartThread == null) { 765 throw new VMDisconnectedException(); 766 } 767 768 return vmStartThread; 769 } 770 771 /* 772 * Tests that expect an exitValue other than 0 or 1 will need to override this method. 773 */ 774 protected boolean allowedExitValue(int exitValue) { 775 return exitValue == 0; 776 } 777 778 public synchronized void waitForVMDisconnect() { 779 traceln("TS: waitForVMDisconnect"); 780 while (!vmDisconnected) { 781 try { 782 wait(); 783 } catch (InterruptedException e) { 784 } 785 } 786 787 // Make sure debuggee exits with no errors. Otherwise failures might go unnoticed. 788 Process p = vm.process(); 789 try { 790 p.waitFor(); 791 } catch (InterruptedException e) { 792 throw new RuntimeException(e); 793 } 794 int exitValue = p.exitValue(); 795 if (!allowedExitValue(exitValue)) { 796 throw new RuntimeException("Invalid debuggee exitValue: " + exitValue); 797 } 798 799 traceln("TS: waitForVMDisconnect: done"); 800 } 801 802 public Event waitForRequestedEvent(final EventRequest request) { 803 class EventNotification { 804 Event event; 805 boolean disconnected = false; 806 } 807 final EventNotification en = new EventNotification(); 808 809 TargetAdapter adapter = new TargetAdapter() { 810 public void eventReceived(Event event) { 811 if (request.equals(event.request())) { 812 traceln("TS:Listener2: got requested event"); 813 synchronized (en) { 814 en.event = event; 815 en.notifyAll(); 816 } 817 removeThisListener(); 818 } else if (event instanceof VMDisconnectEvent) { 819 traceln("TS:Listener2: got VMDisconnectEvent"); 820 synchronized (en) { 821 en.disconnected = true; 822 en.notifyAll(); 823 } 824 removeThisListener(); 825 } 826 } 827 }; 828 829 addListener(adapter); 830 831 try { 832 synchronized (en) { 833 traceln("TS: waitForRequestedEvent: vm.resume called"); 834 vm.resume(); 835 836 while (!en.disconnected && (en.event == null)) { 837 en.wait(); 838 } 839 } 840 } catch (InterruptedException e) { 841 return null; 842 } 843 844 if (en.disconnected) { 845 throw new RuntimeException("VM Disconnected before requested event occurred"); 846 } 847 return en.event; 848 } 849 850 private StepEvent doStep(ThreadReference thread, int gran, int depth) { 851 final StepRequest sr = 852 requestManager.createStepRequest(thread, gran, depth); 853 854 sr.addClassExclusionFilter("java.*"); 855 sr.addClassExclusionFilter("javax.*"); 856 sr.addClassExclusionFilter("sun.*"); 857 sr.addClassExclusionFilter("com.sun.*"); 858 sr.addClassExclusionFilter("com.oracle.*"); 859 sr.addClassExclusionFilter("oracle.*"); 860 sr.addClassExclusionFilter("jdk.internal.*"); 861 sr.addClassExclusionFilter("jdk.jfr.*"); 862 sr.addCountFilter(1); 863 sr.enable(); 864 StepEvent retEvent = (StepEvent)waitForRequestedEvent(sr); 865 requestManager.deleteEventRequest(sr); 866 return retEvent; 867 } 868 869 public StepEvent stepIntoInstruction(ThreadReference thread) { 870 return doStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_INTO); 871 } 872 873 public StepEvent stepIntoLine(ThreadReference thread) { 874 return doStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_INTO); 875 } 876 877 public StepEvent stepOverInstruction(ThreadReference thread) { 878 return doStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_OVER); 879 } 880 881 public StepEvent stepOverLine(ThreadReference thread) { 882 return doStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_OVER); 883 } 884 885 public StepEvent stepOut(ThreadReference thread) { 886 return doStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_OUT); 887 } 888 889 public BreakpointEvent resumeTo(Location loc) { 890 return resumeTo(loc, false); 891 } 892 893 public BreakpointEvent resumeTo(Location loc, boolean suspendThread) { 894 final BreakpointRequest request = 895 requestManager.createBreakpointRequest(loc); 896 request.addCountFilter(1); 897 if (suspendThread) { 898 request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); 899 } 900 request.enable(); 901 return (BreakpointEvent)waitForRequestedEvent(request); 902 } 903 904 public ReferenceType findReferenceType(String name) { 905 List rts = vm.classesByName(name); 906 Iterator iter = rts.iterator(); 907 while (iter.hasNext()) { 908 ReferenceType rt = (ReferenceType)iter.next(); 909 if (rt.name().equals(name)) { 910 return rt; 911 } 912 } 913 return null; 914 } 915 916 public Method findMethod(ReferenceType rt, String name, String signature) { 917 List methods = rt.methods(); 918 Iterator iter = methods.iterator(); 919 while (iter.hasNext()) { 920 Method method = (Method)iter.next(); 921 if (method.name().equals(name) && 922 method.signature().equals(signature)) { 923 return method; 924 } 925 } 926 return null; 927 } 928 929 public Location findLocation(ReferenceType rt, int lineNumber) 930 throws AbsentInformationException { 931 List locs = rt.locationsOfLine(lineNumber); 932 if (locs.size() == 0) { 933 throw new IllegalArgumentException("Bad line number"); 934 } else if (locs.size() > 1) { 935 throw new IllegalArgumentException("Line number has multiple locations"); 936 } 937 938 return (Location)locs.get(0); 939 } 940 941 public Location findMethodLocation(ReferenceType rt, String methodName, 942 String methodSignature, int methodLineNumber) 943 throws AbsentInformationException { 944 Method m = findMethod(rt, methodName, methodSignature); 945 int lineNumber = m.location().lineNumber() + methodLineNumber - 1; 946 return findLocation(rt, lineNumber); 947 } 948 949 public BreakpointEvent resumeTo(String clsName, String methodName, 950 String methodSignature) { 951 return resumeTo(clsName, methodName, methodSignature, false /* suspendThread */); 952 } 953 954 public BreakpointEvent resumeTo(String clsName, String methodName, 955 String methodSignature, 956 boolean suspendThread) { 957 ReferenceType rt = findReferenceType(clsName); 958 if (rt == null) { 959 rt = resumeToPrepareOf(clsName).referenceType(); 960 } 961 962 Method method = findMethod(rt, methodName, methodSignature); 963 if (method == null) { 964 throw new IllegalArgumentException("Bad method name/signature: " 965 + clsName + "." + methodName + ":" + methodSignature); 966 } 967 968 return resumeTo(method.location(), suspendThread); 969 } 970 971 public BreakpointEvent resumeTo(String clsName, int lineNumber) throws AbsentInformationException { 972 return resumeTo(clsName, lineNumber, false); 973 } 974 975 public BreakpointEvent resumeTo(String clsName, int lineNumber, boolean suspendThread) throws AbsentInformationException { 976 ReferenceType rt = findReferenceType(clsName); 977 if (rt == null) { 978 rt = resumeToPrepareOf(clsName).referenceType(); 979 } 980 981 return resumeTo(findLocation(rt, lineNumber), suspendThread); 982 } 983 984 public ClassPrepareEvent resumeToPrepareOf(String className) { 985 final ClassPrepareRequest request = 986 requestManager.createClassPrepareRequest(); 987 request.addClassFilter(className); 988 request.addCountFilter(1); 989 request.enable(); 990 return (ClassPrepareEvent)waitForRequestedEvent(request); 991 } 992 993 public void resumeForMsecs(long msecs) { 994 try { 995 addListener (this); 996 } catch (Exception ex){ 997 ex.printStackTrace(); 998 testFailed = true; 999 return; 1000 } 1001 1002 try { 1003 vm().resume(); 1004 } catch (VMDisconnectedException e) { 1005 } 1006 1007 if (!vmDisconnected) { 1008 try { 1009 System.out.println("Sleeping for " + msecs + " milleseconds"); 1010 Thread.sleep(msecs); 1011 vm().suspend(); 1012 } catch (InterruptedException e) { 1013 } 1014 } 1015 } 1016 1017 public void resumeToVMDisconnect() { 1018 try { 1019 traceln("TS: resumeToVMDisconnect: vm.resume called"); 1020 vm.resume(); 1021 } catch (VMDisconnectedException e) { 1022 // clean up below 1023 } 1024 waitForVMDisconnect(); 1025 } 1026 1027 public void shutdown() { 1028 shutdown(null); 1029 } 1030 1031 public void shutdown(String message) { 1032 traceln("TS: shutdown: vmDied= " + vmDied + 1033 ", vmDisconnected= " + vmDisconnected + 1034 ", connection = " + connection); 1035 1036 if ((connection != null)) { 1037 try { 1038 connection.disposeVM(); 1039 } catch (VMDisconnectedException e) { 1040 // Shutting down after the VM has gone away. This is 1041 // not an error, and we just ignore it. 1042 } 1043 } else { 1044 traceln("TS: shutdown: disposeVM not called"); 1045 } 1046 if (message != null) { 1047 println(message); 1048 } 1049 1050 vmDied = true; 1051 vmDisconnected = true; 1052 } 1053 1054 }