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