From 90f3f4325772773f1dc1814c56d7326d5389e2c7 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Wed, 4 Sep 2024 03:41:42 +0000 Subject: [PATCH] 8328877: [JNI] The JNI Specification needs to address the limitations of integer UTF-8 String lengths Reviewed-by: cjplummer, alanb --- src/hotspot/os/posix/dtrace/hotspot_jni.d | 37 +++++----- src/hotspot/share/prims/jni.cpp | 18 ++++- src/hotspot/share/prims/jniCheck.cpp | 28 ++++++- src/hotspot/share/runtime/threads.cpp | 1 + .../share/utilities/dtrace_disabled.hpp | 6 +- src/java.base/share/native/include/jni.h | 12 ++- .../jtreg/native_sanity/JniVersion.java | 6 +- .../jni/checked/TestLargeUTF8Length.java | 73 +++++++++++++++++++ .../jni/checked/libTestLargeUTF8Length.c | 54 ++++++++++++++ 9 files changed, 208 insertions(+), 27 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/jni/checked/TestLargeUTF8Length.java create mode 100644 test/hotspot/jtreg/runtime/jni/checked/libTestLargeUTF8Length.c diff --git a/src/hotspot/os/posix/dtrace/hotspot_jni.d b/src/hotspot/os/posix/dtrace/hotspot_jni.d index eb95b7e4c3a..c5676921b37 100644 --- a/src/hotspot/os/posix/dtrace/hotspot_jni.d +++ b/src/hotspot/os/posix/dtrace/hotspot_jni.d @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -19,7 +19,7 @@ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. - * + * */ provider hotspot_jni { @@ -129,7 +129,7 @@ provider hotspot_jni { probe CallNonvirtualVoidMethodA__return(); probe CallNonvirtualVoidMethod__entry(void*, void*, void*, uintptr_t); probe CallNonvirtualVoidMethod__return(); - probe CallNonvirtualVoidMethodV__entry(void*, void*, void*, uintptr_t); + probe CallNonvirtualVoidMethodV__entry(void*, void*, void*, uintptr_t); probe CallNonvirtualVoidMethodV__return(); probe CallObjectMethodA__entry(void*, void*, uintptr_t); probe CallObjectMethodA__return(void*); @@ -200,14 +200,14 @@ provider hotspot_jni { probe CallStaticVoidMethodA__entry(void*, void*, uintptr_t); probe CallStaticVoidMethodA__return(); probe CallStaticVoidMethod__entry(void*, void*, uintptr_t); - probe CallStaticVoidMethod__return(); - probe CallStaticVoidMethodV__entry(void*, void*, uintptr_t); + probe CallStaticVoidMethod__return(); + probe CallStaticVoidMethodV__entry(void*, void*, uintptr_t); probe CallStaticVoidMethodV__return(); - probe CallVoidMethodA__entry(void*, void*, uintptr_t); + probe CallVoidMethodA__entry(void*, void*, uintptr_t); probe CallVoidMethodA__return(); - probe CallVoidMethod__entry(void*, void*, uintptr_t); - probe CallVoidMethod__return(); - probe CallVoidMethodV__entry(void*, void*, uintptr_t); + probe CallVoidMethod__entry(void*, void*, uintptr_t); + probe CallVoidMethod__return(); + probe CallVoidMethodV__entry(void*, void*, uintptr_t); probe CallVoidMethodV__return(); probe CreateJavaVM__entry(void**, void**, void*); probe CreateJavaVM__return(uint32_t); @@ -229,7 +229,7 @@ provider hotspot_jni { probe ExceptionCheck__return(uintptr_t); probe ExceptionClear__entry(void*); probe ExceptionClear__return(); - probe ExceptionDescribe__entry(void*); + probe ExceptionDescribe__entry(void*); probe ExceptionDescribe__return(); probe ExceptionOccurred__entry(void*); probe ExceptionOccurred__return(void*); @@ -352,6 +352,8 @@ provider hotspot_jni { probe GetStringUTFChars__return(const char*); probe GetStringUTFLength__entry(void*, void*); probe GetStringUTFLength__return(uintptr_t); + probe GetStringUTFLengthAsLong__entry(void*, void*); + probe GetStringUTFLengthAsLong__return(uintptr_t); probe GetStringUTFRegion__entry(void*, void*, uintptr_t, uintptr_t, char*); probe GetStringUTFRegion__return(); probe GetSuperclass__entry(void*, void*); @@ -388,13 +390,13 @@ provider hotspot_jni { probe NewLocalRef__return(void*); probe NewLongArray__entry(void*, uintptr_t); probe NewLongArray__return(void*); - probe NewObjectA__entry(void*, void*, uintptr_t); + probe NewObjectA__entry(void*, void*, uintptr_t); probe NewObjectA__return(void*); probe NewObjectArray__entry(void*, uintptr_t, void*, void*); probe NewObjectArray__return(void*); - probe NewObject__entry(void*, void*, uintptr_t); + probe NewObject__entry(void*, void*, uintptr_t); probe NewObject__return(void*); - probe NewObjectV__entry(void*, void*, uintptr_t); + probe NewObjectV__entry(void*, void*, uintptr_t); probe NewObjectV__return(void*); probe NewShortArray__entry(void*, uintptr_t); probe NewShortArray__return(void*); @@ -408,7 +410,7 @@ provider hotspot_jni { probe PopLocalFrame__return(void*); probe PushLocalFrame__entry(void*, uint32_t); probe PushLocalFrame__return(uint32_t); - probe RegisterNatives__entry(void*, void*, const void*, uint32_t); + probe RegisterNatives__entry(void*, void*, const void*, uint32_t); probe RegisterNatives__return(uint32_t); probe ReleaseBooleanArrayElements__entry(void*, void*, uintptr_t*, uint32_t); probe ReleaseBooleanArrayElements__return(); @@ -490,13 +492,13 @@ provider hotspot_jni { probe SetStaticShortField__return(); probe Throw__entry(void*, void*); probe Throw__return(intptr_t); - probe ThrowNew__entry(void*, void*, const char*); - probe ThrowNew__return(intptr_t); + probe ThrowNew__entry(void*, void*, const char*); + probe ThrowNew__return(intptr_t); probe ToReflectedField__entry(void*, void*, uintptr_t, uintptr_t); probe ToReflectedField__return(void*); probe ToReflectedMethod__entry(void*, void*, uintptr_t, uintptr_t); probe ToReflectedMethod__return(void*); - probe UnregisterNatives__entry(void*, void*); + probe UnregisterNatives__entry(void*, void*); probe UnregisterNatives__return(uint32_t); }; @@ -505,4 +507,3 @@ provider hotspot_jni { #pragma D attributes Private/Private/Unknown provider hotspot_jni function #pragma D attributes Standard/Standard/Common provider hotspot_jni name #pragma D attributes Evolving/Evolving/Common provider hotspot_jni args - diff --git a/src/hotspot/share/prims/jni.cpp b/src/hotspot/share/prims/jni.cpp index 8a20d5f85b0..fbb2c6e3e08 100644 --- a/src/hotspot/share/prims/jni.cpp +++ b/src/hotspot/share/prims/jni.cpp @@ -102,7 +102,7 @@ #include "jfr/jfr.hpp" #endif -static jint CurrentVersion = JNI_VERSION_21; +static jint CurrentVersion = JNI_VERSION_24; #if defined(_WIN32) && !defined(USE_VECTORED_EXCEPTION_HANDLING) extern LONG WINAPI topLevelExceptionFilter(_EXCEPTION_POINTERS* ); @@ -2221,13 +2221,21 @@ JNI_END JNI_ENTRY(jsize, jni_GetStringUTFLength(JNIEnv *env, jstring string)) - HOTSPOT_JNI_GETSTRINGUTFLENGTH_ENTRY(env, string); + HOTSPOT_JNI_GETSTRINGUTFLENGTH_ENTRY(env, string); oop java_string = JNIHandles::resolve_non_null(string); jsize ret = java_lang_String::utf8_length_as_int(java_string); HOTSPOT_JNI_GETSTRINGUTFLENGTH_RETURN(ret); return ret; JNI_END +JNI_ENTRY(jlong, jni_GetStringUTFLengthAsLong(JNIEnv *env, jstring string)) + HOTSPOT_JNI_GETSTRINGUTFLENGTHASLONG_ENTRY(env, string); + oop java_string = JNIHandles::resolve_non_null(string); + size_t ret = java_lang_String::utf8_length(java_string); + HOTSPOT_JNI_GETSTRINGUTFLENGTHASLONG_RETURN(ret); +return checked_cast(ret); +JNI_END + JNI_ENTRY(const char*, jni_GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy)) HOTSPOT_JNI_GETSTRINGUTFCHARS_ENTRY(env, string, (uintptr_t *) isCopy); @@ -3398,7 +3406,11 @@ struct JNINativeInterface_ jni_NativeInterface = { // Virtual threads - jni_IsVirtualThread + jni_IsVirtualThread, + + // Large UTF8 support + + jni_GetStringUTFLengthAsLong }; diff --git a/src/hotspot/share/prims/jniCheck.cpp b/src/hotspot/share/prims/jniCheck.cpp index 0da0cbbd639..87543686c3c 100644 --- a/src/hotspot/share/prims/jniCheck.cpp +++ b/src/hotspot/share/prims/jniCheck.cpp @@ -1511,6 +1511,27 @@ JNI_ENTRY_CHECKED(jsize, checkString(thr, str); ) jsize result = UNCHECKED()->GetStringUTFLength(env,str); + jlong full_length = UNCHECKED()->GetStringUTFLengthAsLong(env,str); + if (full_length > result) { + ResourceMark rm(thr); + stringStream ss; + ss.print("WARNING: large String with modified UTF-8 length " JLONG_FORMAT + " is reporting a reduced length of %d - use GetStringUTFLengthAsLong instead", + full_length, result); + NativeReportJNIWarning(thr, ss.as_string()); + } + functionExit(thr); + return result; +JNI_END + +JNI_ENTRY_CHECKED(jlong, + checked_jni_GetStringUTFLengthAsLong(JNIEnv *env, + jstring str)) + functionEnter(thr); + IN_VM( + checkString(thr, str); + ) + jlong result = UNCHECKED()->GetStringUTFLengthAsLong(env,str); functionExit(thr); return result; JNI_END @@ -2283,7 +2304,12 @@ struct JNINativeInterface_ checked_jni_NativeInterface = { // Virtual threads - checked_jni_IsVirtualThread + checked_jni_IsVirtualThread, + + // Large UTF8 support + + checked_jni_GetStringUTFLengthAsLong + }; diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index ea18ff3a006..af904bdcfcd 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -984,6 +984,7 @@ jboolean Threads::is_supported_jni_version(jint version) { if (version == JNI_VERSION_19) return JNI_TRUE; if (version == JNI_VERSION_20) return JNI_TRUE; if (version == JNI_VERSION_21) return JNI_TRUE; + if (version == JNI_VERSION_24) return JNI_TRUE; return JNI_FALSE; } diff --git a/src/hotspot/share/utilities/dtrace_disabled.hpp b/src/hotspot/share/utilities/dtrace_disabled.hpp index b6ffb5936ea..6cbd79326ac 100644 --- a/src/hotspot/share/utilities/dtrace_disabled.hpp +++ b/src/hotspot/share/utilities/dtrace_disabled.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -783,6 +783,10 @@ #define HOTSPOT_JNI_GETSTRINGUTFLENGTH_ENTRY_ENABLED() 0 #define HOTSPOT_JNI_GETSTRINGUTFLENGTH_RETURN(arg0) #define HOTSPOT_JNI_GETSTRINGUTFLENGTH_RETURN_ENABLED() 0 +#define HOTSPOT_JNI_GETSTRINGUTFLENGTHASLONG_ENTRY(arg0, arg1) +#define HOTSPOT_JNI_GETSTRINGUTFLENGTHASLONG_ENTRY_ENABLED() 0 +#define HOTSPOT_JNI_GETSTRINGUTFLENGTHASLONG_RETURN(arg0) +#define HOTSPOT_JNI_GETSTRINGUTFLENGTHASLONG_RETURN_ENABLED() 0 #define HOTSPOT_JNI_GETSTRINGUTFREGION_ENTRY(arg0, arg1, arg2, arg3, arg4) #define HOTSPOT_JNI_GETSTRINGUTFREGION_ENTRY_ENABLED() 0 #define HOTSPOT_JNI_GETSTRINGUTFREGION_RETURN() diff --git a/src/java.base/share/native/include/jni.h b/src/java.base/share/native/include/jni.h index c85da1bc67f..6bbc71ded1f 100644 --- a/src/java.base/share/native/include/jni.h +++ b/src/java.base/share/native/include/jni.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -775,6 +775,12 @@ struct JNINativeInterface_ { jboolean (JNICALL *IsVirtualThread) (JNIEnv* env, jobject obj); + + /* Large UTF8 Support */ + + jlong (JNICALL *GetStringUTFLengthAsLong) + (JNIEnv *env, jstring str); + }; /* @@ -1623,6 +1629,9 @@ struct JNIEnv_ { jsize GetStringUTFLength(jstring str) { return functions->GetStringUTFLength(this,str); } + jlong GetStringUTFLengthAsLong(jstring str) { + return functions->GetStringUTFLengthAsLong(this,str); + } const char* GetStringUTFChars(jstring str, jboolean *isCopy) { return functions->GetStringUTFChars(this,str,isCopy); } @@ -1993,6 +2002,7 @@ JNI_OnUnload(JavaVM *vm, void *reserved); #define JNI_VERSION_19 0x00130000 #define JNI_VERSION_20 0x00140000 #define JNI_VERSION_21 0x00150000 +#define JNI_VERSION_24 0x00180000 #ifdef __cplusplus } /* extern "C" */ diff --git a/test/hotspot/jtreg/native_sanity/JniVersion.java b/test/hotspot/jtreg/native_sanity/JniVersion.java index 099ea547fca..6459bcf7da1 100644 --- a/test/hotspot/jtreg/native_sanity/JniVersion.java +++ b/test/hotspot/jtreg/native_sanity/JniVersion.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,12 +27,12 @@ */ public class JniVersion { - public static final int JNI_VERSION_21 = 0x00150000; + public static final int JNI_VERSION_24 = 0x00180000; public static void main(String... args) throws Exception { System.loadLibrary("JniVersion"); int res = getJniVersion(); - if (res != JNI_VERSION_21) { + if (res != JNI_VERSION_24) { throw new Exception("Unexpected value returned from getJniVersion(): 0x" + Integer.toHexString(res)); } } diff --git a/test/hotspot/jtreg/runtime/jni/checked/TestLargeUTF8Length.java b/test/hotspot/jtreg/runtime/jni/checked/TestLargeUTF8Length.java new file mode 100644 index 00000000000..9186a7ea381 --- /dev/null +++ b/test/hotspot/jtreg/runtime/jni/checked/TestLargeUTF8Length.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @bug 8328877 + * @summary Test warning for GetStringUTFLength and functionality of GetStringUTFLengthAsLong + * @requires vm.bits == 64 + * @library /test/lib + * @modules java.management + * @run main/native TestLargeUTF8Length launch + */ + +import java.util.Arrays; +import jdk.test.lib.Utils; +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +public class TestLargeUTF8Length { + + static { + System.loadLibrary("TestLargeUTF8Length"); + } + + static native void checkUTF8Length(String s, long utf8Length); + + static void test() { + int length = Integer.MAX_VALUE/2 + 1; + char character = (char)0XD1; // N with tilde + long utf8Length = 2L * length; + char[] chrs = new char[length]; + Arrays.fill(chrs, character); + String s = new String(chrs); + checkUTF8Length(s, utf8Length); + } + + public static void main(String[] args) throws Throwable { + if (args == null || args.length == 0) { + test(); + return; + } + + OutputAnalyzer oa = ProcessTools.executeTestJava("-Xms9G", + "-Xmx9G", + "-Xcheck:jni", + "-Djava.library.path=" + Utils.TEST_NATIVE_PATH, + "TestLargeUTF8Length"); + String warning = "WARNING: large String with modified UTF-8 length .*" + + "is reporting a reduced length of .* - use GetStringUTFLengthAsLong instead"; + oa.shouldHaveExitValue(0); + oa.stdoutShouldMatch(warning); + oa.reportDiagnosticSummary(); + } +} diff --git a/test/hotspot/jtreg/runtime/jni/checked/libTestLargeUTF8Length.c b/test/hotspot/jtreg/runtime/jni/checked/libTestLargeUTF8Length.c new file mode 100644 index 00000000000..addf7d09d7e --- /dev/null +++ b/test/hotspot/jtreg/runtime/jni/checked/libTestLargeUTF8Length.c @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include + +#include +#include + +JNIEXPORT void JNICALL +Java_TestLargeUTF8Length_checkUTF8Length(JNIEnv *env, jclass clz, + jstring str, jlong expected_length) { + + jlong utf8_length; + + // First get truncated length to generate warning + utf8_length = (*env)->GetStringUTFLength(env, str); + + if (utf8_length != INT_MAX - 1) { + printf("Error: expected length of %d, but got %lld\n", INT_MAX - 1, + (long long) utf8_length); + (*env)->FatalError(env, "Unexpected truncated length"); + } + + // Now get true length + utf8_length = (*env)->GetStringUTFLengthAsLong(env, str); + + if (utf8_length != expected_length ) { + printf("Error: expected length of %lld, but got %lld\n", + (long long) expected_length, (long long) utf8_length); + (*env)->FatalError(env, "Unexpected true length"); + } + + +}