1/*
2 * Copyright (c) 1999, 2013, 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
24import com.sun.jdi.*;
25import com.sun.jdi.request.*;
26import com.sun.jdi.event.*;
27import java.util.*;
28import java.io.*;
29
30
31/**
32 * Framework used by all JDI regression tests
33 */
34abstract public class JDIScaffold {
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    ThreadReference vmStartThread = null;
41    boolean vmDied = false;
42    boolean vmDisconnected = false;
43
44    static private class ArgInfo {
45        String targetVMArgs = "";
46        String targetAppCommandLine = "";
47        String connectorSpec = "com.sun.jdi.CommandLineLaunch:";
48        int traceFlags = 0;
49    }
50
51    static public interface TargetListener {
52        boolean eventSetReceived(EventSet set);
53        boolean eventSetComplete(EventSet set);
54        boolean eventReceived(Event event);
55        boolean breakpointReached(BreakpointEvent event);
56        boolean exceptionThrown(ExceptionEvent event);
57        boolean stepCompleted(StepEvent event);
58        boolean classPrepared(ClassPrepareEvent event);
59        boolean classUnloaded(ClassUnloadEvent event);
60        boolean methodEntered(MethodEntryEvent event);
61        boolean methodExited(MethodExitEvent event);
62        boolean fieldAccessed(AccessWatchpointEvent event);
63        boolean fieldModified(ModificationWatchpointEvent event);
64        boolean threadStarted(ThreadStartEvent event);
65        boolean threadDied(ThreadDeathEvent event);
66        boolean vmStarted(VMStartEvent event);
67        boolean vmDied(VMDeathEvent event);
68        boolean vmDisconnected(VMDisconnectEvent event);
69    }
70
71    static public class TargetAdapter implements TargetListener {
72        public boolean eventSetReceived(EventSet set) {
73            return false;
74        }
75        public boolean eventSetComplete(EventSet set) {
76            return false;
77        }
78        public boolean eventReceived(Event event) {
79            return false;
80        }
81        public boolean breakpointReached(BreakpointEvent event) {
82            return false;
83        }
84        public boolean exceptionThrown(ExceptionEvent event) {
85            return false;
86        }
87        public boolean stepCompleted(StepEvent event) {
88            return false;
89        }
90        public boolean classPrepared(ClassPrepareEvent event) {
91            return false;
92        }
93        public boolean classUnloaded(ClassUnloadEvent event) {
94            return false;
95        }
96        public boolean methodEntered(MethodEntryEvent event) {
97            return false;
98        }
99        public boolean methodExited(MethodExitEvent event) {
100            return false;
101        }
102        public boolean fieldAccessed(AccessWatchpointEvent event) {
103            return false;
104        }
105        public boolean fieldModified(ModificationWatchpointEvent event) {
106            return false;
107        }
108        public boolean threadStarted(ThreadStartEvent event) {
109            return false;
110        }
111        public boolean threadDied(ThreadDeathEvent event) {
112            return false;
113        }
114        public boolean vmStarted(VMStartEvent event) {
115            return false;
116        }
117        public boolean vmDied(VMDeathEvent event) {
118            return false;
119        }
120        public boolean vmDisconnected(VMDisconnectEvent event) {
121            return false;
122        }
123    }
124
125    private class EventHandler implements Runnable {
126        EventHandler() {
127            Thread thread = new Thread(this);
128            thread.setDaemon(true);
129            thread.start();
130        }
131
132        private boolean notifyEvent(TargetListener listener, Event event) {
133            if (listener.eventReceived(event) == true) {
134                return true;
135            } else if (event instanceof BreakpointEvent) {
136                return listener.breakpointReached((BreakpointEvent)event);
137            } else if (event instanceof ExceptionEvent) {
138                return listener.exceptionThrown((ExceptionEvent)event);
139            } else if (event instanceof StepEvent) {
140                return listener.stepCompleted((StepEvent)event);
141            } else if (event instanceof ClassPrepareEvent) {
142                return listener.classPrepared((ClassPrepareEvent)event);
143            } else if (event instanceof ClassUnloadEvent) {
144                return listener.classUnloaded((ClassUnloadEvent)event);
145            } else if (event instanceof MethodEntryEvent) {
146                return listener.methodEntered((MethodEntryEvent)event);
147            } else if (event instanceof MethodExitEvent) {
148                return listener.methodExited((MethodExitEvent)event);
149            } else if (event instanceof AccessWatchpointEvent) {
150                return listener.fieldAccessed((AccessWatchpointEvent)event);
151            } else if (event instanceof ModificationWatchpointEvent) {
152                return listener.fieldModified((ModificationWatchpointEvent)event);
153            } else if (event instanceof ThreadStartEvent) {
154                return listener.threadStarted((ThreadStartEvent)event);
155            } else if (event instanceof ThreadDeathEvent) {
156                return listener.threadDied((ThreadDeathEvent)event);
157            } else if (event instanceof VMStartEvent) {
158                return listener.vmStarted((VMStartEvent)event);
159            } else if (event instanceof VMDeathEvent) {
160                return listener.vmDied((VMDeathEvent)event);
161            } else if (event instanceof VMDisconnectEvent) {
162                return listener.vmDisconnected((VMDisconnectEvent)event);
163            } else {
164                throw new InternalError("Unknown event type: " + event.getClass());
165            }
166        }
167
168        private void traceSuspendPolicy(int policy) {
169            if (shouldTrace) {
170                switch (policy) {
171                case EventRequest.SUSPEND_NONE:
172                    traceln("JDI: runloop: suspend = SUSPEND_NONE");
173                    break;
174                case EventRequest.SUSPEND_ALL:
175                    traceln("JDI: runloop: suspend = SUSPEND_ALL");
176                    break;
177                case EventRequest.SUSPEND_EVENT_THREAD:
178                    traceln("JDI: runloop: suspend = SUSPEND_EVENT_THREAD");
179                    break;
180                }
181            }
182        }
183
184        public void run() {
185            boolean connected = true;
186            do {
187                try {
188                    EventSet set = vm.eventQueue().remove();
189                    traceSuspendPolicy(set.suspendPolicy());
190                    synchronized (listeners) {
191                        ListIterator iter = listeners.listIterator();
192                        while (iter.hasNext()) {
193                            TargetListener listener = (TargetListener)iter.next();
194                            traceln("JDI: runloop: listener = " + listener);
195                            if (listener.eventSetReceived(set) == true) {
196                                iter.remove();
197                            } else {
198                                Iterator jter = set.iterator();
199                                while (jter.hasNext()) {
200                                    Event event = (Event)jter.next();
201                                    traceln("JDI: runloop:    event = " + event.getClass());
202                                    if (event instanceof VMDisconnectEvent) {
203                                        connected = false;
204                                    }
205                                    if (notifyEvent(listener, event) == true) {
206                                        iter.remove();
207                                        break;
208                                    }
209                                }
210                                traceln("JDI: runloop:   end of events loop");
211                                if (listener.eventSetComplete(set) == true) {
212                                    iter.remove();
213                                }
214                            }
215                        traceln("JDI: runloop: end of listener");
216                        }
217                    }
218                } catch (InterruptedException e) {
219                }
220                traceln("JDI: runloop: end of outer loop");
221            } while (connected);
222        }
223    }
224
225    /**
226     * Constructor
227     */
228    public JDIScaffold() {
229    }
230
231    public void enableScaffoldTrace() {
232        this.shouldTrace = true;
233    }
234
235    public void disableScaffoldTrace() {
236        this.shouldTrace = false;
237    }
238
239
240    /*
241     * Test cases should implement tests in runTests and should
242     * initiate testing by calling run().
243     */
244    abstract protected void runTests() throws Exception;
245
246    final public void startTests() throws Exception {
247        try {
248            runTests();
249        } finally {
250            shutdown();
251        }
252    }
253
254    protected void println(String str) {
255        System.err.println(str);
256    }
257
258    protected void traceln(String str) {
259        if (shouldTrace) {
260            println(str);
261        }
262    }
263
264    private ArgInfo parseArgs(String args[]) {
265        ArgInfo argInfo = new ArgInfo();
266        for (int i = 0; i < args.length; i++) {
267            if (args[i].equals("-connect")) {
268                i++;
269                argInfo.connectorSpec = args[i];
270            } else if (args[i].equals("-trace")) {
271                i++;
272                argInfo.traceFlags = Integer.decode(args[i]).intValue();
273            } else if (args[i].startsWith("-J")) {
274                argInfo.targetVMArgs += (args[i].substring(2) + ' ');
275
276                /*
277                 * classpath can span two arguments so we need to handle
278                 * it specially.
279                 */
280                if (args[i].equals("-J-classpath")) {
281                    i++;
282                    argInfo.targetVMArgs += (args[i] + ' ');
283                }
284            } else {
285                argInfo.targetAppCommandLine += (args[i] + ' ');
286            }
287        }
288        return argInfo;
289    }
290
291    public void connect(String args[]) {
292        ArgInfo argInfo = parseArgs(args);
293
294        argInfo.targetVMArgs += VMConnection.getDebuggeeVMOptions();
295        connection = new VMConnection(argInfo.connectorSpec,
296                                      argInfo.traceFlags);
297        if (!connection.isLaunch()) {
298            throw new UnsupportedOperationException(
299                                 "Listening and Attaching not yet supported");
300        }
301
302        /*
303         * Add a listener to track VM start/death/disconnection and
304         * to update status fields accordingly.
305         */
306        addListener(new TargetAdapter() {
307                        public boolean vmStarted(VMStartEvent event) {
308                            traceln("JDI: listener1:  got VMStart");
309                            synchronized(JDIScaffold.this) {
310                                vmStartThread = event.thread();
311                                JDIScaffold.this.notifyAll();
312                            }
313                            return false;
314                        }
315
316                        public boolean vmDied(VMDeathEvent event) {
317                            traceln("JDI: listener1:  got VMDeath");
318                            synchronized(JDIScaffold.this) {
319                                vmDied = true;
320                                JDIScaffold.this.notifyAll();
321                            }
322                            return false;
323                        }
324
325                        public boolean vmDisconnected(VMDisconnectEvent event) {
326                            traceln("JDI: listener1:  got VMDisconnectedEvent");
327                            synchronized(JDIScaffold.this) {
328                                vmDisconnected = true;
329                                JDIScaffold.this.notifyAll();
330                            }
331                            return false;
332                        }
333                    });
334
335        if (connection.connector().name().equals("com.sun.jdi.CommandLineLaunch")) {
336            if (argInfo.targetVMArgs.length() > 0) {
337                if (connection.connectorArg("options").length() > 0) {
338                    throw new IllegalArgumentException("VM options in two places");
339                }
340                connection.setConnectorArg("options", argInfo.targetVMArgs);
341            }
342            if (argInfo.targetAppCommandLine.length() > 0) {
343                if (connection.connectorArg("main").length() > 0) {
344                    throw new IllegalArgumentException("Command line in two places");
345                }
346                connection.setConnectorArg("main", argInfo.targetAppCommandLine);
347            }
348        }
349
350        vm = connection.open();
351        requestManager = vm.eventRequestManager();
352        new EventHandler();
353    }
354
355
356    public VirtualMachine vm() {
357        return vm;
358    }
359
360    public EventRequestManager eventRequestManager() {
361        return requestManager;
362    }
363
364    public void addListener(TargetListener listener) {
365        listeners.add(listener);
366    }
367
368    public void removeListener(TargetListener listener) {
369        listeners.remove(listener);
370    }
371
372    public synchronized ThreadReference waitForVMStart() {
373        while ((vmStartThread == null) && !vmDisconnected) {
374            try {
375                wait();
376            } catch (InterruptedException e) {
377            }
378        }
379
380        if (vmStartThread == null) {
381            throw new VMDisconnectedException();
382        }
383
384        return vmStartThread;
385    }
386
387    public synchronized void waitForVMDeath() {
388        while (!vmDied && !vmDisconnected) {
389            try {
390                traceln("JDI: waitForVMDeath:  waiting");
391                wait();
392            } catch (InterruptedException e) {
393            }
394        }
395        traceln("JDI: waitForVMDeath:  done waiting");
396
397        if (!vmDied) {
398            throw new VMDisconnectedException();
399        }
400    }
401
402    public Event waitForRequestedEvent(final EventRequest request) {
403        class EventNotification {
404            Event event;
405            boolean disconnected = false;
406        }
407        final EventNotification en = new EventNotification();
408
409        TargetAdapter adapter = new TargetAdapter() {
410            public boolean eventReceived(Event event) {
411                if (request.equals(event.request())) {
412                    synchronized (en) {
413                        en.event = event;
414                        en.notifyAll();
415                    }
416                    return true;
417                } else if (event instanceof VMDisconnectEvent) {
418                    synchronized (en) {
419                        en.disconnected = true;
420                        en.notifyAll();
421                    }
422                    return true;
423                } else {
424                    return false;
425                }
426            }
427        };
428
429        addListener(adapter);
430
431        try {
432            synchronized (en) {
433                vm.resume();
434                while (!en.disconnected && (en.event == null)) {
435                    en.wait();
436                }
437            }
438        } catch (InterruptedException e) {
439            return null;
440        }
441
442        if (en.disconnected) {
443            throw new RuntimeException("VM Disconnected before requested event occurred");
444        }
445        return en.event;
446    }
447
448    private StepEvent doStep(ThreadReference thread, int gran, int depth) {
449        final StepRequest sr =
450                  requestManager.createStepRequest(thread, gran, depth);
451
452        sr.addClassExclusionFilter("java.*");
453        sr.addClassExclusionFilter("javax.*");
454        sr.addClassExclusionFilter("sun.*");
455        sr.addClassExclusionFilter("com.sun.*");
456        sr.addClassExclusionFilter("com.oracle.*");
457        sr.addClassExclusionFilter("oracle.*");
458        sr.addClassExclusionFilter("jdk.internal.*");
459        sr.addCountFilter(1);
460        sr.enable();
461        StepEvent retEvent = (StepEvent)waitForRequestedEvent(sr);
462        requestManager.deleteEventRequest(sr);
463        return retEvent;
464    }
465
466    public StepEvent stepIntoInstruction(ThreadReference thread) {
467        return doStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_INTO);
468    }
469
470    public StepEvent stepIntoLine(ThreadReference thread) {
471        return doStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_INTO);
472    }
473
474    public StepEvent stepOverInstruction(ThreadReference thread) {
475        return doStep(thread, StepRequest.STEP_MIN, StepRequest.STEP_OVER);
476    }
477
478    public StepEvent stepOverLine(ThreadReference thread) {
479        return doStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_OVER);
480    }
481
482    public StepEvent stepOut(ThreadReference thread) {
483        return doStep(thread, StepRequest.STEP_LINE, StepRequest.STEP_OUT);
484    }
485
486    public BreakpointEvent resumeTo(Location loc) {
487        final BreakpointRequest request =
488            requestManager.createBreakpointRequest(loc);
489        request.addCountFilter(1);
490        request.enable();
491        return (BreakpointEvent)waitForRequestedEvent(request);
492    }
493
494    public ReferenceType findReferenceType(String name) {
495        List rts = vm.classesByName(name);
496        Iterator iter = rts.iterator();
497        while (iter.hasNext()) {
498            ReferenceType rt = (ReferenceType)iter.next();
499            if (rt.name().equals(name)) {
500                return rt;
501            }
502        }
503        return null;
504    }
505
506    public Method findMethod(ReferenceType rt, String name, String signature) {
507        List methods = rt.methods();
508        Iterator iter = methods.iterator();
509        while (iter.hasNext()) {
510            Method method = (Method)iter.next();
511            if (method.name().equals(name) &&
512                method.signature().equals(signature)) {
513                return method;
514            }
515        }
516        return null;
517    }
518
519    public Location findLocation(ReferenceType rt, int lineNumber)
520                         throws AbsentInformationException {
521        List locs = rt.locationsOfLine(lineNumber);
522        if (locs.size() == 0) {
523            throw new IllegalArgumentException("Bad line number");
524        } else if (locs.size() > 1) {
525            throw new IllegalArgumentException("Line number has multiple locations");
526        }
527
528        return (Location)locs.get(0);
529    }
530
531    public BreakpointEvent resumeTo(String clsName, String methodName,
532                                         String methodSignature) {
533        ReferenceType rt = findReferenceType(clsName);
534        if (rt == null) {
535            rt = resumeToPrepareOf(clsName).referenceType();
536        }
537
538        Method method = findMethod(rt, methodName, methodSignature);
539        if (method == null) {
540            throw new IllegalArgumentException("Bad method name/signature");
541        }
542
543        return resumeTo(method.location());
544    }
545
546    public BreakpointEvent resumeTo(String clsName, int lineNumber) throws AbsentInformationException {
547        ReferenceType rt = findReferenceType(clsName);
548        if (rt == null) {
549            rt = resumeToPrepareOf(clsName).referenceType();
550        }
551
552        return resumeTo(findLocation(rt, lineNumber));
553    }
554
555    public ClassPrepareEvent resumeToPrepareOf(String className) {
556        final ClassPrepareRequest request =
557            requestManager.createClassPrepareRequest();
558        request.addClassFilter(className);
559        request.addCountFilter(1);
560        request.enable();
561        return (ClassPrepareEvent)waitForRequestedEvent(request);
562    }
563
564    public void resumeToVMDeath() {
565        // If we are very close to VM death, we might get a VM disconnect
566        // before resume is complete. In that case ignore any VMDisconnectException
567        // and let the waitForVMDeath to clean up.
568        try {
569            traceln("JDI: resumeToVMDeath:  resuming");
570            vm.resume();
571            traceln("JDI: resumeToVMDeath:  resumed");
572        } catch (VMDisconnectedException e) {
573            // clean up below
574        }
575        waitForVMDeath();
576    }
577
578    public void shutdown() {
579        shutdown(null);
580    }
581
582    public void shutdown(String message) {
583        if ((connection != null) && !vmDied) {
584            try {
585                connection.disposeVM();
586            } catch (VMDisconnectedException e) {
587                // Shutting down after the VM has gone away. This is
588                // not an error, and we just ignore it.
589            }
590        }
591        if (message != null) {
592            System.out.println(message);
593        }
594    }
595}
596