Skip to content
This repository was archived by the owner on Feb 2, 2023. It is now read-only.

Commit 4cf46e9

Browse files
Ekaterina VergizovaYuri Nesterenko
authored andcommitted
8238676: jni crashes on accessing it from process exit hook
Reviewed-by: yan Backport-of: c42de93
1 parent ad457af commit 4cf46e9

File tree

4 files changed

+220
-9
lines changed

4 files changed

+220
-9
lines changed

make/test/JtregNativeHotspot.gmk

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -879,7 +879,7 @@ endif
879879
ifeq ($(call isTargetOs, windows), true)
880880
BUILD_HOTSPOT_JTREG_EXECUTABLES_CFLAGS_exeFPRegs := -MT
881881
BUILD_HOTSPOT_JTREG_EXCLUDE += exesigtest.c libterminatedThread.c
882-
882+
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libatExit := jvm.lib
883883
else
884884
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libbootclssearch_agent += -lpthread
885885
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libsystemclssearch_agent += -lpthread
@@ -1514,6 +1514,7 @@ else
15141514
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libgetphase001 += -lpthread
15151515
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libgetphase002 += -lpthread
15161516
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libterminatedThread += -lpthread
1517+
BUILD_HOTSPOT_JTREG_LIBRARIES_LIBS_libatExit += -ljvm
15171518
endif
15181519

15191520
# This evaluation is expensive and should only be done if this target was

src/hotspot/share/prims/jni.cpp

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3844,7 +3844,8 @@ extern const struct JNIInvokeInterface_ jni_InvokeInterface;
38443844

38453845
// Global invocation API vars
38463846
volatile int vm_created = 0;
3847-
// Indicate whether it is safe to recreate VM
3847+
// Indicate whether it is safe to recreate VM. Recreation is only
3848+
// possible after a failed initial creation attempt in some cases.
38483849
volatile int safe_to_recreate_vm = 1;
38493850
struct JavaVM_ main_vm = {&jni_InvokeInterface};
38503851

