Fix GetDirectBufferCapacity for zero length direct buffer
It is possible to create a direct buffer that has a null address and
zero size, e.g. mapping a FileChannel on a zero-length file. In this
case, GetDirectBufferCapacity() should return zero rather than -1.
Bug: 122025675
Test: art/test/run-test --host 2036-jni-filechannel
Test: art/test.py --target -r -t 2036-jni-filechannel
Test: art_runtime_tests
Change-Id: If615025385a8dfad00d815da128dfde19d2ddbc2
diff --git a/runtime/jni/jni_internal.cc b/runtime/jni/jni_internal.cc
index 8e69157..274552a 100644
--- a/runtime/jni/jni_internal.cc
+++ b/runtime/jni/jni_internal.cc
@@ -2536,8 +2536,24 @@
}
static jlong GetDirectBufferCapacity(JNIEnv* env, jobject java_buffer) {
- // Check if |java_buffer| is a direct buffer, bail if not.
- if (GetDirectBufferAddress(env, java_buffer) == nullptr) {
+ if (java_buffer == nullptr) {
+ return -1;
+ }
+
+ if (!IsInstanceOf(env, java_buffer, WellKnownClasses::java_nio_Buffer)) {
+ return -1;
+ }
+
+ // When checking the buffer capacity, it's important to note that a zero-sized direct buffer
+ // may have a null address field which means we can't tell whether it is direct or not.
+ // We therefore call Buffer.isDirect(). One path that creates such a buffer is
+ // FileChannel.map() if the file size is zero.
+ //
+ // NB GetDirectBufferAddress() does not need to call Buffer.isDirect() since it is only
+ // able return a valid address if the Buffer address field is not-null.
+ jboolean direct = env->CallBooleanMethod(java_buffer,
+ WellKnownClasses::java_nio_Buffer_isDirect);
+ if (!direct) {
return -1;
}
diff --git a/runtime/well_known_classes.cc b/runtime/well_known_classes.cc
index 1a3a292..838eb8a 100644
--- a/runtime/well_known_classes.cc
+++ b/runtime/well_known_classes.cc
@@ -117,6 +117,7 @@
jmethodID WellKnownClasses::java_lang_Thread_run;
jmethodID WellKnownClasses::java_lang_ThreadGroup_add;
jmethodID WellKnownClasses::java_lang_ThreadGroup_removeThread;
+jmethodID WellKnownClasses::java_nio_Buffer_isDirect;
jmethodID WellKnownClasses::java_nio_DirectByteBuffer_init;
jmethodID WellKnownClasses::java_util_function_Consumer_accept;
jmethodID WellKnownClasses::libcore_reflect_AnnotationFactory_createAnnotation;
@@ -405,6 +406,7 @@
java_lang_Thread_run = CacheMethod(env, java_lang_Thread, false, "run", "()V");
java_lang_ThreadGroup_add = CacheMethod(env, java_lang_ThreadGroup, false, "add", "(Ljava/lang/Thread;)V");
java_lang_ThreadGroup_removeThread = CacheMethod(env, java_lang_ThreadGroup, false, "threadTerminated", "(Ljava/lang/Thread;)V");
+ java_nio_Buffer_isDirect = CacheMethod(env, java_nio_Buffer, false, "isDirect", "()Z");
java_nio_DirectByteBuffer_init = CacheMethod(env, java_nio_DirectByteBuffer, false, "<init>", "(JI)V");
java_util_function_Consumer_accept = CacheMethod(env, java_util_function_Consumer, false, "accept", "(Ljava/lang/Object;)V");
libcore_reflect_AnnotationFactory_createAnnotation = CacheMethod(env, libcore_reflect_AnnotationFactory, true, "createAnnotation", "(Ljava/lang/Class;[Llibcore/reflect/AnnotationMember;)Ljava/lang/annotation/Annotation;");
@@ -575,6 +577,7 @@
java_lang_Thread_run = nullptr;
java_lang_ThreadGroup_add = nullptr;
java_lang_ThreadGroup_removeThread = nullptr;
+ java_nio_Buffer_isDirect = nullptr;
java_nio_DirectByteBuffer_init = nullptr;
libcore_reflect_AnnotationFactory_createAnnotation = nullptr;
libcore_reflect_AnnotationMember_init = nullptr;
diff --git a/runtime/well_known_classes.h b/runtime/well_known_classes.h
index 34d2e85..5c933bd 100644
--- a/runtime/well_known_classes.h
+++ b/runtime/well_known_classes.h
@@ -130,6 +130,7 @@
static jmethodID java_lang_Thread_run;
static jmethodID java_lang_ThreadGroup_add;
static jmethodID java_lang_ThreadGroup_removeThread;
+ static jmethodID java_nio_Buffer_isDirect;
static jmethodID java_nio_DirectByteBuffer_init;
static jmethodID java_util_function_Consumer_accept;
static jmethodID libcore_reflect_AnnotationFactory_createAnnotation;
diff --git a/test/2036-jni-filechannel/Android.bp b/test/2036-jni-filechannel/Android.bp
new file mode 100644
index 0000000..35aa1d9
--- /dev/null
+++ b/test/2036-jni-filechannel/Android.bp
@@ -0,0 +1,6 @@
+// Generated by `regen-test-files`. Do not edit manually.
+java_test {
+ name: "art-run-test-2036-jni-filechannel",
+ defaults: ["art-run-test-defaults"],
+ srcs: ["src/**/*.java"],
+}
diff --git a/test/2036-jni-filechannel/expected.txt b/test/2036-jni-filechannel/expected.txt
new file mode 100644
index 0000000..98ea70a
--- /dev/null
+++ b/test/2036-jni-filechannel/expected.txt
@@ -0,0 +1,2 @@
+JNI_OnLoad called
+Expected 0 got 0 PASS
diff --git a/test/2036-jni-filechannel/info.txt b/test/2036-jni-filechannel/info.txt
new file mode 100644
index 0000000..2d3ac28
--- /dev/null
+++ b/test/2036-jni-filechannel/info.txt
@@ -0,0 +1,2 @@
+Test that GetDirectBufferCapacity works correctly for a mapped buffer
+from a zero length file.
diff --git a/test/2036-jni-filechannel/jni_filechannel.cc b/test/2036-jni-filechannel/jni_filechannel.cc
new file mode 100644
index 0000000..edaee32
--- /dev/null
+++ b/test/2036-jni-filechannel/jni_filechannel.cc
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+#include <jni.h>
+
+extern "C" JNIEXPORT void Java_Main_checkBufferCapacity(JNIEnv* env,
+ jclass /*clazz*/,
+ jobject buffer,
+ jint expectedCapacity) {
+ jlong capacity = env->GetDirectBufferCapacity(buffer);
+ const char* status = (capacity == expectedCapacity) ? "PASS" : "FAIL";
+ std::cout << "Expected " << expectedCapacity
+ << " got " << capacity
+ << " " << status << std::endl;
+}
diff --git a/test/2036-jni-filechannel/src/Main.java b/test/2036-jni-filechannel/src/Main.java
new file mode 100644
index 0000000..204cde7
--- /dev/null
+++ b/test/2036-jni-filechannel/src/Main.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.nio.channels.FileChannel;
+import java.nio.file.*;
+import java.nio.Buffer;
+import java.nio.MappedByteBuffer;
+
+public class Main {
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+
+ FileChannel channel = FileChannel.open(Paths.get("/dev", "null"),
+ StandardOpenOption.READ);
+ MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, 0);
+ checkBufferCapacity(buffer, 0);
+ }
+
+ static native void checkBufferCapacity(Buffer buffer, int expectedCapacity);
+}
diff --git a/test/Android.bp b/test/Android.bp
index 1aa2edb..79c3cb8 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -641,6 +641,7 @@
"2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc",
"2031-zygote-compiled-frame-deopt/native-wait.cc",
"2033-shutdown-mechanics/native_shutdown.cc",
+ "2036-jni-filechannel/jni_filechannel.cc",
"common/runtime_state.cc",
"common/stack_inspect.cc",
],