ThreadReferenceImpl.java revision 11099:678faa7d1a6a
1/*
2 * Copyright (c) 1998, 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.jdi;
27
28import com.sun.jdi.*;
29import com.sun.jdi.request.BreakpointRequest;
30import java.util.*;
31import java.lang.ref.WeakReference;
32
33public class ThreadReferenceImpl extends ObjectReferenceImpl
34             implements ThreadReference, VMListener {
35    static final int SUSPEND_STATUS_SUSPENDED = 0x1;
36    static final int SUSPEND_STATUS_BREAK = 0x2;
37
38    private int suspendedZombieCount = 0;
39
40    /*
41     * Some objects can only be created while a thread is suspended and are valid
42     * only while the thread remains suspended.  Examples are StackFrameImpl
43     * and MonitorInfoImpl.  When the thread resumes, these objects have to be
44     * marked as invalid so that their methods can throw
45     * InvalidStackFrameException if they are called.  To do this, such objects
46     * register themselves as listeners of the associated thread.  When the
47     * thread is resumed, its listeners are notified and mark themselves
48     * invalid.
49     * Also, note that ThreadReferenceImpl itself caches some info that
50     * is valid only as long as the thread is suspended.  When the thread
51     * is resumed, that cache must be purged.
52     * Lastly, note that ThreadReferenceImpl and its super, ObjectReferenceImpl
53     * cache some info that is only valid as long as the entire VM is suspended.
54     * If _any_ thread is resumed, this cache must be purged.  To handle this,
55     * both ThreadReferenceImpl and ObjectReferenceImpl register themselves as
56     * VMListeners so that they get notified when all threads are suspended and
57     * when any thread is resumed.
58     */
59
60    // This is cached for the life of the thread
61    private ThreadGroupReference threadGroup;
62
63    // This is cached only while this one thread is suspended.  Each time
64    // the thread is resumed, we abandon the current cache object and
65    // create a new initialized one.
66    private static class LocalCache {
67        JDWP.ThreadReference.Status status = null;
68        List<StackFrame> frames = null;
69        int framesStart = -1;
70        int framesLength = 0;
71        int frameCount = -1;
72        List<ObjectReference> ownedMonitors = null;
73        List<MonitorInfo> ownedMonitorsInfo = null;
74        ObjectReference contendedMonitor = null;
75        boolean triedCurrentContended = false;
76    }
77
78    /*
79     * The localCache instance var is set by resetLocalCache to an initialized
80     * object as shown above.  This occurs when the ThreadReference
81     * object is created, and when the mirrored thread is resumed.
82     * The fields are then filled in by the relevant methods as they
83     * are called.  A problem can occur if resetLocalCache is called
84     * (ie, a resume() is executed) at certain points in the execution
85     * of some of these methods - see 6751643.  To avoid this, each
86     * method that wants to use this cache must make a local copy of
87     * this variable and use that.  This means that each invocation of
88     * these methods will use a copy of the cache object that was in
89     * effect at the point that the copy was made; if a racy resume
90     * occurs, it won't affect the method's local copy.  This means that
91     * the values returned by these calls may not match the state of
92     * the debuggee at the time the caller gets the values.  EG,
93     * frameCount() is called and comes up with 5 frames.  But before
94     * it returns this, a resume of the debuggee thread is executed in a
95     * different debugger thread.  The thread is resumed and running at
96     * the time that the value 5 is returned.  Or even worse, the thread
97     * could be suspended again and have a different number of frames, eg, 24,
98     * but this call will still return 5.
99     */
100    private LocalCache localCache;
101
102    private void resetLocalCache() {
103        localCache = new LocalCache();
104    }
105
106    // This is cached only while all threads in the VM are suspended
107    // Yes, someone could change the name of a thread while it is suspended.
108    private static class Cache extends ObjectReferenceImpl.Cache {
109        String name = null;
110    }
111    protected ObjectReferenceImpl.Cache newCache() {
112        return new Cache();
113    }
114
115    // Listeners - synchronized on vm.state()
116    private List<WeakReference<ThreadListener>> listeners = new ArrayList<WeakReference<ThreadListener>>();
117
118
119    ThreadReferenceImpl(VirtualMachine aVm, long aRef) {
120        super(aVm,aRef);
121        resetLocalCache();
122        vm.state().addListener(this);
123    }
124
125    protected String description() {
126        return "ThreadReference " + uniqueID();
127    }
128
129    /*
130     * VMListener implementation
131     */
132    public boolean vmNotSuspended(VMAction action) {
133        if (action.resumingThread() == null) {
134            // all threads are being resumed
135            synchronized (vm.state()) {
136                processThreadAction(new ThreadAction(this,
137                                            ThreadAction.THREAD_RESUMABLE));
138            }
139
140        }
141
142        /*
143         * Othewise, only one thread is being resumed:
144         *   if it is us,
145         *      we have already done our processThreadAction to notify our
146         *      listeners when we processed the resume.
147         *   if it is not us,
148         *      we don't want to notify our listeners
149         *       because we are not being resumed.
150         */
151        return super.vmNotSuspended(action);
152    }
153
154    /**
155     * Note that we only cache the name string while the entire VM is suspended
156     * because the name can change via Thread.setName arbitrarily while this
157     * thread is running.
158     */
159    public String name() {
160        String name = null;
161        try {
162            Cache local = (Cache)getCache();
163
164            if (local != null) {
165                name = local.name;
166            }
167            if (name == null) {
168                name = JDWP.ThreadReference.Name.process(vm, this)
169                                                             .threadName;
170                if (local != null) {
171                    local.name = name;
172                }
173            }
174        } catch (JDWPException exc) {
175            throw exc.toJDIException();
176        }
177        return name;
178    }
179
180    /*
181     * Sends a command to the back end which is defined to do an
182     * implicit vm-wide resume.
183     */
184    PacketStream sendResumingCommand(CommandSender sender) {
185        synchronized (vm.state()) {
186            processThreadAction(new ThreadAction(this,
187                                        ThreadAction.THREAD_RESUMABLE));
188            return sender.send();
189        }
190    }
191
192    public void suspend() {
193        try {
194            JDWP.ThreadReference.Suspend.process(vm, this);
195        } catch (JDWPException exc) {
196            throw exc.toJDIException();
197        }
198        // Don't consider the thread suspended yet. On reply, notifySuspend()
199        // will be called.
200    }
201
202    public void resume() {
203        /*
204         * If it's a zombie, we can just update internal state without
205         * going to back end.
206         */
207        if (suspendedZombieCount > 0) {
208            suspendedZombieCount--;
209            return;
210        }
211
212        PacketStream stream;
213        synchronized (vm.state()) {
214            processThreadAction(new ThreadAction(this,
215                                      ThreadAction.THREAD_RESUMABLE));
216            stream = JDWP.ThreadReference.Resume.enqueueCommand(vm, this);
217        }
218        try {
219            JDWP.ThreadReference.Resume.waitForReply(vm, stream);
220        } catch (JDWPException exc) {
221            throw exc.toJDIException();
222        }
223    }
224
225    public int suspendCount() {
226        /*
227         * If it's a zombie, we maintain the count in the front end.
228         */
229        if (suspendedZombieCount > 0) {
230            return suspendedZombieCount;
231        }
232
233        try {
234            return JDWP.ThreadReference.SuspendCount.process(vm, this).suspendCount;
235        } catch (JDWPException exc) {
236            throw exc.toJDIException();
237        }
238    }
239
240    public void stop(ObjectReference throwable) throws InvalidTypeException {
241        validateMirror(throwable);
242        // Verify that the given object is a Throwable instance
243        List<ReferenceType> list = vm.classesByName("java.lang.Throwable");
244        ClassTypeImpl throwableClass = (ClassTypeImpl)list.get(0);
245        if ((throwable == null) ||
246            !throwableClass.isAssignableFrom(throwable)) {
247             throw new InvalidTypeException("Not an instance of Throwable");
248        }
249
250        try {
251            JDWP.ThreadReference.Stop.process(vm, this,
252                                         (ObjectReferenceImpl)throwable);
253        } catch (JDWPException exc) {
254            throw exc.toJDIException();
255        }
256    }
257
258    public void interrupt() {
259        try {
260            JDWP.ThreadReference.Interrupt.process(vm, this);
261        } catch (JDWPException exc) {
262            throw exc.toJDIException();
263        }
264    }
265
266    private JDWP.ThreadReference.Status jdwpStatus() {
267        LocalCache snapshot = localCache;
268        JDWP.ThreadReference.Status myStatus = snapshot.status;
269        try {
270             if (myStatus == null) {
271                 myStatus = JDWP.ThreadReference.Status.process(vm, this);
272                if ((myStatus.suspendStatus & SUSPEND_STATUS_SUSPENDED) != 0) {
273                    // thread is suspended, we can cache the status.
274                    snapshot.status = myStatus;
275                }
276            }
277         } catch (JDWPException exc) {
278            throw exc.toJDIException();
279        }
280        return myStatus;
281    }
282
283    public int status() {
284        return jdwpStatus().threadStatus;
285    }
286
287    public boolean isSuspended() {
288        return ((suspendedZombieCount > 0) ||
289                ((jdwpStatus().suspendStatus & SUSPEND_STATUS_SUSPENDED) != 0));
290    }
291
292    public boolean isAtBreakpoint() {
293        /*
294         * TO DO: This fails to take filters into account.
295         */
296        try {
297            StackFrame frame = frame(0);
298            Location location = frame.location();
299            List<BreakpointRequest> requests = vm.eventRequestManager().breakpointRequests();
300            Iterator<BreakpointRequest> iter = requests.iterator();
301            while (iter.hasNext()) {
302                BreakpointRequest request = iter.next();
303                if (location.equals(request.location())) {
304                    return true;
305                }
306            }
307            return false;
308        } catch (IndexOutOfBoundsException iobe) {
309            return false;  // no frames on stack => not at breakpoint
310        } catch (IncompatibleThreadStateException itse) {
311            // Per the javadoc, not suspended => return false
312            return false;
313        }
314    }
315
316    public ThreadGroupReference threadGroup() {
317        /*
318         * Thread group can't change, so it's cached once and for all.
319         */
320        if (threadGroup == null) {
321            try {
322                threadGroup = JDWP.ThreadReference.ThreadGroup.
323                    process(vm, this).group;
324            } catch (JDWPException exc) {
325                throw exc.toJDIException();
326            }
327        }
328        return threadGroup;
329    }
330
331    public int frameCount() throws IncompatibleThreadStateException  {
332        LocalCache snapshot = localCache;
333        try {
334            if (snapshot.frameCount == -1) {
335                snapshot.frameCount = JDWP.ThreadReference.FrameCount
336                                          .process(vm, this).frameCount;
337            }
338        } catch (JDWPException exc) {
339            switch (exc.errorCode()) {
340            case JDWP.Error.THREAD_NOT_SUSPENDED:
341            case JDWP.Error.INVALID_THREAD:   /* zombie */
342                throw new IncompatibleThreadStateException();
343            default:
344                throw exc.toJDIException();
345            }
346        }
347        return snapshot.frameCount;
348    }
349
350    public List<StackFrame> frames() throws IncompatibleThreadStateException  {
351        return privateFrames(0, -1);
352    }
353
354    public StackFrame frame(int index) throws IncompatibleThreadStateException  {
355        List<StackFrame> list = privateFrames(index, 1);
356        return list.get(0);
357    }
358
359    /**
360     * Is the requested subrange within what has been retrieved?
361     * local is known to be non-null.  Should only be called from
362     * a sync method.
363     */
364    private boolean isSubrange(LocalCache snapshot,
365                               int start, int length) {
366        if (start < snapshot.framesStart) {
367            return false;
368        }
369        if (length == -1) {
370            return (snapshot.framesLength == -1);
371        }
372        if (snapshot.framesLength == -1) {
373            if ((start + length) > (snapshot.framesStart +
374                                    snapshot.frames.size())) {
375                throw new IndexOutOfBoundsException();
376            }
377            return true;
378        }
379        return ((start + length) <= (snapshot.framesStart + snapshot.framesLength));
380    }
381
382    public List<StackFrame> frames(int start, int length)
383                              throws IncompatibleThreadStateException  {
384        if (length < 0) {
385            throw new IndexOutOfBoundsException(
386                "length must be greater than or equal to zero");
387        }
388        return privateFrames(start, length);
389    }
390
391    /**
392     * Private version of frames() allows "-1" to specify all
393     * remaining frames.
394     */
395    synchronized private List<StackFrame> privateFrames(int start, int length)
396                              throws IncompatibleThreadStateException  {
397
398        // Lock must be held while creating stack frames so if that two threads
399        // do this at the same time, one won't clobber the subset created by the other.
400        LocalCache snapshot = localCache;
401        try {
402            if (snapshot.frames == null || !isSubrange(snapshot, start, length)) {
403                JDWP.ThreadReference.Frames.Frame[] jdwpFrames
404                    = JDWP.ThreadReference.Frames.
405                    process(vm, this, start, length).frames;
406                int count = jdwpFrames.length;
407                snapshot.frames = new ArrayList<StackFrame>(count);
408
409                for (int i = 0; i<count; i++) {
410                    if (jdwpFrames[i].location == null) {
411                        throw new InternalException("Invalid frame location");
412                    }
413                    StackFrame frame = new StackFrameImpl(vm, this,
414                                                          jdwpFrames[i].frameID,
415                                                          jdwpFrames[i].location);
416                    // Add to the frame list
417                    snapshot.frames.add(frame);
418                }
419                snapshot.framesStart = start;
420                snapshot.framesLength = length;
421                return Collections.unmodifiableList(snapshot.frames);
422            } else {
423                int fromIndex = start - snapshot.framesStart;
424                int toIndex;
425                if (length == -1) {
426                    toIndex = snapshot.frames.size() - fromIndex;
427                } else {
428                    toIndex = fromIndex + length;
429                }
430                return Collections.unmodifiableList(snapshot.frames.subList(fromIndex, toIndex));
431            }
432        } catch (JDWPException exc) {
433            switch (exc.errorCode()) {
434            case JDWP.Error.THREAD_NOT_SUSPENDED:
435            case JDWP.Error.INVALID_THREAD:   /* zombie */
436                throw new IncompatibleThreadStateException();
437            default:
438                throw exc.toJDIException();
439            }
440        }
441    }
442
443    public List<ObjectReference> ownedMonitors()  throws IncompatibleThreadStateException  {
444        LocalCache snapshot = localCache;
445        try {
446            if (snapshot.ownedMonitors == null) {
447                snapshot.ownedMonitors = Arrays.asList(
448                                 (ObjectReference[])JDWP.ThreadReference.OwnedMonitors.
449                                         process(vm, this).owned);
450                if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {
451                    vm.printTrace(description() +
452                                  " temporarily caching owned monitors"+
453                                  " (count = " + snapshot.ownedMonitors.size() + ")");
454                }
455            }
456        } catch (JDWPException exc) {
457            switch (exc.errorCode()) {
458            case JDWP.Error.THREAD_NOT_SUSPENDED:
459            case JDWP.Error.INVALID_THREAD:   /* zombie */
460                throw new IncompatibleThreadStateException();
461            default:
462                throw exc.toJDIException();
463            }
464        }
465        return snapshot.ownedMonitors;
466    }
467
468    public ObjectReference currentContendedMonitor()
469                              throws IncompatibleThreadStateException  {
470        LocalCache snapshot = localCache;
471        try {
472            if (snapshot.contendedMonitor == null &&
473                !snapshot.triedCurrentContended) {
474                snapshot.contendedMonitor = JDWP.ThreadReference.CurrentContendedMonitor.
475                    process(vm, this).monitor;
476                snapshot.triedCurrentContended = true;
477                if ((snapshot.contendedMonitor != null) &&
478                    ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0)) {
479                    vm.printTrace(description() +
480                                  " temporarily caching contended monitor"+
481                                  " (id = " + snapshot.contendedMonitor.uniqueID() + ")");
482                }
483            }
484        } catch (JDWPException exc) {
485            switch (exc.errorCode()) {
486            case JDWP.Error.THREAD_NOT_SUSPENDED:
487            case JDWP.Error.INVALID_THREAD:   /* zombie */
488                throw new IncompatibleThreadStateException();
489            default:
490                throw exc.toJDIException();
491            }
492        }
493        return snapshot.contendedMonitor;
494    }
495
496    public List<MonitorInfo> ownedMonitorsAndFrames()  throws IncompatibleThreadStateException  {
497        LocalCache snapshot = localCache;
498        try {
499            if (snapshot.ownedMonitorsInfo == null) {
500                JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.monitor[] minfo;
501                minfo = JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.process(vm, this).owned;
502
503                snapshot.ownedMonitorsInfo = new ArrayList<MonitorInfo>(minfo.length);
504
505                for (int i=0; i < minfo.length; i++) {
506                    JDWP.ThreadReference.OwnedMonitorsStackDepthInfo.monitor mi =
507                                                                         minfo[i];
508                    MonitorInfo mon = new MonitorInfoImpl(vm, minfo[i].monitor, this, minfo[i].stack_depth);
509                    snapshot.ownedMonitorsInfo.add(mon);
510                }
511
512                if ((vm.traceFlags & VirtualMachine.TRACE_OBJREFS) != 0) {
513                    vm.printTrace(description() +
514                                  " temporarily caching owned monitors"+
515                                  " (count = " + snapshot.ownedMonitorsInfo.size() + ")");
516                    }
517                }
518
519        } catch (JDWPException exc) {
520            switch (exc.errorCode()) {
521            case JDWP.Error.THREAD_NOT_SUSPENDED:
522            case JDWP.Error.INVALID_THREAD:   /* zombie */
523                throw new IncompatibleThreadStateException();
524            default:
525                throw exc.toJDIException();
526            }
527        }
528        return snapshot.ownedMonitorsInfo;
529    }
530
531    public void popFrames(StackFrame frame) throws IncompatibleThreadStateException {
532        // Note that interface-wise this functionality belongs
533        // here in ThreadReference, but implementation-wise it
534        // belongs in StackFrame, so we just forward it.
535        if (!frame.thread().equals(this)) {
536            throw new IllegalArgumentException("frame does not belong to this thread");
537        }
538        if (!vm.canPopFrames()) {
539            throw new UnsupportedOperationException(
540                "target does not support popping frames");
541        }
542        ((StackFrameImpl)frame).pop();
543    }
544
545    public void forceEarlyReturn(Value  returnValue) throws InvalidTypeException,
546                                                            ClassNotLoadedException,
547                                             IncompatibleThreadStateException {
548        if (!vm.canForceEarlyReturn()) {
549            throw new UnsupportedOperationException(
550                "target does not support the forcing of a method to return early");
551        }
552
553        validateMirrorOrNull(returnValue);
554
555        StackFrameImpl sf;
556        try {
557           sf = (StackFrameImpl)frame(0);
558        } catch (IndexOutOfBoundsException exc) {
559           throw new InvalidStackFrameException("No more frames on the stack");
560        }
561        sf.validateStackFrame();
562        MethodImpl meth = (MethodImpl)sf.location().method();
563        ValueImpl convertedValue  = ValueImpl.prepareForAssignment(returnValue,
564                                                                   meth.getReturnValueContainer());
565
566        try {
567            JDWP.ThreadReference.ForceEarlyReturn.process(vm, this, convertedValue);
568        } catch (JDWPException exc) {
569            switch (exc.errorCode()) {
570            case JDWP.Error.OPAQUE_FRAME:
571                throw new NativeMethodException();
572            case JDWP.Error.THREAD_NOT_SUSPENDED:
573                throw new IncompatibleThreadStateException(
574                         "Thread not suspended");
575            case JDWP.Error.THREAD_NOT_ALIVE:
576                throw new IncompatibleThreadStateException(
577                                     "Thread has not started or has finished");
578            case JDWP.Error.NO_MORE_FRAMES:
579                throw new InvalidStackFrameException(
580                         "No more frames on the stack");
581            default:
582                throw exc.toJDIException();
583            }
584        }
585    }
586
587    public String toString() {
588        return "instance of " + referenceType().name() +
589               "(name='" + name() + "', " + "id=" + uniqueID() + ")";
590    }
591
592    byte typeValueKey() {
593        return JDWP.Tag.THREAD;
594    }
595
596    void addListener(ThreadListener listener) {
597        synchronized (vm.state()) {
598            listeners.add(new WeakReference<ThreadListener>(listener));
599        }
600    }
601
602    void removeListener(ThreadListener listener) {
603        synchronized (vm.state()) {
604            Iterator<WeakReference<ThreadListener>> iter = listeners.iterator();
605            while (iter.hasNext()) {
606                WeakReference<ThreadListener> ref = iter.next();
607                if (listener.equals(ref.get())) {
608                    iter.remove();
609                    break;
610                }
611            }
612        }
613    }
614
615    /**
616     * Propagate the thread state change information
617     * to registered listeners.
618     * Must be entered while synchronized on vm.state()
619     */
620    private void processThreadAction(ThreadAction action) {
621        synchronized (vm.state()) {
622            Iterator<WeakReference<ThreadListener>> iter = listeners.iterator();
623            while (iter.hasNext()) {
624                WeakReference<ThreadListener> ref = iter.next();
625                ThreadListener listener = ref.get();
626                if (listener != null) {
627                    switch (action.id()) {
628                        case ThreadAction.THREAD_RESUMABLE:
629                            if (!listener.threadResumable(action)) {
630                                iter.remove();
631                            }
632                            break;
633                    }
634                } else {
635                    // Listener is unreachable; clean up
636                    iter.remove();
637                }
638            }
639
640            // Discard our local cache
641            resetLocalCache();
642        }
643    }
644}
645