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",
     ],