Add JNI app bug workarounds.

Specifically, this hands out direct pointers for all local references,
and lets you use a JNIEnv* on the wrong thread. This is off by default,
but enabled for apps that don't have ICS as their targetSdkVersion.

Bug: 4772166
Change-Id: I20c403a8e63481a35d579d2bd3b121c80ec08f89
diff --git a/vm/CheckJni.cpp b/vm/CheckJni.cpp
index bd80f57..22ecce7 100644
--- a/vm/CheckJni.cpp
+++ b/vm/CheckJni.cpp
@@ -804,6 +804,11 @@
                     threadEnv->envThreadId, ((JNIEnvExt*) mEnv)->envThreadId);
             printWarn = true;
 
+            // If we're keeping broken code limping along, we need to suppress the abort...
+            if (!gDvmJni.workAroundAppJniBugs) {
+                printWarn = false;
+            }
+
             /* this is a bad idea -- need to throw as we exit, or abort func */
             //dvmThrowRuntimeException("invalid use of JNI env ptr");
         } else if (((JNIEnvExt*) mEnv)->self != dvmThreadSelf()) {
@@ -1382,7 +1387,7 @@
     CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, globalRef);
     if (globalRef != NULL && dvmGetJNIRefType(env, globalRef) != JNIGlobalRefType) {
         LOGW("JNI WARNING: DeleteGlobalRef on non-global %p (type=%d)",
-            globalRef, dvmGetJNIRefType(env, globalRef));
+                globalRef, dvmGetJNIRefType(env, globalRef));
         abortMaybe();
     } else {
         baseEnv(env)->DeleteGlobalRef(env, globalRef);
@@ -1399,7 +1404,7 @@
     CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, localRef);
     if (localRef != NULL && dvmGetJNIRefType(env, localRef) != JNILocalRefType) {
         LOGW("JNI WARNING: DeleteLocalRef on non-local %p (type=%d)",
-            localRef, dvmGetJNIRefType(env, localRef));
+                localRef, dvmGetJNIRefType(env, localRef));
         abortMaybe();
     } else {
         baseEnv(env)->DeleteLocalRef(env, localRef);
diff --git a/vm/Globals.h b/vm/Globals.h
index a47641b..397bfea 100644
--- a/vm/Globals.h
+++ b/vm/Globals.h
@@ -966,8 +966,8 @@
     bool warnOnly;
     bool forceCopy;
 
-    // Don't trust that we've been passed the right JNIEnv* for this thread.
-    bool alwaysCheckThread;
+    // Provide backwards compatibility for pre-ICS apps on ICS.
+    bool workAroundAppJniBugs;
 
     // Debugging help for third-party developers. Similar to -Xjnitrace.
     bool logThirdPartyJni;
diff --git a/vm/IndirectRefTable.cpp b/vm/IndirectRefTable.cpp
index fcf59af..6a50dab 100644
--- a/vm/IndirectRefTable.cpp
+++ b/vm/IndirectRefTable.cpp
@@ -194,6 +194,19 @@
     return true;
 }
 
