Improve JniAbort (and JNI tests).
This has been on my to-do list for a while, but it actually bit people
in the ass yesterday. This change enables us to write a lot more (and
better) tests, but for now I've just improved the tests that already
existed.
Change-Id: I04a18656de60b47e5a6b5777204c144209d1448e
diff --git a/src/check_jni.cc b/src/check_jni.cc
index fb7fa02..4da4b37 100644
--- a/src/check_jni.cc
+++ b/src/check_jni.cc
@@ -33,19 +33,19 @@
namespace art {
-void JniAbort(const char* jni_function_name) {
+static void JniAbort(const char* jni_function_name, const char* msg) {
Thread* self = Thread::Current();
Method* current_method = self->GetCurrentMethod();
std::ostringstream os;
- os << "Aborting because JNI app bug detected (see above for details)";
+ os << "JNI DETECTED ERROR IN APPLICATION: " << msg;
if (jni_function_name != NULL) {
- os << "\n in call to " << jni_function_name;
+ os << "\n in call to " << jni_function_name;
}
// TODO: is this useful given that we're about to dump the calling thread's stack?
if (current_method != NULL) {
- os << "\n from " << PrettyMethod(current_method);
+ os << "\n from " << PrettyMethod(current_method);
}
os << "\n";
self->Dump(os);
@@ -59,6 +59,19 @@
}
}
+static void JniAbortV(const char* jni_function_name, const char* fmt, va_list ap) {
+ std::string msg;
+ StringAppendV(&msg, fmt, ap);
+ JniAbort(jni_function_name, msg.c_str());
+}
+
+void JniAbortF(const char* jni_function_name, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ JniAbortV(jni_function_name, fmt, args);
+ va_end(args);
+}
+
/*
* ===========================================================================
* JNI function helpers
@@ -75,32 +88,28 @@
return reinterpret_cast<T>(ts.Self()->DecodeJObject(obj));
}
-/*
- * Hack to allow forcecopy to work with jniGetNonMovableArrayElements.
- * The code deliberately uses an invalid sequence of operations, so we
- * need to pass it through unmodified. Review that code before making
- * any changes here.
- */
+// Hack to allow forcecopy to work with jniGetNonMovableArrayElements.
+// The code deliberately uses an invalid sequence of operations, so we
+// need to pass it through unmodified. Review that code before making
+// any changes here.
#define kNoCopyMagic 0xd5aab57f
-/*
- * Flags passed into ScopedCheck.
- */
+// Flags passed into ScopedCheck.
#define kFlag_Default 0x0000
-#define kFlag_CritBad 0x0000 /* calling while in critical is bad */
-#define kFlag_CritOkay 0x0001 /* ...okay */
-#define kFlag_CritGet 0x0002 /* this is a critical "get" */
-#define kFlag_CritRelease 0x0003 /* this is a critical "release" */
-#define kFlag_CritMask 0x0003 /* bit mask to get "crit" value */
+#define kFlag_CritBad 0x0000 // Calling while in critical is not allowed.
+#define kFlag_CritOkay 0x0001 // Calling while in critical is allowed.
+#define kFlag_CritGet 0x0002 // This is a critical "get".
+#define kFlag_CritRelease 0x0003 // This is a critical "release".
+#define kFlag_CritMask 0x0003 // Bit mask to get "crit" value.
-#define kFlag_ExcepBad 0x0000 /* raised exceptions are bad */
-#define kFlag_ExcepOkay 0x0004 /* ...okay */
+#define kFlag_ExcepBad 0x0000 // Raised exceptions are not allowed.
+#define kFlag_ExcepOkay 0x0004 // Raised exceptions are allowed.
-#define kFlag_Release 0x0010 /* are we in a non-critical release function? */
-#define kFlag_NullableUtf 0x0020 /* are our UTF parameters nullable? */
+#define kFlag_Release 0x0010 // Are we in a non-critical release function?
+#define kFlag_NullableUtf 0x0020 // Are our UTF parameters nullable?
-#define kFlag_Invocation 0x8000 /* Part of the invocation interface (JavaVM*) */
+#define kFlag_Invocation 0x8000 // Part of the invocation interface (JavaVM*).
#define kFlag_ForceTrace 0x80000000 // Add this to a JNI function's flags if you want to trace every call.
@@ -116,7 +125,7 @@
NULL
};
-bool ShouldTrace(JavaVMExt* vm, const Method* method) {
+static bool ShouldTrace(JavaVMExt* vm, const Method* method) {
// If both "-Xcheck:jni" and "-Xjnitrace:" are enabled, we print trace messages
// when a native method that matches the -Xjnitrace argument calls a JNI function
// such as NewByteArray.
@@ -162,9 +171,10 @@
// circumstances, but this is incorrect.
void CheckClassName(const char* class_name) {
if (!IsValidJniClassName(class_name)) {
- LOG(ERROR) << "JNI ERROR: illegal class name '" << class_name << "' (" << function_name_ << ")\n"
- << " (should be of the form 'java/lang/String', [Ljava/lang/String;' or '[[B')\n";
- JniAbort();
+ JniAbortF(function_name_,
+ "illegal class name '%s'\n"
+ " (should be of the form 'package/Class', [Lpackage/Class;' or '[[B')",
+ class_name);
}
}
@@ -184,36 +194,33 @@
if (!field_type->IsPrimitive()) {
if (java_object != NULL) {
Object* obj = Decode<Object*>(ts, java_object);
- /*
- * If java_object is a weak global ref whose referent has been cleared,
- * obj will be NULL. Otherwise, obj should always be non-NULL
- * and valid.
- */
+ // If java_object is a weak global ref whose referent has been cleared,
+ // obj will be NULL. Otherwise, obj should always be non-NULL
+ // and valid.
if (!Runtime::Current()->GetHeap()->IsHeapAddress(obj)) {
- LOG(ERROR) << "JNI ERROR: field operation on invalid " << GetIndirectRefKind(java_object) << ": " << java_object;
- JniAbort();
+ JniAbortF(function_name_, "field operation on invalid %s: %p",
+ ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object);
return;
} else {
if (!obj->InstanceOf(field_type)) {
- LOG(ERROR) << "JNI ERROR: attempt to set field " << PrettyField(f) << " with value of wrong type: " << PrettyTypeOf(obj);
- JniAbort();
+ JniAbortF(function_name_, "attempt to set field %s with value of wrong type: %s",
+ PrettyField(f).c_str(), PrettyTypeOf(obj).c_str());
return;
}
}
}
} else if (field_type != Runtime::Current()->GetClassLinker()->FindPrimitiveClass(prim)) {
- LOG(ERROR) << "JNI ERROR: attempt to set field " << PrettyField(f) << " with value of wrong type: " << prim;
- JniAbort();
+ JniAbortF(function_name_, "attempt to set field %s with value of wrong type: %c",
+ PrettyField(f).c_str(), prim);
return;
}
- if (isStatic && !f->IsStatic()) {
+ if (isStatic != f->IsStatic()) {
if (isStatic) {
- LOG(ERROR) << "JNI ERROR: accessing non-static field " << PrettyField(f) << " as static";
+ JniAbortF(function_name_, "accessing non-static field %s as static", PrettyField(f).c_str());
} else {
- LOG(ERROR) << "JNI ERROR: accessing static field " << PrettyField(f) << " as non-static";
+ JniAbortF(function_name_, "accessing static field %s as non-static", PrettyField(f).c_str());
}
- JniAbort();
return;
}
}
@@ -228,8 +235,8 @@
Object* o = Decode<Object*>(ts, java_object);
if (o == NULL || !Runtime::Current()->GetHeap()->IsHeapAddress(o)) {
- LOG(ERROR) << "JNI ERROR: field operation on invalid " << GetIndirectRefKind(java_object) << ": " << java_object;
- JniAbort();
+ JniAbortF(function_name_, "field operation on invalid %s: %p",
+ ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object);
return;
}
@@ -240,9 +247,8 @@
Class* c = o->GetClass();
FieldHelper fh(f);
if (c->FindInstanceField(fh.GetName(), fh.GetTypeDescriptor()) == NULL) {
- LOG(ERROR) << "JNI ERROR: jfieldID " << PrettyField(f)
- << " not valid for an object of class " << PrettyTypeOf(o);
- JniAbort();
+ JniAbortF(function_name_, "jfieldID %s not valid for an object of class %s",
+ PrettyField(f).c_str(), PrettyTypeOf(o).c_str());
}
}
@@ -251,8 +257,7 @@
*/
void CheckNonNull(const void* ptr) {
if (ptr == NULL) {
- LOG(ERROR) << "JNI ERROR: invalid null pointer";
- JniAbort();
+ JniAbortF(function_name_, "non-nullable argument was NULL");
}
}
@@ -267,18 +272,17 @@
return;
}
if (*expectedType != MethodHelper(m).GetShorty()[0]) {
- LOG(ERROR) << "JNI ERROR: the return type of " << function_name_ << " does not match "
- << PrettyMethod(m);
- JniAbort();
- } else if (isStatic && !m->IsStatic()) {
+ JniAbortF(function_name_, "the return type of %s does not match %s",
+ function_name_, PrettyMethod(m).c_str());
+ }
+ if (isStatic != m->IsStatic()) {
if (isStatic) {
- LOG(ERROR) << "JNI ERROR: calling non-static method "
- << PrettyMethod(m) << " with " << function_name_;
+ JniAbortF(function_name_, "calling non-static method %s with %s",
+ PrettyMethod(m).c_str(), function_name_);
} else {
- LOG(ERROR) << "JNI ERROR: calling static method "
- << PrettyMethod(m) << " with non-static " << function_name_;
+ JniAbortF(function_name_, "calling static method %s with %s",
+ PrettyMethod(m).c_str(), function_name_);
}
- JniAbort();
}
}
@@ -295,8 +299,8 @@
return;
}
if (f->GetDeclaringClass() != c) {
- LOG(ERROR) << "JNI ERROR: static jfieldID " << fid << " not valid for class " << PrettyClass(c);
- JniAbort();
+ JniAbortF(function_name_, "static jfieldID %p not valid for class %s",
+ fid, PrettyClass(c).c_str());
}
}
@@ -317,8 +321,8 @@
}
Class* c = Decode<Class*>(ts, java_class);
if (!c->IsAssignableFrom(m->GetDeclaringClass())) {
- LOG(ERROR) << "JNI ERROR: can't call static " << PrettyMethod(m) << " on class " << PrettyClass(c);
- JniAbort();
+ JniAbortF(function_name_, "can't call static %s on class %s",
+ PrettyMethod(m).c_str(), PrettyClass(c).c_str());
}
}
@@ -337,8 +341,8 @@
}
Object* o = Decode<Object*>(ts, java_object);
if (!o->InstanceOf(m->GetDeclaringClass())) {
- LOG(ERROR) << "JNI ERROR: can't call " << PrettyMethod(m) << " on instance of " << PrettyTypeOf(o);
- JniAbort();
+ JniAbortF(function_name_, "can't call %s on instance of %s",
+ PrettyMethod(m).c_str(), PrettyTypeOf(o).c_str());
}
}
@@ -500,8 +504,7 @@
} else if (ch == '.') {
msg += "...";
} else {
- LOG(ERROR) << "unknown trace format specifier: " << ch;
- JniAbort();
+ JniAbortF(function_name_, "unknown trace format specifier: %c", ch);
return;
}
if (*fmt) {
@@ -603,17 +606,15 @@
}
if (java_object == NULL) {
- LOG(ERROR) << "JNI ERROR: " << function_name_ << " received null " << what;
- JniAbort();
+ JniAbortF(function_name_, "%s received null %s", function_name_, what);
return false;
}
ScopedJniThreadState ts(env_);
Object* obj = Decode<Object*>(ts, java_object);
if (!Runtime::Current()->GetHeap()->IsHeapAddress(obj)) {
- LOG(ERROR) << "JNI ERROR: " << what << " is an invalid " << GetIndirectRefKind(java_object) << ": "
- << java_object << " (" << obj << ")";
- JniAbort();
+ JniAbortF(function_name_, "%s is an invalid %s: %p (%p)",
+ what, ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object, obj);
return false;
}
@@ -635,8 +636,7 @@
break;
}
if (!okay) {
- LOG(ERROR) << "JNI ERROR: " << what << " has wrong type: " << PrettyTypeOf(obj);
- JniAbort();
+ JniAbortF(function_name_, "%s has wrong type: %s", what, PrettyTypeOf(obj).c_str());
return false;
}
@@ -662,39 +662,34 @@
*/
void CheckArray(jarray java_array) {
if (java_array == NULL) {
- LOG(ERROR) << "JNI ERROR: received null array";
- JniAbort();
+ JniAbortF(function_name_, "jarray was NULL");
return;
}
ScopedJniThreadState ts(env_);
Array* a = Decode<Array*>(ts, java_array);
if (!Runtime::Current()->GetHeap()->IsHeapAddress(a)) {
- LOG(ERROR) << "JNI ERROR: jarray is an invalid " << GetIndirectRefKind(java_array) << ": " << reinterpret_cast<void*>(java_array) << " (" << a << ")";
- JniAbort();
+ JniAbortF(function_name_, "jarray is an invalid %s: %p (%p)",
+ ToStr<IndirectRefKind>(GetIndirectRefKind(java_array)).c_str(), java_array, a);
} else if (!a->IsArrayInstance()) {
- LOG(ERROR) << "JNI ERROR: jarray argument has non-array type: " << PrettyTypeOf(a);
- JniAbort();
+ JniAbortF(function_name_, "jarray argument has non-array type: %s", PrettyTypeOf(a).c_str());
}
}
void CheckLengthPositive(jsize length) {
if (length < 0) {
- LOG(ERROR) << "JNI ERROR: negative jsize: " << length;
- JniAbort();
+ JniAbortF(function_name_, "negative jsize: %d", length);
}
}
Field* CheckFieldID(jfieldID fid) {
if (fid == NULL) {
- LOG(ERROR) << "JNI ERROR: null jfieldID";
- JniAbort();
+ JniAbortF(function_name_, "jfieldID was NULL");
return NULL;
}
Field* f = DecodeField(fid);
if (!Runtime::Current()->GetHeap()->IsHeapAddress(f)) {
- LOG(ERROR) << "JNI ERROR: invalid jfieldID: " << fid;
- JniAbort();
+ JniAbortF(function_name_, "invalid jfieldID: %p", fid);
return NULL;
}
return f;
@@ -702,14 +697,12 @@
Method* CheckMethodID(jmethodID mid) {
if (mid == NULL) {
- LOG(ERROR) << "JNI ERROR: null jmethodID";
- JniAbort();
+ JniAbortF(function_name_, "jmethodID was NULL");
return NULL;
}
Method* m = DecodeMethod(mid);
if (!Runtime::Current()->GetHeap()->IsHeapAddress(m)) {
- LOG(ERROR) << "JNI ERROR: invalid jmethodID: " << mid;
- JniAbort();
+ JniAbortF(function_name_, "invalid jmethodID: %p", mid);
return NULL;
}
return m;
@@ -731,8 +724,8 @@
Object* o = Decode<Object*>(ts, java_object);
if (!Runtime::Current()->GetHeap()->IsHeapAddress(o)) {
// TODO: when we remove work_around_app_jni_bugs, this should be impossible.
- LOG(ERROR) << "JNI ERROR: native code passing in reference to invalid " << GetIndirectRefKind(java_object) << ": " << java_object;
- JniAbort();
+ JniAbortF(function_name_, "native code passing in reference to invalid %s: %p",
+ ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object);
}
}
@@ -742,58 +735,52 @@
*/
void CheckReleaseMode(jint mode) {
if (mode != 0 && mode != JNI_COMMIT && mode != JNI_ABORT) {
- LOG(ERROR) << "JNI ERROR: bad value for release mode: " << mode;
- JniAbort();
+ JniAbortF(function_name_, "bad value for release mode: %d", mode);
}
}
void CheckThread(int flags) {
Thread* self = Thread::Current();
if (self == NULL) {
- LOG(ERROR) << "JNI ERROR: a thread is making JNI calls without being attached";
- JniAbort();
+ JniAbortF(function_name_, "a thread (tid %d) is making JNI calls without being attached", GetTid());
return;
}
// Get the *correct* JNIEnv by going through our TLS pointer.
JNIEnvExt* threadEnv = self->GetJniEnv();
- /*
- * Verify that the current thread is (a) attached and (b) associated with
- * this particular instance of JNIEnv.
- */
+ // Verify that the current thread is (a) attached and (b) associated with
+ // this particular instance of JNIEnv.
if (env_ != threadEnv) {
- LOG(ERROR) << "JNI ERROR: thread " << *self << " using JNIEnv* from thread " << *env_->self;
- // If we're keeping broken code limping along, we need to suppress the abort...
- if (!vm_->work_around_app_jni_bugs) {
- JniAbort();
+ if (vm_->work_around_app_jni_bugs) {
+ // If we're keeping broken code limping along, we need to suppress the abort...
+ LOG(ERROR) << "APP BUG DETECTED: thread " << *self << " using JNIEnv* from thread " << *env_->self;
+ } else {
+ JniAbortF(function_name_, "thread %s using JNIEnv* from thread %s",
+ ToStr<Thread>(*self).c_str(), ToStr<Thread>(*env_->self).c_str());
return;
}
}
- /*
- * Verify that, if this thread previously made a critical "get" call, we
- * do the corresponding "release" call before we try anything else.
- */
+ // Verify that, if this thread previously made a critical "get" call, we
+ // do the corresponding "release" call before we try anything else.
switch (flags & kFlag_CritMask) {
case kFlag_CritOkay: // okay to call this method
break;
case kFlag_CritBad: // not okay to call
if (threadEnv->critical) {
- LOG(ERROR) << "JNI ERROR: thread " << *self << " using JNI after critical get";
- JniAbort();
+ JniAbortF(function_name_, "thread %s using JNI after critical get", ToStr<Thread>(*self).c_str());
return;
}
break;
case kFlag_CritGet: // this is a "get" call
- /* don't check here; we allow nested gets */
+ // Don't check here; we allow nested gets.
threadEnv->critical++;
break;
case kFlag_CritRelease: // this is a "release" call
threadEnv->critical--;
if (threadEnv->critical < 0) {
- LOG(ERROR) << "JNI ERROR: thread " << *self << " called too many critical releases";
- JniAbort();
+ JniAbortF(function_name_, "thread %s called too many critical releases", ToStr<Thread>(*self).c_str());
return;
}
break;
@@ -801,31 +788,27 @@
LOG(FATAL) << "Bad flags (internal error): " << flags;
}
- /*
- * Verify that, if an exception has been raised, the native code doesn't
- * make any JNI calls other than the Exception* methods.
- */
+ // Verify that, if an exception has been raised, the native code doesn't
+ // make any JNI calls other than the Exception* methods.
if ((flags & kFlag_ExcepOkay) == 0 && self->IsExceptionPending()) {
std::string type(PrettyTypeOf(self->GetException()));
- LOG(ERROR) << "JNI ERROR: JNI " << function_name_ << " called with " << type << " pending";
// TODO: write native code that doesn't require allocation for dumping an exception.
+ // TODO: do we care any more? art always dumps pending exceptions on aborting threads.
if (type != "java.lang.OutOfMemoryError") {
- LOG(ERROR) << "Pending exception is: ";
- LOG(ERROR) << jniGetStackTrace(env_);
+ JniAbortF(function_name_, "JNI %s called with pending exception: %s",
+ function_name_, type.c_str(), jniGetStackTrace(env_).c_str());
+ } else {
+ JniAbortF(function_name_, "JNI %s called with %s pending", function_name_, type.c_str());
}
- JniAbort();
return;
}
}
- /*
- * Verify that "bytes" points to valid Modified UTF-8 data.
- */
+ // Verifies that "bytes" points to valid Modified UTF-8 data.
void CheckUtfString(const char* bytes, bool nullable) {
if (bytes == NULL) {
if (!nullable) {
- LOG(ERROR) << "JNI ERROR: non-nullable const char* was NULL";
- JniAbort();
+ JniAbortF(function_name_, "non-nullable const char* was NULL");
return;
}
return;
@@ -834,10 +817,9 @@
const char* errorKind = NULL;
uint8_t utf8 = CheckUtfBytes(bytes, &errorKind);
if (errorKind != NULL) {
- LOG(ERROR) << "JNI ERROR: input is not valid Modified UTF-8: "
- << "illegal " << errorKind << " byte " << StringPrintf("%#x", utf8) << "\n"
- << " string: '" << bytes << "'";
- JniAbort();
+ JniAbortF(function_name_,
+ "input is not valid Modified UTF-8: illegal %s byte %#x\n"
+ " string: '%s'", errorKind, utf8, bytes);
return;
}
}
@@ -891,10 +873,6 @@
return 0;
}
- void JniAbort() {
- ::art::JniAbort(function_name_);
- }
-
JNIEnvExt* env_;
JavaVMExt* vm_;
const char* function_name_;
@@ -948,16 +926,16 @@
size_t newLen = ActualLength(len);
uint8_t* newBuf = DebugAlloc(newLen);
- /* fill it in with a pattern */
+ // Fill it in with a pattern.
uint16_t* pat = reinterpret_cast<uint16_t*>(newBuf);
for (size_t i = 0; i < newLen / 2; i++) {
*pat++ = kGuardPattern;
}
- /* copy the data in; note "len" could be zero */
+ // Copy the data in; note "len" could be zero.
memcpy(newBuf + kGuardLen / 2, buf, len);
- /* if modification is not expected, grab a checksum */
+ // If modification is not expected, grab a checksum.
uLong adler = 0;
if (!modOkay) {
adler = adler32(0L, Z_NULL, 0);
@@ -996,65 +974,57 @@
const uint8_t* fullBuf = ActualBuffer(dataBuf);
const GuardedCopy* pExtra = GuardedCopy::FromData(dataBuf);
- /*
- * Before we do anything with "pExtra", check the magic number. We
- * do the check with memcmp rather than "==" in case the pointer is
- * unaligned. If it points to completely bogus memory we're going
- * to crash, but there's no easy way around that.
- */
+ // Before we do anything with "pExtra", check the magic number. We
+ // do the check with memcmp rather than "==" in case the pointer is
+ // unaligned. If it points to completely bogus memory we're going
+ // to crash, but there's no easy way around that.
if (memcmp(&pExtra->magic, &kMagicCmp, 4) != 0) {
uint8_t buf[4];
memcpy(buf, &pExtra->magic, 4);
- LOG(ERROR) << StringPrintf("JNI: guard magic does not match "
- "(found 0x%02x%02x%02x%02x) -- incorrect data pointer %p?",
- buf[3], buf[2], buf[1], buf[0], dataBuf); /* assume little endian */
- JniAbort(functionName);
+ JniAbortF(functionName,
+ "guard magic does not match (found 0x%02x%02x%02x%02x) -- incorrect data pointer %p?",
+ buf[3], buf[2], buf[1], buf[0], dataBuf); // Assumes little-endian.
}
size_t len = pExtra->original_length;
- /* check bottom half of guard; skip over optional checksum storage */
+ // Check bottom half of guard; skip over optional checksum storage.
const uint16_t* pat = reinterpret_cast<const uint16_t*>(fullBuf);
for (size_t i = sizeof(GuardedCopy) / 2; i < (kGuardLen / 2 - sizeof(GuardedCopy)) / 2; i++) {
if (pat[i] != kGuardPattern) {
- LOG(ERROR) << "JNI: guard pattern(1) disturbed at " << reinterpret_cast<const void*>(fullBuf) << " + " << (i*2);
- JniAbort(functionName);
+ JniAbortF(functionName, "guard pattern(1) disturbed at %p +%d", fullBuf, i*2);
}
}
int offset = kGuardLen / 2 + len;
if (offset & 0x01) {
- /* odd byte; expected value depends on endian-ness of host */
+ // Odd byte; expected value depends on endian.
const uint16_t patSample = kGuardPattern;
- if (fullBuf[offset] != reinterpret_cast<const uint8_t*>(&patSample)[1]) {
- LOG(ERROR) << "JNI: guard pattern disturbed in odd byte after "
- << reinterpret_cast<const void*>(fullBuf) << " (+" << offset << ") "
- << StringPrintf("0x%02x 0x%02x", fullBuf[offset], ((const uint8_t*) &patSample)[1]);
- JniAbort(functionName);
+ uint8_t expected_byte = reinterpret_cast<const uint8_t*>(&patSample)[1];
+ if (fullBuf[offset] != expected_byte) {
+ JniAbortF(functionName, "guard pattern disturbed in odd byte after %p +%d 0x%02x 0x%02x",
+ fullBuf, offset, fullBuf[offset], expected_byte);
}
offset++;
}
- /* check top half of guard */
+ // Check top half of guard.
pat = reinterpret_cast<const uint16_t*>(fullBuf + offset);
for (size_t i = 0; i < kGuardLen / 4; i++) {
if (pat[i] != kGuardPattern) {
- LOG(ERROR) << "JNI: guard pattern(2) disturbed at " << reinterpret_cast<const void*>(fullBuf) << " + " << (offset + i*2);
- JniAbort(functionName);
+ JniAbortF(functionName, "guard pattern(2) disturbed at %p +%d", fullBuf, offset + i*2);
}
}
- /*
- * If modification is not expected, verify checksum. Strictly speaking
- * this is wrong: if we told the client that we made a copy, there's no
- * reason they can't alter the buffer.
- */
+ // If modification is not expected, verify checksum. Strictly speaking
+ // this is wrong: if we told the client that we made a copy, there's no
+ // reason they can't alter the buffer.
if (!modOkay) {
uLong adler = adler32(0L, Z_NULL, 0);
adler = adler32(adler, (const Bytef*)dataBuf, len);
if (pExtra->adler != adler) {
- LOG(ERROR) << StringPrintf("JNI: buffer modified (0x%08lx vs 0x%08lx) at addr %p", pExtra->adler, adler, dataBuf);
- JniAbort(functionName);
+ JniAbortF(functionName, "buffer modified (0x%08lx vs 0x%08lx) at address %p",
+ pExtra->adler, adler, dataBuf);
}
}
}
@@ -1099,7 +1069,7 @@
* Create a guarded copy of a primitive array. Modifications to the copied
* data are allowed. Returns a pointer to the copied data.
*/
-void* CreateGuardedPACopy(JNIEnv* env, const jarray java_array, jboolean* isCopy) {
+static void* CreateGuardedPACopy(JNIEnv* env, const jarray java_array, jboolean* isCopy) {
ScopedJniThreadState ts(env);
Array* a = Decode<Array*>(ts, java_array);
@@ -1116,7 +1086,7 @@
* Perform the array "release" operation, which may or may not copy data
* back into the managed heap, and may or may not release the underlying storage.
*/
-void ReleaseGuardedPACopy(JNIEnv* env, jarray java_array, void* dataBuf, int mode) {
+static void ReleaseGuardedPACopy(JNIEnv* env, jarray java_array, void* dataBuf, int mode) {
if (reinterpret_cast<uintptr_t>(dataBuf) == kNoCopyMagic) {
return;
}
@@ -1249,8 +1219,8 @@
static void DeleteGlobalRef(JNIEnv* env, jobject globalRef) {
CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, globalRef);
if (globalRef != NULL && GetIndirectRefKind(globalRef) != kGlobal) {
- LOG(ERROR) << "JNI ERROR: DeleteGlobalRef on " << GetIndirectRefKind(globalRef) << ": " << globalRef;
- JniAbort(__FUNCTION__);
+ JniAbortF(__FUNCTION__, "DeleteGlobalRef on %s: %p",
+ ToStr<IndirectRefKind>(GetIndirectRefKind(globalRef)).c_str(), globalRef);
} else {
baseEnv(env)->DeleteGlobalRef(env, globalRef);
CHECK_JNI_EXIT_VOID();
@@ -1260,8 +1230,8 @@
static void DeleteWeakGlobalRef(JNIEnv* env, jweak weakGlobalRef) {
CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, weakGlobalRef);
if (weakGlobalRef != NULL && GetIndirectRefKind(weakGlobalRef) != kWeakGlobal) {
- LOG(ERROR) << "JNI ERROR: DeleteWeakGlobalRef on " << GetIndirectRefKind(weakGlobalRef) << ": " << weakGlobalRef;
- JniAbort(__FUNCTION__);
+ JniAbortF(__FUNCTION__, "DeleteWeakGlobalRef on %s: %p",
+ ToStr<IndirectRefKind>(GetIndirectRefKind(weakGlobalRef)).c_str(), weakGlobalRef);
} else {
baseEnv(env)->DeleteWeakGlobalRef(env, weakGlobalRef);
CHECK_JNI_EXIT_VOID();
@@ -1271,8 +1241,8 @@
static void DeleteLocalRef(JNIEnv* env, jobject localRef) {
CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, localRef);
if (localRef != NULL && GetIndirectRefKind(localRef) != kLocal && !IsSirtLocalRef(env, localRef)) {
- LOG(ERROR) << "JNI ERROR: DeleteLocalRef on " << GetIndirectRefKind(localRef) << ": " << localRef;
- JniAbort(__FUNCTION__);
+ JniAbortF(__FUNCTION__, "DeleteLocalRef on %s: %p",
+ ToStr<IndirectRefKind>(GetIndirectRefKind(localRef)).c_str(), localRef);
} else {
baseEnv(env)->DeleteLocalRef(env, localRef);
CHECK_JNI_EXIT_VOID();
@@ -1605,7 +1575,7 @@
force_copy = sc.ForceCopy();
no_copy = 0;
if (force_copy && isCopy != NULL) {
- /* capture this before the base call tramples on it */
+ // Capture this before the base call tramples on it.
no_copy = *reinterpret_cast<uint32_t*>(isCopy);
}
}
@@ -1662,7 +1632,7 @@
GET_PRIMITIVE_ARRAY_REGION(_ctype, _jname); \
SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname);
-/* TODO: verify primitive array type matches call type */
+// TODO: verify primitive array type matches call type.
PRIMITIVE_ARRAY_FUNCTIONS(jboolean, Boolean, 'Z');
PRIMITIVE_ARRAY_FUNCTIONS(jbyte, Byte, 'B');
PRIMITIVE_ARRAY_FUNCTIONS(jchar, Char, 'C');
@@ -1781,12 +1751,10 @@
static jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) {
CHECK_JNI_ENTRY(kFlag_Default, "EpJ", env, address, capacity);
if (address == NULL) {
- LOG(ERROR) << "JNI ERROR: non-nullable address is NULL";
- JniAbort(__FUNCTION__);
+ JniAbortF(__FUNCTION__, "non-nullable address is NULL");
}
if (capacity <= 0) {
- LOG(ERROR) << "JNI ERROR: capacity must be greater than 0: " << capacity;
- JniAbort(__FUNCTION__);
+ JniAbortF(__FUNCTION__, "capacity must be greater than 0: %d", capacity);
}
return CHECK_JNI_EXIT("L", baseEnv(env)->NewDirectByteBuffer(env, address, capacity));
}
diff --git a/src/common_test.h b/src/common_test.h
index 6b284fd..54c9bb7 100644
--- a/src/common_test.h
+++ b/src/common_test.h
@@ -548,11 +548,13 @@
EXPECT_TRUE(actual_.find(expected_text) != std::string::npos) << "\n"
<< "Expected to find: " << expected_text << "\n"
<< "In the output : " << actual_;
+ actual_.clear();
}
private:
static void Hook(void* data, const std::string& reason) {
- *reinterpret_cast<std::string*>(data) = reason;
+ // We use += because when we're hooking the aborts like this, multiple problems can be found.
+ *reinterpret_cast<std::string*>(data) += reason;
}
JavaVMExt* vm_;
diff --git a/src/compiler_llvm/runtime_support_llvm.cc b/src/compiler_llvm/runtime_support_llvm.cc
index 2236525..27541c3 100644
--- a/src/compiler_llvm/runtime_support_llvm.cc
+++ b/src/compiler_llvm/runtime_support_llvm.cc
@@ -507,9 +507,8 @@
}
if (o == kInvalidIndirectRefObject) {
- LOG(ERROR) << "JNI ERROR (app bug): invalid reference returned from "
- << PrettyMethod(self->GetCurrentMethod());
- JniAbort(NULL);
+ JniAbortF(NULL, "invalid reference returned from %s",
+ PrettyMethod(self->GetCurrentMethod()).c_str());
}
// Make sure that the result is an instance of the type this
@@ -519,9 +518,8 @@
Class* return_type = mh.GetReturnType();
if (!o->InstanceOf(return_type)) {
- LOG(ERROR) << "JNI ERROR (app bug): attempt to return an instance of " << PrettyTypeOf(o)
- << " from " << PrettyMethod(m);
- JniAbort(NULL);
+ JniAbortF(NULL, "attempt to return an instance of %s from %s",
+ PrettyTypeOf(o).c_str(), PrettyMethod(m).c_str());
}
return o;
diff --git a/src/jni_compiler_test.cc b/src/jni_compiler_test.cc
index 61e1867..c5710f8 100644
--- a/src/jni_compiler_test.cc
+++ b/src/jni_compiler_test.cc
@@ -673,18 +673,17 @@
SetUpForTest(class_loader.get(), false, "instanceMethodThatShouldReturnClass", "()Ljava/lang/Class;",
reinterpret_cast<void*>(&Java_MyClassNatives_instanceMethodThatShouldReturnClass));
- {
- CheckJniAbortCatcher check_jni_abort_catcher;
- // TODO: check type of returns with portable JNI compiler.
- // This native method is bad, and tries to return a jstring as a jclass.
- env_->CallObjectMethod(jobj_, jmethod_);
- check_jni_abort_catcher.Check("java.lang.Class MyClassNatives.instanceMethodThatShouldReturnClass");
- }
-
CheckJniAbortCatcher check_jni_abort_catcher;
- // Here, we just call the method wrong; we should catch that too.
+ // TODO: check type of returns with portable JNI compiler.
+ // This native method is bad, and tries to return a jstring as a jclass.
+ env_->CallObjectMethod(jobj_, jmethod_);
+ check_jni_abort_catcher.Check("attempt to return an instance of java.lang.String from java.lang.Class MyClassNatives.instanceMethodThatShouldReturnClass()");
+
+ // Here, we just call the method incorrectly; we should catch that too.
env_->CallVoidMethod(jobj_, jmethod_);
- check_jni_abort_catcher.Check("Aborting because JNI app bug detected");
+ check_jni_abort_catcher.Check("attempt to return an instance of java.lang.String from java.lang.Class MyClassNatives.instanceMethodThatShouldReturnClass()");
+ env_->CallStaticVoidMethod(jklass_, jmethod_);
+ check_jni_abort_catcher.Check("calling non-static method java.lang.Class MyClassNatives.instanceMethodThatShouldReturnClass() with CallStaticVoidMethodV");
}
TEST_F(JniCompilerTest, UpcallReturnTypeChecking_Static) {
@@ -692,18 +691,17 @@
SetUpForTest(class_loader.get(), true, "staticMethodThatShouldReturnClass", "()Ljava/lang/Class;",
reinterpret_cast<void*>(&Java_MyClassNatives_staticMethodThatShouldReturnClass));
- {
- CheckJniAbortCatcher check_jni_abort_catcher;
- // TODO: check type of returns with portable JNI compiler.
- // This native method is bad, and tries to return a jstring as a jclass.
- env_->CallStaticObjectMethod(jklass_, jmethod_);
- check_jni_abort_catcher.Check("java.lang.Class MyClassNatives.staticMethodThatShouldReturnClass");
- }
-
CheckJniAbortCatcher check_jni_abort_catcher;
- // Here, we just call the method wrong; we should catch that too.
+ // TODO: check type of returns with portable JNI compiler.
+ // This native method is bad, and tries to return a jstring as a jclass.
+ env_->CallStaticObjectMethod(jklass_, jmethod_);
+ check_jni_abort_catcher.Check("attempt to return an instance of java.lang.String from java.lang.Class MyClassNatives.staticMethodThatShouldReturnClass()");
+
+ // Here, we just call the method incorrectly; we should catch that too.
+ env_->CallStaticVoidMethod(jklass_, jmethod_);
+ check_jni_abort_catcher.Check("attempt to return an instance of java.lang.String from java.lang.Class MyClassNatives.staticMethodThatShouldReturnClass()");
env_->CallVoidMethod(jobj_, jmethod_);
- check_jni_abort_catcher.Check("Aborting because JNI app bug detected");
+ check_jni_abort_catcher.Check("calling static method java.lang.Class MyClassNatives.staticMethodThatShouldReturnClass() with CallVoidMethodV");
}
// This should take jclass, but we're imitating a bug pattern.
@@ -722,7 +720,7 @@
CheckJniAbortCatcher check_jni_abort_catcher;
// We deliberately pass a bad second argument here.
env_->CallVoidMethod(jobj_, jmethod_, 123, env_->NewStringUTF("not a class!"));
- check_jni_abort_catcher.Check("Aborting because JNI app bug detected");
+ check_jni_abort_catcher.Check("bad arguments passed to void MyClassNatives.instanceMethodThatShouldTakeClass(int, java.lang.Class)");
}
TEST_F(JniCompilerTest, UpcallArgumentTypeChecking_Static) {
@@ -733,7 +731,7 @@
CheckJniAbortCatcher check_jni_abort_catcher;
// We deliberately pass a bad second argument here.
env_->CallStaticVoidMethod(jklass_, jmethod_, 123, env_->NewStringUTF("not a class!"));
- check_jni_abort_catcher.Check("Aborting because JNI app bug detected");
+ check_jni_abort_catcher.Check("bad arguments passed to void MyClassNatives.staticMethodThatShouldTakeClass(int, java.lang.Class)");
}
} // namespace art
diff --git a/src/jni_internal.cc b/src/jni_internal.cc
index 82fba54..ed46837 100644
--- a/src/jni_internal.cc
+++ b/src/jni_internal.cc
@@ -302,7 +302,7 @@
if (error_count > 0) {
// TODO: pass the JNI function name (such as "CallVoidMethodV") through so we can call JniAbort
// with an argument.
- JniAbort(NULL);
+ JniAbortF(NULL, "bad arguments passed to %s (see above for details)", PrettyMethod(m).c_str());
}
}
diff --git a/src/jni_internal.h b/src/jni_internal.h
index e62d62a..be5bca0 100644
--- a/src/jni_internal.h
+++ b/src/jni_internal.h
@@ -46,7 +46,7 @@
class Thread;
void SetJniGlobalsMax(size_t max);
-void JniAbort(const char* jni_function_name);
+void JniAbortF(const char* jni_function_name, const char* fmt, ...);
void* FindNativeMethod(Thread* thread);
void RegisterNativeMethods(JNIEnv* env, const char* jni_class_name, const JNINativeMethod* methods, size_t method_count);
diff --git a/src/jni_internal_test.cc b/src/jni_internal_test.cc
index 2ca2b3c..9383db3 100644
--- a/src/jni_internal_test.cc
+++ b/src/jni_internal_test.cc
@@ -547,28 +547,33 @@
EXPECT_CLASS_FOUND("java/lang/String");
// ...for arrays too, where you must include "L;".
EXPECT_CLASS_FOUND("[Ljava/lang/String;");
+ // Primitive arrays are okay too, if the primitive type is valid.
+ EXPECT_CLASS_FOUND("[C");
{
- CheckJniAbortCatcher check_jni_abort_catcher;
-
// We support . as well as / for compatibility, if -Xcheck:jni is off.
+ CheckJniAbortCatcher check_jni_abort_catcher;
EXPECT_CLASS_FOUND("java.lang.String");
+ check_jni_abort_catcher.Check("JNI ERROR: app bug found: illegal class name 'java.lang.String'");
EXPECT_CLASS_NOT_FOUND("Ljava.lang.String;");
+ check_jni_abort_catcher.Check("JNI ERROR: app bug found: illegal class name 'Ljava.lang.String;'");
EXPECT_CLASS_FOUND("[Ljava.lang.String;");
+ check_jni_abort_catcher.Check("JNI ERROR: app bug found: illegal class name '[Ljava.lang.String;'");
EXPECT_CLASS_NOT_FOUND("[java.lang.String");
+ check_jni_abort_catcher.Check("JNI ERROR: app bug found: illegal class name '[java.lang.String'");
// You can't include the "L;" in a JNI class descriptor.
EXPECT_CLASS_NOT_FOUND("Ljava/lang/String;");
+ check_jni_abort_catcher.Check("JNI ERROR: app bug found: illegal class name 'Ljava/lang/String;'");
+
// But you must include it for an array of any reference type.
EXPECT_CLASS_NOT_FOUND("[java/lang/String");
+ check_jni_abort_catcher.Check("JNI ERROR: app bug found: illegal class name '[java/lang/String'");
+
+ EXPECT_CLASS_NOT_FOUND("[K");
+ check_jni_abort_catcher.Check("JNI ERROR: app bug found: illegal class name '[K'");
}
- // Primitive arrays are okay (if the primitive type is valid)...
- EXPECT_CLASS_FOUND("[C");
- {
- CheckJniAbortCatcher check_jni_abort_catcher;
- EXPECT_CLASS_NOT_FOUND("[K");
- }
// But primitive types aren't allowed...
EXPECT_CLASS_NOT_FOUND("C");
EXPECT_CLASS_NOT_FOUND("K");
@@ -1022,10 +1027,11 @@
}
TEST_F(JniInternalTest, GetStringUTFChars_ReleaseStringUTFChars) {
+ // Passing in a NULL jstring is ignored normally, but caught by -Xcheck:jni.
{
- // Passing in a NULL jstring is ignored normally, but caught by -Xcheck:jni.
CheckJniAbortCatcher check_jni_abort_catcher;
EXPECT_TRUE(env_->GetStringUTFChars(NULL, NULL) == NULL);
+ check_jni_abort_catcher.Check("JNI ERROR: app bug found: GetStringUTFChars received null jstring");
}
jstring s = env_->NewStringUTF("hello");
@@ -1217,10 +1223,11 @@
ASSERT_TRUE(s != NULL);
env_->DeleteLocalRef(s);
+ // Currently, deleting an already-deleted reference is just a CheckJNI warning.
{
- // Currently, deleting an already-deleted reference is just a CheckJNI warning.
CheckJniAbortCatcher check_jni_abort_catcher;
env_->DeleteLocalRef(s);
+ check_jni_abort_catcher.Check("JNI ERROR: app bug found: native code passing in reference to invalid local reference: 0x200001");
}
s = env_->NewStringUTF("");
@@ -1296,10 +1303,11 @@
ASSERT_TRUE(o != NULL);
env_->DeleteGlobalRef(o);
+ // Currently, deleting an already-deleted reference is just a CheckJNI warning.
{
- // Currently, deleting an already-deleted reference is just a CheckJNI warning.
CheckJniAbortCatcher check_jni_abort_catcher;
env_->DeleteGlobalRef(o);
+ check_jni_abort_catcher.Check("JNI ERROR: app bug found: native code passing in reference to invalid global reference: 0x10000e");
}
jobject o1 = env_->NewGlobalRef(s);
@@ -1337,10 +1345,11 @@
ASSERT_TRUE(o != NULL);
env_->DeleteWeakGlobalRef(o);
+ // Currently, deleting an already-deleted reference is just a CheckJNI warning.
{
- // Currently, deleting an already-deleted reference is just a CheckJNI warning.
CheckJniAbortCatcher check_jni_abort_catcher;
env_->DeleteWeakGlobalRef(o);
+ check_jni_abort_catcher.Check("JNI ERROR: app bug found: native code passing in reference to invalid weak global reference: 0x100003");
}
jobject o1 = env_->NewWeakGlobalRef(s);
@@ -1566,10 +1575,7 @@
CheckJniAbortCatcher check_jni_abort_catcher;
env_->MonitorEnter(NULL);
check_jni_abort_catcher.Check("in call to MonitorEnter");
- }
- {
- CheckJniAbortCatcher check_jni_abort_catcher;
env_->MonitorExit(NULL);
check_jni_abort_catcher.Check("in call to MonitorExit");
}
diff --git a/src/logging.h b/src/logging.h
index f7b687b..585cc6d 100644
--- a/src/logging.h
+++ b/src/logging.h
@@ -249,6 +249,30 @@
return os;
}
+// Helps you use operator<< in a const char*-like context such as our various 'F' methods with
+// format strings.
+template<typename T>
+class ToStr {
+ public:
+ ToStr(const T& value) {
+ std::ostringstream os;
+ os << value;
+ s_ = os.str();
+ }
+
+ const char* c_str() const {
+ return s_.c_str();
+ }
+
+ const std::string& str() const {
+ return s_;
+ }
+
+ private:
+ std::string s_;
+ DISALLOW_COPY_AND_ASSIGN(ToStr);
+};
+
// The members of this struct are the valid arguments to VLOG and VLOG_IS_ON in code,
// and the "-verbose:" command line argument.
struct LogVerbosity {
diff --git a/src/oat/runtime/support_jni.cc b/src/oat/runtime/support_jni.cc
index e6e6478..fb8b2e0 100644
--- a/src/oat/runtime/support_jni.cc
+++ b/src/oat/runtime/support_jni.cc
@@ -51,9 +51,8 @@
}
if (o == kInvalidIndirectRefObject) {
- LOG(ERROR) << "JNI ERROR (app bug): invalid reference returned from "
- << PrettyMethod(self->GetCurrentMethod());
- JniAbort(NULL);
+ JniAbortF(NULL, "invalid reference returned from %s",
+ PrettyMethod(self->GetCurrentMethod()).c_str());
}
// Make sure that the result is an instance of the type this
@@ -63,9 +62,8 @@
Class* return_type = mh.GetReturnType();
if (!o->InstanceOf(return_type)) {
- LOG(ERROR) << "JNI ERROR (app bug): attempt to return an instance of " << PrettyTypeOf(o)
- << " from " << PrettyMethod(m);
- JniAbort(NULL);
+ JniAbortF(NULL, "attempt to return an instance of %s from %s",
+ PrettyTypeOf(o).c_str(), PrettyMethod(m).c_str());
}
return o;
diff --git a/src/scoped_jni_thread_state.h b/src/scoped_jni_thread_state.h
index fdf6581..7ef92d4 100644
--- a/src/scoped_jni_thread_state.h
+++ b/src/scoped_jni_thread_state.h
@@ -55,9 +55,9 @@
Thread* env_self = full_env->self;
Thread* self = work_around_app_jni_bugs ? Thread::Current() : env_self;
if (!work_around_app_jni_bugs && self != env_self) {
- LOG(FATAL) << "JNI ERROR (app bug): JNIEnv for " << *env_self
- << " used on " << *self;
- // TODO: pass JNI function through so we can call JniAbort(function_name) instead.
+ // TODO: pass through function name so we can use it here instead of NULL...
+ JniAbortF(NULL, "JNIEnv for %s used on %s",
+ ToStr<Thread>(*env_self).c_str(), ToStr<Thread>(*self).c_str());
}
return self;
}
diff --git a/src/thread.cc b/src/thread.cc
index a87e550..60f0199 100644
--- a/src/thread.cc
+++ b/src/thread.cc
@@ -1062,8 +1062,7 @@
}
if (result == NULL) {
- LOG(ERROR) << "JNI ERROR (app bug): use of deleted " << kind << ": " << obj;
- JniAbort(NULL);
+ JniAbortF(NULL, "use of deleted %s %p", ToStr<IndirectRefKind>(kind).c_str(), obj);
} else {
if (result != kInvalidIndirectRefObject) {
Runtime::Current()->GetHeap()->VerifyObject(result);