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 }