+static int linearScan(IndirectRef iref, int bottomIndex, int topIndex, Object** table) {
+    for (int i = bottomIndex; i < topIndex; ++i) {
+        if (table[i] == reinterpret_cast<Object*>(iref)) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+bool IndirectRefTable::contains(IndirectRef iref) const {
+    return linearScan(iref, 0, segmentState.parts.topIndex, table) != -1;
+}
+
 /*
  * Remove "obj" from "pRef".  We extract the table offset bits from "iref"
  * and zap the corresponding entry, leaving a hole if it's not at the top.
@@ -219,6 +232,17 @@
     assert(segmentState.parts.numHoles >= prevState.parts.numHoles);
 
     int idx = extractIndex(iref);
+    bool fakeDirectReferenceHack = false;
+
+    if (indirectRefKind(iref) == kIndirectKindInvalid && gDvmJni.workAroundAppJniBugs) {
+        idx = linearScan(iref, bottomIndex, topIndex, table);
+        fakeDirectReferenceHack = true;
+        if (idx == -1) {
+            LOGW("trying to work around app JNI bugs, but didn't find %p in table!", iref);
+            return false;
+        }
+    }
+
     if (idx < bottomIndex) {
         /* wrong segment */
         LOGV("Attempt to remove index outside index area (%d vs %d-%d)",
@@ -237,10 +261,9 @@
          * Top-most entry.  Scan up and consume holes.  No need to NULL
          * out the entry, since the test vs. topIndex will catch it.
          */
-        if (!checkEntry("remove", iref, idx)) {
+        if (fakeDirectReferenceHack == false && !checkEntry("remove", iref, idx)) {
             return false;
         }
-        updateSlotRemove(idx);
 
 #ifndef NDEBUG
         table[idx] = (Object*)0xd3d3d3d3;
@@ -274,10 +297,9 @@
             LOGV("--- WEIRD: removing null entry %d", idx);
             return false;
         }
-        if (!checkEntry("remove", iref, idx)) {
+        if (fakeDirectReferenceHack == false && !checkEntry("remove", iref, idx)) {
             return false;
         }
-        updateSlotRemove(idx);
 
         table[idx] = NULL;
         segmentState.parts.numHoles++;
diff --git a/vm/IndirectRefTable.h b/vm/IndirectRefTable.h
index c8d5ab5..27e70f1 100644
--- a/vm/IndirectRefTable.h
+++ b/vm/IndirectRefTable.h
@@ -239,6 +239,9 @@
         return table[extractIndex(iref)];
     }
 
+    // TODO: only used for workAroundAppJniBugs support.
+    bool contains(IndirectRef iref) const;
+
     /*
      * Remove an existing entry.
      *
@@ -325,12 +328,6 @@
         }
     }
 
-    /*
-     * Update extended debug info when an entry is removed.
-     */
-    void updateSlotRemove(int slot) {
-    }
-
     /* extra debugging checks */
     bool getChecked(IndirectRef) const;
     bool checkEntry(const char*, IndirectRef, int) const;
diff --git a/vm/Jni.cpp b/vm/Jni.cpp
index f1f4ae7..814a657 100644
--- a/vm/Jni.cpp
+++ b/vm/Jni.cpp
@@ -197,7 +197,9 @@
 
 static inline Thread* self(JNIEnv* env) {
     Thread* envSelf = ((JNIEnvExt*) env)->self;
-    Thread* self = gDvmJni.alwaysCheckThread ? dvmThreadSelf() : envSelf;
+    // When emulating direct pointers with indirect references, it's critical
+    // that we use the correct per-thread indirect reference table.
+    Thread* self = gDvmJni.workAroundAppJniBugs ? dvmThreadSelf() : envSelf;
     if (self != envSelf) {
         LOGE("JNI ERROR: env->self != thread-self (%p vs. %p); auto-correcting",
                 envSelf, self);
@@ -367,6 +369,10 @@
         }
     case kIndirectKindInvalid:
     default:
+        if (gDvmJni.workAroundAppJniBugs) {
+            // Assume an invalid local reference is actually a direct pointer.
+            return reinterpret_cast<Object*>(jobj);
+        }
         LOGW("Invalid indirect reference %p in decodeIndirectRef", jobj);
         dvmAbort();
         return kInvalidIndirectRefObject;
@@ -421,6 +427,10 @@
     }
 #endif
 
+    if (gDvmJni.workAroundAppJniBugs) {
+        // Hand out direct pointers to support broken old apps.
+        return reinterpret_cast<jobject>(obj);
+    }
     return jobj;
 }
 
@@ -703,8 +713,11 @@
     assert(jobj != NULL);
 
     Object* obj = dvmDecodeIndirectRef(env, jobj);
-
-    if (obj == kInvalidIndirectRefObject) {
+    if (obj == reinterpret_cast<Object*>(jobj) && gDvmJni.workAroundAppJniBugs) {
+        // If we're handing out direct pointers, check whether 'jobj' is a direct reference
+        // to a local reference.
+        return getLocalRefTable(env)->contains(jobj) ? JNILocalRefType : JNIInvalidRefType;
+    } else if (obj == kInvalidIndirectRefObject) {
         return JNIInvalidRefType;
     } else {
         return (jobjectRefType) indirectRefKind(jobj);
@@ -3517,8 +3530,6 @@
                     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 {
diff --git a/vm/native/dalvik_system_VMRuntime.cpp b/vm/native/dalvik_system_VMRuntime.cpp
index e4482b4..6bd5333 100644
--- a/vm/native/dalvik_system_VMRuntime.cpp
+++ b/vm/native/dalvik_system_VMRuntime.cpp
@@ -190,9 +190,12 @@
 {
     // This is the target SDK version of the app we're about to run.
     // Note that this value may be CUR_DEVELOPMENT (10000).
+    // Note that this value may be 0, meaning "current".
     int targetSdkVersion = args[1];
-    if (targetSdkVersion <= 13 /* honeycomb-mr2 */) {
-        // TODO: turn on compatibility stuff when it's written!
+    if (targetSdkVersion > 0 && targetSdkVersion <= 13 /* honeycomb-mr2 */) {
+        // TODO: running with CheckJNI should override this and force you to obey the strictest rules.
+        LOGI("Turning on JNI app bug workarounds for target SDK version %i...", targetSdkVersion);
+        gDvmJni.workAroundAppJniBugs = true;
     }
     RETURN_VOID();
 }