Add two new JNI debugging options.

The alwaysCheckThread option provides some backwards compatibility for apps
that misuse JNIEnv*s across threads. The logThirdPartyJni is a step towards
making it easier for third-party developers to debug their JNI errors.

Change-Id: I134374da0fe94f3fbc6b6d5aef52e3eef658aff9
diff --git a/vm/Globals.h b/vm/Globals.h
index fcb6b29..a47641b 100644
--- a/vm/Globals.h
+++ b/vm/Globals.h
@@ -966,9 +966,13 @@
     bool warnOnly;
     bool forceCopy;
 
-    /**
-     * The JNI JavaVM object. Dalvik only supports a single VM per process.
-     */
+    // Don't trust that we've been passed the right JNIEnv* for this thread.
+    bool alwaysCheckThread;
+
+    // Debugging help for third-party developers. Similar to -Xjnitrace.
+    bool logThirdPartyJni;
+
+    // We only support a single JavaVM per process.
     JavaVM*     jniVm;
 };
 
diff --git a/vm/Jni.cpp b/vm/Jni.cpp
index 4686647..c9689ad 100644
--- a/vm/Jni.cpp
+++ b/vm/Jni.cpp
@@ -195,6 +195,16 @@
  * ===========================================================================
  */
 
+static inline Thread* self(JNIEnv* env) {
+    Thread* envSelf = ((JNIEnvExt*) env)->self;
+    Thread* self = gDvmJni.alwaysCheckThread ? dvmThreadSelf() : envSelf;
+    if (self != envSelf) {
+        LOGE("JNI ERROR: env->self != thread-self (%p vs. %p); auto-correcting",
+                envSelf, self);
+    }
+    return self;
+}
+
 /*
  * Entry/exit processing for all JNI calls.
  *
@@ -207,7 +217,7 @@
 class ScopedJniThreadState {
 public:
     explicit ScopedJniThreadState(JNIEnv* env) {
-        mSelf = ((JNIEnvExt*) env)->self; // dvmThreadSelf() would be safer
+        mSelf = ::self(env);
         CHECK_STACK_SUM(mSelf);
         dvmChangeStatus(mSelf, THREAD_RUNNING);
     }
@@ -293,7 +303,7 @@
  * get weird if the JNI code is passing the wrong JNIEnv around.
  */
 static inline IndirectRefTable* getLocalRefTable(JNIEnv* env) {
-    return &((JNIEnvExt*)env)->self->jniLocalRefTable;
+    return &self(env)->jniLocalRefTable;
 }
 
 /*
@@ -370,7 +380,7 @@
     }
 
     IndirectRefTable* pRefTable = getLocalRefTable(env);
-    void* curFrame = ((JNIEnvExt*)env)->self->interpSave.curFrame;
+    void* curFrame = self(env)->interpSave.curFrame;
     u4 cookie = SAVEAREA_FROM_FP(curFrame)->xtra.localRefCookie;
     jobject jobj = (jobject) pRefTable->add(cookie, obj);
     if (jobj == NULL) {
@@ -423,10 +433,8 @@
     }
 
     IndirectRefTable* pRefTable = getLocalRefTable(env);
-    Thread* self = ((JNIEnvExt*)env)->self;
-    u4 cookie =
-        SAVEAREA_FROM_FP(self->interpSave.curFrame)->xtra.localRefCookie;
-
+    void* curFrame = self(env)->interpSave.curFrame;
+    u4 cookie = SAVEAREA_FROM_FP(curFrame)->xtra.localRefCookie;
     if (!pRefTable->remove(cookie, jobj)) {
         /*
          * Attempting to delete a local reference that is not in the
@@ -846,11 +854,33 @@
     dvmLogNativeMethodExit(method, self, *pResult);
 }
 
-/**
- * Returns true if the -Xjnitrace setting implies we should trace 'method'.
- */
+static const char* builtInPrefixes[] = {
+    "Landroid/",
+    "Lcom/android/",
+    "Lcom/google/android/",
+    "Ldalvik/",
+    "Ljava/",
+    "Ljavax/",
+    "Llibcore/",
+    "Lorg/apache/harmony/",
+};
 static bool shouldTrace(Method* method) {
-    return gDvm.jniTrace && strstr(method->clazz->descriptor, gDvm.jniTrace);
+    const char* className = method->clazz->descriptor;
+    // Return true if the -Xjnitrace setting implies we should trace 'method'.
+    if (gDvm.jniTrace && strstr(className, gDvm.jniTrace)) {
+        return true;
+    }
+    // Return true if we're trying to log all third-party JNI activity and 'method' doesn't look
+    // like part of Android.
+    if (gDvmJni.logThirdPartyJni) {
+        for (size_t i = 0; i < NELEM(builtInPrefixes); ++i) {
+            if (strstr(className, builtInPrefixes[i]) == className) {
+                return false;
+            }
+        }
+        return true;
+    }
+    return false;
 }
 
 /*
@@ -3497,6 +3527,10 @@
                     gDvmJni.warnOnly = true;
                 } else if (strcmp(jniOpt, "forcecopy") == 0) {
                     gDvmJni.forceCopy = true;
+                } else if (strcmp(jniOpt, "alwaysCheckThread") == 0) {
+                    gDvmJni.alwaysCheckThread = true;
+                } else if (strcmp(jniOpt, "logThirdPartyJni") == 0) {
+                    gDvmJni.logThirdPartyJni = true;
                 } else {
                     dvmFprintf(stderr, "ERROR: CreateJavaVM failed: unknown -Xjniopts option '%s'\n",
                             jniOpt);
@@ -3511,11 +3545,6 @@
         }
     }
 
-    if (sawJniOpts && !gDvmJni.useCheckJni) {
-        dvmFprintf(stderr, "ERROR: -Xjniopts only makes sense with -Xcheck:jni\n");
-        return JNI_ERR;
-    }
-
     if (gDvmJni.useCheckJni) {
         dvmUseCheckedJniVm(pVM);
     }