1 /*
  2  * Copyright (c) 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.  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 package jdk.internal.vm;
 26 
 27 import java.util.Arrays;
 28 import java.util.stream.Stream;
 29 
 30 /**
 31  * Represents a snapshot of information about a Thread.
 32  */
 33 class ThreadSnapshot {
 34     private static final StackTraceElement[] EMPTY_STACK = new StackTraceElement[0];
 35     private static final ThreadLock[] EMPTY_LOCKS = new ThreadLock[0];
 36 
 37     private String name;
 38     private int threadStatus;
 39     private StackTraceElement[] stackTrace;
 40     private ThreadLock[] locks;
 41 
 42     // called by the VM
 43     private ThreadSnapshot(StackTraceElement[] stackTrace,
 44                            ThreadLock[] locks,
 45                            String name,
 46                            int threadStatus) {
 47         this.stackTrace = stackTrace;
 48         this.locks = locks;
 49         this.name = name;
 50         this.threadStatus = threadStatus;
 51     }
 52 
 53     /**
 54      * Take a snapshot of a Thread to get all information about the thread.
 55      */
 56     static ThreadSnapshot of(Thread thread) {
 57         ThreadSnapshot snapshot = create(thread, true);
 58         if (snapshot.stackTrace == null) {
 59             snapshot.stackTrace = EMPTY_STACK;
 60         }
 61         if (snapshot.locks == null) {
 62             snapshot.locks = EMPTY_LOCKS;
 63         }
 64         return snapshot;
 65     }
 66 
 67     /**
 68      * Returns the thread name.
 69      */
 70     String threadName() {
 71         return name;
 72     }
 73 
 74     /**
 75      * Returns the thread state.
 76      */
 77     Thread.State threadState() {
 78         // is this valid for virtual threads
 79         return jdk.internal.misc.VM.toThreadState(threadStatus);
 80     }
 81 
 82     /**
 83      * Returns the thread stack trace.
 84      */
 85     StackTraceElement[] stackTrace() {
 86         return stackTrace;
 87     }
 88 
 89     /**
 90      * Returns the thread's parkBlocker.
 91      */
 92     Object parkBlocker() {
 93         return findLockObject(0, LockType.PARKING_TO_WAIT)
 94                 .findAny()
 95                 .orElse(null);
 96     }
 97 
 98     /**
 99      * Returns the object that the thread is blocked on.
100      * @throws IllegalStateException if not in the blocked state
101      */
102     Object blockedOn() {
103         if (threadState() != Thread.State.BLOCKED) {
104             throw new IllegalStateException();
105         }
106         return findLockObject(0, LockType.WAITING_TO_LOCK)
107                 .findAny()
108                 .orElse(null);
109     }
110 
111     /**
112      * Returns the object that the thread is waiting on.
113      * @throws IllegalStateException if not in the waiting state
114      */
115     Object waitingOn() {
116         if (threadState() != Thread.State.WAITING
117                 && threadState() != Thread.State.TIMED_WAITING) {
118             throw new IllegalStateException();
119         }
120         return findLockObject(0, LockType.WAITING_ON)
121                 .findAny()
122                 .orElse(null);
123     }
124 
125     /**
126      * Returns true if the thread owns any object monitors.
127      */
128     boolean ownsMonitors() {
129         return Arrays.stream(locks)
130                 .anyMatch(lock -> lock.type == LockType.LOCKED);
131     }
132 
133     /**
134      * Returns the objects that the thread locked at the given depth.
135      */
136     Stream<Object> ownedMonitorsAt(int depth) {
137         return findLockObject(depth, LockType.LOCKED);
138     }
139 
140     private Stream<Object> findLockObject(int depth, LockType type) {
141         return Arrays.stream(locks)
142                 .filter(lock -> lock.depth == depth
143                         && lock.type() == type
144                         && lock.lockObject() != null)
145                 .map(ThreadLock::lockObject);
146     }
147 
148     /**
149      * Represents information about a locking operation.
150      */
151     private enum LockType {
152         // Park blocker
153         PARKING_TO_WAIT,
154         // Lock object is a class of the eliminated monitor
155         ELEMINATED_SCALAR_REPLACED,
156         ELEMINATED_MONITOR,
157         LOCKED,
158         WAITING_TO_LOCK,
159         WAITING_ON,
160         WAITING_TO_RELOCK,
161         // No corresponding stack frame, depth is always == -1
162         OWNABLE_SYNCHRONIZER
163     }
164 
165     /**
166      * Represents a locking operation of a thread at a specific stack depth.
167      */
168     private record ThreadLock(int depth, LockType type, Object obj) {
169         private static final LockType[] lockTypeValues = LockType.values(); // cache
170 
171         // called by the VM
172         private ThreadLock(int depth, int typeOrdinal, Object obj) {
173             this(depth, lockTypeValues[typeOrdinal], obj);
174         }
175 
176         Object lockObject() {
177             if (type == LockType.ELEMINATED_SCALAR_REPLACED) {
178                 // we have no lock object, lock contains lock class
179                 return null;
180             }
181             return obj;
182         }
183     }
184 
185     private static native ThreadSnapshot create(Thread thread, boolean withLocks);
186 }