Skip to content

Commit 31ad80a

Browse files
tprinzingMandy Chung
authored andcommitted
8280902: ResourceBundle::getBundle may throw NPE when invoked by JNI code with no caller frame
Reviewed-by: naoto, mchung, ihse
1 parent 12693a6 commit 31ad80a

File tree

4 files changed

+215
-11
lines changed

4 files changed

+215
-11
lines changed

make/test/JtregNativeJdk.gmk

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ ifeq ($(call isTargetOs, windows), true)
6464
BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeCallerAccessTest := jvm.lib
6565
BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeNullCallerClassLoaderTest := jvm.lib
6666
BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeNullCallerLookupTest := jvm.lib
67+
BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeNullCallerResourceBundle := jvm.lib
6768
BUILD_JDK_JTREG_EXECUTABLES_LIBS_exerevokeall := advapi32.lib
6869
BUILD_JDK_JTREG_LIBRARIES_CFLAGS_libAsyncStackWalk := /EHsc
6970
BUILD_JDK_JTREG_LIBRARIES_CFLAGS_libAsyncInvokers := /EHsc
@@ -84,6 +85,7 @@ else
8485
BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeCallerAccessTest := -ljvm
8586
BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeNullCallerClassLoaderTest := -ljvm
8687
BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeNullCallerLookupTest := -ljvm
88+
BUILD_JDK_JTREG_EXECUTABLES_LIBS_exeNullCallerResourceBundle := -ljvm
8789
endif
8890

8991
ifeq ($(call isTargetOs, macosx), true)