@@ -3914,8 +3915,14 @@ static jint JNI_CreateJavaVM_inner(JavaVM **vm, void **penv, void *args) {
39143915
if (Atomic::xchg(1, &vm_created) == 1) {
39153916
return JNI_EEXIST; // already created, or create attempt in progress
39163917
}
3918+
3919+
// If a previous creation attempt failed but can be retried safely,
3920+
// then safe_to_recreate_vm will have been reset to 1 after being
3921+
// cleared here. If a previous creation attempt succeeded and we then
3922+
// destroyed that VM, we will be prevented from trying to recreate
3923+
// the VM in the same process, as the value will still be 0.
39173924
if (Atomic::xchg(0, &safe_to_recreate_vm) == 0) {
3918-
return JNI_ERR; // someone tried and failed and retry not allowed.
3925+
return JNI_ERR;
39193926
}
39203927

39213928
assert(vm_created == 1, "vm_created is true during the creation");
@@ -4108,9 +4115,14 @@ static jint attach_current_thread(JavaVM *vm, void **penv, void *_args, bool dae
41084115

41094116
Thread* t = Thread::current_or_null();
41104117
if (t != NULL) {
4111-
// If the thread has been attached this operation is a no-op
4112-
*(JNIEnv**)penv = ((JavaThread*) t)->jni_environment();
4113-
return JNI_OK;
4118+
// If executing from an atexit hook we may be in the VMThread.
4119+
if (t->is_Java_thread()) {
4120+
// If the thread has been attached this operation is a no-op
4121+
*(JNIEnv**)penv = ((JavaThread*) t)->jni_environment();
4122+
return JNI_OK;
4123+
} else {
4124+
return JNI_ERR;
4125+
}
41144126
}
41154127

41164128
// Create a thread and mark it as attaching so it will be skipped by the
@@ -4209,7 +4221,7 @@ static jint attach_current_thread(JavaVM *vm, void **penv, void *_args, bool dae
42094221
jint JNICALL jni_AttachCurrentThread(JavaVM *vm, void **penv, void *_args) {
42104222
HOTSPOT_JNI_ATTACHCURRENTTHREAD_ENTRY(vm, penv, _args);
42114223
if (vm_created == 0) {
4212-
HOTSPOT_JNI_ATTACHCURRENTTHREAD_RETURN((uint32_t) JNI_ERR);
4224+
HOTSPOT_JNI_ATTACHCURRENTTHREAD_RETURN((uint32_t) JNI_ERR);
42134225
return JNI_ERR;
42144226
}
42154227

@@ -4222,18 +4234,30 @@ jint JNICALL jni_AttachCurrentThread(JavaVM *vm, void **penv, void *_args) {
42224234

42234235
jint JNICALL jni_DetachCurrentThread(JavaVM *vm) {
42244236
HOTSPOT_JNI_DETACHCURRENTTHREAD_ENTRY(vm);
4237+
if (vm_created == 0) {
4238+
HOTSPOT_JNI_DETACHCURRENTTHREAD_RETURN(JNI_ERR);
4239+
return JNI_ERR;
4240+
}
42254241

42264242
JNIWrapper("DetachCurrentThread");
42274243

4244+
Thread* current = Thread::current_or_null();
4245+
42284246
// If the thread has already been detached the operation is a no-op
4229-
if (Thread::current_or_null() == NULL) {
4247+
if (current == NULL) {
42304248
HOTSPOT_JNI_DETACHCURRENTTHREAD_RETURN(JNI_OK);
42314249
return JNI_OK;
42324250
}
42334251

4252+
// If executing from an atexit hook we may be in the VMThread.
4253+
if (!current->is_Java_thread()) {
4254+
HOTSPOT_JNI_DETACHCURRENTTHREAD_RETURN((uint32_t) JNI_ERR);
4255+
return JNI_ERR;
4256+
}
4257+
42344258
VM_Exit::block_if_vm_exited();
42354259

4236-
JavaThread* thread = JavaThread::current();
4260+
JavaThread* thread = (JavaThread*) current;
42374261
if (thread->has_last_Java_frame()) {
42384262
HOTSPOT_JNI_DETACHCURRENTTHREAD_RETURN((uint32_t) JNI_ERR);
42394263
// Can't detach a thread that's running java, that can't work.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright (c) 2020, 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+
import jdk.test.lib.process.OutputAnalyzer;
24+
import jdk.test.lib.process.ProcessTools;
25+
26+
/*
27+
* @test
28+
* @bug 8238676
29+
* @summary Check that attempting to use the JNI invocation API from an
30+
* atexit handler fails as expected without crashing.
31+
*
32+
* @library /test/lib
33+
* @run main/othervm/native TestAtExit
34+
*/
35+
36+
public class TestAtExit {
37+
38+
// Using a nested class that invokes an enclosing method makes it
39+
// easier to setup and use the native library.
40+
static class Tester {
41+
static {
42+
System.loadLibrary("atExit");
43+
}
44+
45+
// Record the fact we are using System.exit for termination
46+
static native void setUsingSystemExit();
47+
48+
public static void main(String[] args) throws Exception {
49+
if (args.length > 0) {
50+
setUsingSystemExit();
51+
System.exit(0);
52+
}
53+
}
54+
}
55+
56+
public static void main(String[] args) throws Exception {
57+
// We mustn't load Tester in this VM so we exec by name.
58+
String main = "TestAtExit$Tester";
59+
60+
String jlp = "-Djava.library.path=" + System.getProperty("test.nativepath");
61+
// First run will terminate via DestroyJavaVM
62+
OutputAnalyzer output = ProcessTools.executeTestJvm(jlp, main);
63+
output.shouldNotContain("Unexpected");
64+
output.shouldHaveExitValue(0);
65+
output.reportDiagnosticSummary();
66+
67+
// Second run will terminate via System.exit()
68+
output = ProcessTools.executeTestJvm(jlp, main, "doExit");
69+
output.shouldNotContain("Unexpected");
70+
output.shouldHaveExitValue(0);
71+
output.reportDiagnosticSummary();
72+
}
73+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright (c) 2020, 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 <stdio.h>
25+
#include <stdlib.h>
26+
27+
#include "jni.h"
28+
29+
static JavaVM *jvm;
30+
31+
static const char* jni_error_code(int ret) {
32+
switch(ret) {
33+
case JNI_OK: return "JNI_OK";
34+
case JNI_ERR: return "JNI_ERR";
35+
case JNI_EDETACHED: return "JNI_EDETACHED";
36+
case JNI_EVERSION: return "JNI_EVERSION";
37+
case JNI_ENOMEM: return "JNI_ENOMEM";
38+
case JNI_EEXIST: return "JNI_EEXIST";
39+
case JNI_EINVAL: return "JNI_EINVAL";
40+
default: return "Invalid JNI error code";
41+
}
42+
}
43+
44+
static void report(const char* func, int ret_actual, int ret_expected) {
45+
const char* ret = jni_error_code(ret_actual);
46+
if (ret_actual == ret_expected) {
47+
printf("%s returned %s as expected\n", func, ret);
48+
} else {
49+
printf("Unexpected JNI return code %s from %s\n", ret, func);
50+
}
51+
}
52+
53+
static int using_system_exit = 0; // Not System.exit by default
54+
55+
JNIEXPORT
56+
void JNICALL Java_TestAtExit_00024Tester_setUsingSystemExit(JNIEnv* env, jclass c) {
57+
using_system_exit = 1;
58+
}
59+
60+
void at_exit_handler(void) {
61+
printf("In at_exit_handler\n");
62+
63+
// We've saved the JavaVM from OnLoad time so we first try to
64+
// get a JNIEnv for the current thread.
65+
JNIEnv *env;
66+
jint res = (*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2);
67+
report("GetEnv", res, JNI_EDETACHED);
68+
if (res == JNI_EDETACHED) {
69+
70+
// Test all of the Invocation API functions
71+
72+
res = (*jvm)->AttachCurrentThreadAsDaemon(jvm, (void **)&env, NULL);
73+
report("AttachCurrentThreadAsDaemon", res, JNI_ERR);
74+
res = (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
75+
report("AttachCurrentThread", res, JNI_ERR);
76+
res = (*jvm)->DetachCurrentThread(jvm);
77+
report("DetachCurrentThread", res, JNI_ERR);
78+
79+
JavaVMInitArgs args;
80+
args.version = JNI_VERSION_1_2;
81+
res = JNI_GetDefaultJavaVMInitArgs(&args);
82+
report("JNI_GetDefaultJavaVMInitArgs", res, JNI_OK);
83+
84+
JavaVM* jvm_p[1];
85+
int nVMs;
86+
res = JNI_GetCreatedJavaVMs(jvm_p, 1, &nVMs);
87+
report("JNI_GetCreatedJavaVMs", res, JNI_OK);
88+
// Whether nVMs is 0 or 1 depends on the termination path
89+
if (nVMs == 0 && !using_system_exit) {
90+
printf("Found 0 created VMs as expected\n");
91+
} else if (nVMs == 1 && using_system_exit) {
92+
printf("Found 1 created VM as expected\n");
93+
} else {
94+
printf("Unexpected number of created VMs: %d\n", nVMs);
95+
}
96+
97+
res = (*jvm)->DestroyJavaVM(jvm);
98+
report("DestroyJavaVM", res, JNI_ERR);
99+
100+
// Failure mode depends on the termination path
101+
res = JNI_CreateJavaVM(jvm_p, (void**)&env, &args);
102+
report("JNI_CreateJavaVM", res, using_system_exit ? JNI_EEXIST : JNI_ERR);
103+
}
104+
// else test has already failed
105+
}
106+
107+
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
108+
printf("JNI_OnLoad: registering atexit handler\n");
109+
jvm = vm;
110+
atexit(at_exit_handler);
111+
112+
return JNI_VERSION_1_1;
113+
}

0 commit comments

Comments
 (0)