1 /*
  2  * Copyright (c) 2022, 2023, 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 
 24 #include <jni.h>
 25 #include <pthread.h>
 26 #include <stdio.h>
 27 #include <stdlib.h>
 28 #include <unistd.h>
 29 
 30 #ifdef __cplusplus
 31 extern "C" {
 32 #endif
 33 
 34 static JavaVM* jvm;
 35 static pthread_t attacher;
 36 
 37 #define die(x) do { printf("%s:%s\n",x , __func__); perror(x); exit(EXIT_FAILURE); } while (0)
 38 
 39 static void check_exception(JNIEnv* env, const char* msg) {
 40   if ((*env)->ExceptionCheck(env)) {
 41     fprintf(stderr, "Error: %s", msg);
 42     exit(-1);
 43   }
 44 }
 45 
 46 #define check(env, what, msg)                      \
 47   check_exception((env), (msg));                   \
 48   do {                                             \
 49     if ((what) == 0) {                             \
 50       fprintf(stderr, #what "is null: %s", (msg)); \
 51       exit(-2);                                    \
 52     }                                              \
 53   } while (0)
 54 
 55 static jobject create_object(JNIEnv* env) {
 56   jclass clazz = (*env)->FindClass(env, "java/lang/Object");
 57   check(env, clazz, "No class");
 58 
 59   jmethodID constructor = (*env)->GetMethodID(env, clazz, "<init>", "()V");
 60   check(env, constructor, "No constructor");
 61 
 62   jobject obj = (*env)->NewObject(env, clazz, constructor);
 63   check(env, constructor, "No object");
 64 
 65   return obj;
 66 }
 67 
 68 static void system_gc(JNIEnv* env) {
 69   jclass clazz = (*env)->FindClass(env, "java/lang/System");
 70   check(env, clazz, "No class");
 71 
 72   jmethodID method = (*env)->GetStaticMethodID(env, clazz, "gc", "()V");
 73   check(env, method, "No method");
 74 
 75   (*env)->CallStaticVoidMethod(env, clazz, method);
 76   check_exception(env, "Calling System.gc()");
 77 }
 78 
 79 static void thread_dump_with_locked_monitors(JNIEnv* env) {
 80   jclass ManagementFactoryClass = (*env)->FindClass(env, "java/lang/management/ManagementFactory");
 81   check(env, ManagementFactoryClass, "No ManagementFactory class");
 82 
 83   jmethodID getThreadMXBeanMethod = (*env)->GetStaticMethodID(env, ManagementFactoryClass, "getThreadMXBean", "()Ljava/lang/management/ThreadMXBean;");
 84   check(env, getThreadMXBeanMethod, "No getThreadMXBean method");
 85 
 86   jobject threadBean = (*env)->CallStaticObjectMethod(env, ManagementFactoryClass, getThreadMXBeanMethod);
 87   check(env, threadBean, "Calling getThreadMXBean()");
 88 
 89   jclass ThreadMXBeanClass = (*env)->FindClass(env, "java/lang/management/ThreadMXBean");
 90   check(env, ThreadMXBeanClass, "No ThreadMXBean class");
 91 
 92   jmethodID dumpAllThreadsMethod = (*env)->GetMethodID(env, ThreadMXBeanClass, "dumpAllThreads", "(ZZ)[Ljava/lang/management/ThreadInfo;");
 93   check(env, dumpAllThreadsMethod, "No dumpAllThreads method");
 94 
 95   // The 'lockedMonitors == true' is what causes the monitor with a dead object to be examined.
 96   jobject array = (*env)->CallObjectMethod(env, threadBean, dumpAllThreadsMethod, JNI_TRUE /* lockedMonitors */, JNI_FALSE /* lockedSynchronizers*/);
 97   check(env, array, "Calling dumpAllThreads(true, false)");
 98 }
 99 
100 static void create_monitor_with_dead_object(JNIEnv* env) {
101   jobject obj = create_object(env);
102 
103   if ((*env)->MonitorEnter(env, obj) != 0) die("MonitorEnter");
104 
105   // Drop the last strong reference to the object associated with the monitor.
106   // The monitor only keeps a weak reference to the object.
107   (*env)->DeleteLocalRef(env, obj);
108 
109   // Let the GC clear the weak reference to the object.
110   system_gc(env);
111 }
112 
113 static void* create_monitor_with_dead_object_in_thread(void* arg) {
114   JNIEnv* env;
115   int res = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);
116   if (res != JNI_OK) die("AttachCurrentThread");
117 
118   // Make the correct incantation to create a monitor with a dead object.
119   create_monitor_with_dead_object(env);
120 
121   // DetachCurrentThread will try to unlock held monitors. This has been a
122   // source of at least two bugs:
123   // - When the object reference in the monitor was cleared, the monitor
124   //   iterator code would skip it, preventing it from being unlocked when
125   //   the owner thread detached, leaving it lingering in the system.
126   // - When the monitor iterator API was rewritten the code was changed to
127   //   assert that we didn't have "owned" monitors with dead objects. This
128   //   test provokes that situation and that asserts.
129   if ((*jvm)->DetachCurrentThread(jvm) != JNI_OK) die("DetachCurrentThread");
130 
131   return NULL;
132 }
133 
134 static void* create_monitor_with_dead_object_and_dump_threads_in_thread(void* arg) {
135   JNIEnv* env;
136   int res = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);
137   if (res != JNI_OK) die("AttachCurrentThread");
138 
139   // Make the correct incantation to create a monitor with a dead object.
140   create_monitor_with_dead_object(env);
141 
142   // Perform a thread dump that checks for all thread's monitors.
143   // That code didn't expect the monitor iterators to return monitors
144   // with dead objects and therefore asserted/crashed.
145   thread_dump_with_locked_monitors(env);
146 
147   if ((*jvm)->DetachCurrentThread(jvm) != JNI_OK) die("DetachCurrentThread");
148 
149   return NULL;
150 }
151 
152 JNIEXPORT void JNICALL Java_MonitorWithDeadObjectTest_createMonitorWithDeadObject(JNIEnv* env, jclass jc) {
153   void* ret;
154 
155   (*env)->GetJavaVM(env, &jvm);
156 
157   if (pthread_create(&attacher, NULL, create_monitor_with_dead_object_in_thread, NULL) != 0) die("pthread_create");
158   if (pthread_join(attacher, &ret) != 0) die("pthread_join");
159 }
160 
161 JNIEXPORT void JNICALL Java_MonitorWithDeadObjectTest_createMonitorWithDeadObjectDumpThreadsBeforeDetach(JNIEnv* env, jclass jc) {
162   void* ret;
163 
164   (*env)->GetJavaVM(env, &jvm);
165 
166   if (pthread_create(&attacher, NULL, create_monitor_with_dead_object_and_dump_threads_in_thread, NULL) != 0) die("pthread_create");
167   if (pthread_join(attacher, &ret) != 0) die("pthread_join");
168 }
169 
170 #ifdef __cplusplus
171 }
172 #endif