src/java.base/share/classes/java/util/ResourceBundle.java

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1996, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -256,6 +256,12 @@
256256
* resource bundle provider</a>, it does not fall back to the
257257
* class loader search.
258258
*
259+
* <p>
260+
* In cases where the {@code getBundle} factory method is called from a context
261+
* where there is no caller frame on the stack (e.g. when called directly from
262+
* a JNI attached thread), the caller module is default to the unnamed module for the
263+
* {@linkplain ClassLoader#getSystemClassLoader system class loader}.
264+
*
259265
* <h3>Resource bundles in automatic modules</h3>
260266
*
261267
* A common format of resource bundles is in {@linkplain PropertyResourceBundle
@@ -1505,7 +1511,8 @@ public static ResourceBundle getBundle(String baseName, Locale targetLocale,
15051511
}
15061512

15071513
private static Control getDefaultControl(Class<?> caller, String baseName) {
1508-
return getDefaultControl(caller.getModule(), baseName);
1514+
Module callerModule = getCallerModule(caller);
1515+
return getDefaultControl(callerModule, baseName);
15091516
}
15101517

15111518
private static Control getDefaultControl(Module targetModule, String baseName) {
@@ -1536,7 +1543,8 @@ private static Control getControl(String baseName) {
15361543
}
15371544

15381545
private static void checkNamedModule(Class<?> caller) {
1539-
if (caller.getModule().isNamed()) {
1546+
Module callerModule = getCallerModule(caller);
1547+
if (callerModule.isNamed()) {
15401548
throw new UnsupportedOperationException(
15411549
"ResourceBundle.Control not supported in named modules");
15421550
}
@@ -1546,7 +1554,19 @@ private static ResourceBundle getBundleImpl(String baseName,
15461554
Locale locale,
15471555
Class<?> caller,
15481556
Control control) {
1549-
return getBundleImpl(baseName, locale, caller, caller.getClassLoader(), control);
1557+
ClassLoader loader = getLoader(getCallerModule(caller));
1558+
return getBundleImpl(baseName, locale, caller, loader, control);
1559+
}
1560+
1561+
/*
1562+
* Determine the module to be used for the caller. If
1563+
* Reflection::getCallerClass is called from JNI with an empty
1564+
* stack frame the caller will be null, so the system class loader unnamed
1565+
* module will be used.
1566+
*/
1567+
private static Module getCallerModule(Class<?> caller) {
1568+
return (caller != null) ? caller.getModule()
1569+
: ClassLoader.getSystemClassLoader().getUnnamedModule();
15501570
}
15511571

15521572
/**
@@ -1565,10 +1585,7 @@ private static ResourceBundle getBundleImpl(String baseName,
15651585
Class<?> caller,
15661586
ClassLoader loader,
15671587
Control control) {
1568-
if (caller == null) {
1569-
throw new InternalError("null caller");
1570-
}
1571-
Module callerModule = caller.getModule();
1588+
Module callerModule = getCallerModule(caller);
15721589

15731590
// get resource bundles for a named module only if loader is the module's class loader
15741591
if (callerModule.isNamed() && loader == getLoader(callerModule)) {
@@ -1592,7 +1609,7 @@ private static ResourceBundle getBundleFromModule(Class<?> caller,
15921609
Locale locale,
15931610
Control control) {
15941611
Objects.requireNonNull(module);
1595-
Module callerModule = caller.getModule();
1612+
Module callerModule = getCallerModule(caller);
15961613
if (callerModule != module) {
15971614
@SuppressWarnings("removal")
15981615
SecurityManager sm = System.getSecurityManager();
@@ -2228,9 +2245,9 @@ private static void setExpirationTime(CacheKey cacheKey, Control control) {
22282245
*/
22292246
@CallerSensitive
22302247
public static final void clearCache() {
2231-
Class<?> caller = Reflection.getCallerClass();
2248+
Module callerModule = getCallerModule(Reflection.getCallerClass());
22322249
cacheList.keySet().removeIf(
2233-
key -> key.getCallerModule() == caller.getModule()
2250+
key -> key.getCallerModule() == callerModule
22342251
);
22352252
}
22362253

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright (c) 2022, 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+
25+
/**
26+
* @test
27+
* @bug 8280902
28+
* @summary Test uses custom launcher that starts VM using JNI that verifies
29+
* ResourceBundle::getBundle with null caller class functions properly
30+
* using the system class loader unnamed module. The custom launcher
31+
* creates a properties file and passes the VM option to the JNI
32+
* functionality for the resource lookup.
33+
* @library /test/lib
34+
* @requires os.family != "aix"
35+
* @run main/native NullCallerResourceBundle
36+
*/
37+
38+
// Test disabled on AIX since we cannot invoke the JVM on the primordial thread.
39+
40+
import java.io.File;
41+
import java.io.IOException;
42+
import java.nio.file.Files;
43+
import java.nio.file.Path;
44+
import java.util.Properties;
45+
import jdk.test.lib.Platform;
46+
import jdk.test.lib.process.OutputAnalyzer;
47+
48+
public class NullCallerResourceBundle {
49+
public static void main(String[] args) throws IOException {
50+
51+
// build a properties file for the native test
52+
var propPath = Path.of(System.getProperty("test.classes"), "NullCallerResource.properties");
53+
try (var stream = Files.newOutputStream(propPath)) {
54+
var props = new Properties();
55+
props.put("message", "Hello!");
56+
props.save(stream, "Test property list");
57+
}
58+
59+
var launcher = Path.of(System.getProperty("test.nativepath"), "NullCallerResourceBundle");
60+
var classpathAppend = "-Djava.class.path=" + System.getProperty("test.classes");
61+
var pb = new ProcessBuilder(launcher.toString(), classpathAppend);
62+
var env = pb.environment();
63+
64+
var libDir = Platform.libDir().toString();
65+
var vmDir = Platform.jvmLibDir().toString();
66+
67+
// set up shared library path
68+
var sharedLibraryPathEnvName = Platform.sharedLibraryPathVariableName();
69+
env.compute(sharedLibraryPathEnvName,
70+
(k, v) -> (v == null) ? libDir : v + File.pathSeparator + libDir);
71+
env.compute(sharedLibraryPathEnvName,
72+
(k, v) -> (v == null) ? vmDir : v + File.pathSeparator + vmDir);
73+
74+
System.out.println("Launching: " + launcher + " shared library path: " +
75+
env.get(sharedLibraryPathEnvName));
76+
new OutputAnalyzer(pb.start())
77+
.outputTo(System.out)
78+
.errorTo(System.err)
79+
.shouldHaveExitValue(0);
80+
}
81+
82+
}
83+
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright (c) 2022, 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+
#undef NDEBUG
29+
#include "assert.h"
30+
#include "string.h"
31+
32+
33+
/*
34+
* The java test running this native test passes in an argument to provide as
35+
* an option for the configuration of the JVM. The system classpath has the
36+
* classpath of the java test appended so it can pick up the resource that
37+
* was created by the java part of the test.
38+
*/
39+
int main(int argc, char** args) {
40+
JavaVM *jvm;
41+
JNIEnv *env;
42+
JavaVMInitArgs vm_args;
43+
JavaVMOption options[1];
44+
jint rc;
45+
46+
assert(argc == 2);
47+
options[0].optionString = args[1];
48+
49+
vm_args.version = JNI_VERSION_1_2;
50+
vm_args.nOptions = 1;
51+
vm_args.options = options;
52+
53+
if ((rc = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)) != JNI_OK) {
54+
printf("ERROR: cannot create VM.\n");
55+
exit(-1);
56+
}
57+
58+
// b = ResourceBundle.getBundle("NullCallerResource");
59+
jclass class_ResourceBundle = (*env)->FindClass(env, "java/util/ResourceBundle");
60+
assert(class_ResourceBundle != NULL);
61+
jmethodID mid_ResourceBundle_getBundle = (*env)->GetStaticMethodID(env, class_ResourceBundle, "getBundle", "(Ljava/lang/String;)Ljava/util/ResourceBundle;" );
62+
assert(mid_ResourceBundle_getBundle != NULL);
63+
jobject resourceName = (*env)->NewStringUTF(env, "NullCallerResource");
64+
assert(resourceName != NULL);
65+
jobject b = (*env)->CallStaticObjectMethod(env, class_ResourceBundle, mid_ResourceBundle_getBundle, resourceName);
66+
if ((*env)->ExceptionOccurred(env) != NULL) {
67+
printf("ERROR: Exception was thrown calling ResourceBundle::getBundle.\n");
68+
(*env)->ExceptionDescribe(env);
69+
exit(-1);
70+
}
71+
72+
// msg = b.getString("message");
73+
jmethodID mid_ResourceBundle_getString = (*env)->GetMethodID(env, class_ResourceBundle, "getString", "(Ljava/lang/String;)Ljava/lang/String;" );
74+
assert(mid_ResourceBundle_getString != NULL);
75+
jobject key = (*env)->NewStringUTF(env, "message");
76+
jobject msg =(*env)->CallObjectMethod(env, b, mid_ResourceBundle_getString, key);
77+
if ((*env)->ExceptionOccurred(env) != NULL) {
78+
printf("ERROR: Exception was thrown calling ResourceBundle::getString.\n");
79+
(*env)->ExceptionDescribe(env);
80+
exit(-1);
81+
}
82+
assert(msg != NULL);
83+
84+
// check the message
85+
const char* cstr = (*env)->GetStringUTFChars(env, msg, NULL);
86+
assert(cstr != NULL);
87+
assert(strcmp(cstr,"Hello!") == 0);
88+
89+
// ResourceBundle.clearCache()
90+
jmethodID mid_ResourceBundle_clearCache = (*env)->GetStaticMethodID(env, class_ResourceBundle, "clearCache", "()V" );
91+
assert(mid_ResourceBundle_clearCache != NULL);
92+
(*env)->CallStaticVoidMethod(env, class_ResourceBundle, mid_ResourceBundle_clearCache);
93+
if ((*env)->ExceptionOccurred(env) != NULL) {
94+
printf("ERROR: Exception was thrown calling ResourceBundle::clearCache.\n");
95+
(*env)->ExceptionDescribe(env);
96+
exit(-1);
97+
}
98+
99+
(*jvm)->DestroyJavaVM(jvm);
100+
return 0;
101+
}
102+

0 commit comments

Comments
 (0)