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