Nativehelper: Remove static locals for field and method IDs.

This commit replaces static locals with lazy initialization functions so
that field and method IDs can be updated between different VM instances.
It also adds a clear() function to reset the state.

Bug: 65522645
Test: m
Test: m test-art-host
Change-Id: I340cc2ced6b2e5bcd541f1d7b9741574e37c27eb
diff --git a/Android.bp b/Android.bp
index 8282d87..6d3ab86 100644
--- a/Android.bp
+++ b/Android.bp
@@ -46,7 +46,7 @@
         },
     },
 
-    header_libs: ["jni_headers"],
+    header_libs: ["jni_headers", "jni_platform_headers"],
     export_header_lib_headers: ["jni_headers"],
 
     shared_libs: [
@@ -73,6 +73,7 @@
         "include/nativehelper", // TODO(b/63762847): remove
     ],
     cflags: ["-Werror"],
+    include_dirs: ["libnativehelper/platform_include"],
     srcs: [
         "JNIHelp.cpp",
         "JniConstants.cpp",
diff --git a/JNIHelp-priv.h b/JNIHelp-priv.h
new file mode 100644
index 0000000..ce350a5
--- /dev/null
+++ b/JNIHelp-priv.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017 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.
+ */
+
+#ifndef NATIVEHELPER_JNIHELP_PRIV_H_
+#define NATIVEHELPER_JNIHELP_PRIV_H_
+
+namespace android {
+
+void ClearJNIHelpLocalCache();
+
+}  // namespace android
+
+#endif  // JNIHELP_PRIV
diff --git a/JNIHelp.cpp b/JNIHelp.cpp
index 5aefd42..9c6cd74 100644
--- a/JNIHelp.cpp
+++ b/JNIHelp.cpp
@@ -34,6 +34,55 @@
 
 #include <string>
 
+namespace {
+
+// java.io.FileDescriptor.descriptor.
+jfieldID fileDescriptorDescriptorField = nullptr;
+
+// void java.io.FileDescriptor.<init>().
+jmethodID fileDescriptorInitMethod = nullptr;
+// Object java.lang.ref.Reference.get()
+jmethodID referenceGetMethod = nullptr;
+
+jfieldID FindField(JNIEnv* env, jclass klass, const char* name, const char* desc) {
+    jfieldID result = env->GetFieldID(klass, name, desc);
+    if (result == NULL) {
+        ALOGV("failed to find field '%s:%s'", name, desc);
+        abort();
+    }
+    return result;
+}
+
+jmethodID FindMethod(JNIEnv* env, jclass klass, const char* name, const char* signature) {
+    jmethodID result = env->GetMethodID(klass, name, signature);
+    if (result == NULL) {
+        ALOGV("failed to find method '%s%s'", name, signature);
+        abort();
+    }
+    return result;
+}
+
+void InitFieldsAndMethods(JNIEnv* env) {
+    JniConstants::init(env);  // Ensure that classes are cached.
+    fileDescriptorDescriptorField = FindField(env, JniConstants::fileDescriptorClass, "descriptor",
+            "I");
+    fileDescriptorInitMethod = FindMethod(env, JniConstants::fileDescriptorClass, "<init>", "()V");
+    referenceGetMethod = FindMethod(env, JniConstants::referenceClass, "get",
+            "()Ljava/lang/Object;");
+}
+
+}
+
+namespace android {
+
+void ClearJNIHelpLocalCache() {
+    fileDescriptorDescriptorField = nullptr;
+    fileDescriptorInitMethod = nullptr;
+    referenceGetMethod = nullptr;
+}
+
+}
+
 /**
  * Equivalent to ScopedLocalRef, but for C_JNIEnv instead. (And slightly more powerful.)
  */
@@ -326,9 +375,11 @@
 
 jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd) {
     JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
-    JniConstants::init(e);
-    static jmethodID ctor = e->GetMethodID(JniConstants::fileDescriptorClass, "<init>", "()V");
-    jobject fileDescriptor = (*env)->NewObject(e, JniConstants::fileDescriptorClass, ctor);
+    if (fileDescriptorInitMethod == nullptr) {
+        InitFieldsAndMethods(e);
+    }
+    jobject fileDescriptor = (*env)->NewObject(e, JniConstants::fileDescriptorClass,
+            fileDescriptorInitMethod);
     // NOTE: NewObject ensures that an OutOfMemoryError will be seen by the Java
     // caller if the alloc fails, so we just return NULL when that happens.
     if (fileDescriptor != NULL)  {
@@ -339,10 +390,12 @@
 
 int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor) {
     JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
-    JniConstants::init(e);
-    static jfieldID fid = e->GetFieldID(JniConstants::fileDescriptorClass, "descriptor", "I");
+    if (fileDescriptorDescriptorField == nullptr) {
+        InitFieldsAndMethods(e);
+    }
     if (fileDescriptor != NULL) {
-        return (*env)->GetIntField(e, fileDescriptor, fid);
+        return (*env)->GetIntField(e, fileDescriptor,
+                fileDescriptorDescriptorField);
     } else {
         return -1;
     }
@@ -350,16 +403,18 @@
 
 void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value) {
     JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
-    JniConstants::init(e);
-    static jfieldID fid = e->GetFieldID(JniConstants::fileDescriptorClass, "descriptor", "I");
-    (*env)->SetIntField(e, fileDescriptor, fid, value);
+    if (fileDescriptorDescriptorField == nullptr) {
+        InitFieldsAndMethods(e);
+    }
+    (*env)->SetIntField(e, fileDescriptor, fileDescriptorDescriptorField, value);
 }
 
 jobject jniGetReferent(C_JNIEnv* env, jobject ref) {
     JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
-    JniConstants::init(e);
-    static jmethodID get = e->GetMethodID(JniConstants::referenceClass, "get", "()Ljava/lang/Object;");
-    return (*env)->CallObjectMethod(e, ref, get);
+    if (referenceGetMethod == nullptr) {
+        InitFieldsAndMethods(e);
+    }
+    return (*env)->CallObjectMethod(e, ref, referenceGetMethod);
 }
 
 jstring jniCreateString(C_JNIEnv* env, const jchar* unicodeChars, jsize len) {
diff --git a/JniConstants.cpp b/JniConstants.cpp
index f42de2f..632e40c 100644
--- a/JniConstants.cpp
+++ b/JniConstants.cpp
@@ -17,7 +17,9 @@
 #define LOG_TAG "JniConstants"
 
 #include "ALog-priv.h"
+#include "JNIHelp-priv.h"
 #include <nativehelper/JniConstants.h>
+#include <nativehelper/JniConstants-priv.h>
 #include <nativehelper/ScopedLocalRef.h>
 
 #include <stdlib.h>
@@ -138,3 +140,12 @@
 
     g_constants_initialized = true;
 }
+
+namespace android {
+
+void ClearJniConstantsCache() {
+    g_constants_initialized = false;
+    ClearJNIHelpLocalCache();
+}
+
+}
diff --git a/platform_include/nativehelper/JniConstants-priv.h b/platform_include/nativehelper/JniConstants-priv.h
new file mode 100644
index 0000000..c4cba7c
--- /dev/null
+++ b/platform_include/nativehelper/JniConstants-priv.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017 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.
+ */
+
+#ifndef NATIVEHELPER_PLATFORM_INCLUDE_NATIVEHELPER_JNICONSTANTS_PRIV_H_
+#define NATIVEHELPER_PLATFORM_INCLUDE_NATIVEHELPER_JNICONSTANTS_PRIV_H_
+
+namespace android {
+
+void ClearJniConstantsCache();
+
+}  // namespace android
+
+#endif  // NATIVEHELPER_PLATFORM_INCLUDE_NATIVEHELPER_JNICONSTANTS_PRIV_H_