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();
}