Added a new JNI helper for logging exceptions.
Plugged some JNI reference leaks in existing JNI helpers.

Change-Id: I62213cdae375235f6ee9304ecd8dc3d2b7f58c85
diff --git a/JNIHelp.c b/JNIHelp.c
index a75b837..1dff85d 100644
--- a/JNIHelp.c
+++ b/JNIHelp.c
@@ -40,11 +40,15 @@
         LOGE("Native registration unable to find class '%s'\n", className);
         return -1;
     }
+
+    int result = 0;
     if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
         LOGE("RegisterNatives failed for '%s'\n", className);
-        return -1;
+        result = -1;
     }
-    return 0;
+
+    (*env)->DeleteLocalRef(env, clazz);
+    return result;
 }
 
 /*
@@ -52,45 +56,113 @@
  * be populated with the "binary" class name and, if present, the
  * exception message.
  */
-static void getExceptionSummary(JNIEnv* env, jthrowable excep, char* buf,
-    size_t bufLen)
+static void getExceptionSummary(JNIEnv* env, jthrowable exception, char* buf, size_t bufLen)
 {
-    if (excep == NULL)
-        return;
+    int success = 0;
 
-    /* get the name of the exception's class; none of these should fail */
-    jclass clazz = (*env)->GetObjectClass(env, excep); // exception's class
-    jclass jlc = (*env)->GetObjectClass(env, clazz);   // java.lang.Class
-    jmethodID getNameMethod =
-        (*env)->GetMethodID(env, jlc, "getName", "()Ljava/lang/String;");
-    jstring className = (*env)->CallObjectMethod(env, clazz, getNameMethod);
+    /* get the name of the exception's class */
+    jclass exceptionClazz = (*env)->GetObjectClass(env, exception); // can't fail
+    jclass classClazz = (*env)->GetObjectClass(env, exceptionClazz); // java.lang.Class, can't fail
+    jmethodID classGetNameMethod = (*env)->GetMethodID(
+            env, classClazz, "getName", "()Ljava/lang/String;");
+    jstring classNameStr = (*env)->CallObjectMethod(env, exceptionClazz, classGetNameMethod);
+    if (classNameStr != NULL) {
+        /* get printable string */
+        const char* classNameChars = (*env)->GetStringUTFChars(env, classNameStr, NULL);
+        if (classNameChars != NULL) {
+            /* if the exception has a message string, get that */
+            jmethodID throwableGetMessageMethod = (*env)->GetMethodID(
+                    env, exceptionClazz, "getMessage", "()Ljava/lang/String;");
+            jstring messageStr = (*env)->CallObjectMethod(
+                    env, exception, throwableGetMessageMethod);
 
-    /* get printable string */
-    const char* nameStr = (*env)->GetStringUTFChars(env, className, NULL);
-    if (nameStr == NULL) {
-        snprintf(buf, bufLen, "%s", "out of memory generating summary");
-        (*env)->ExceptionClear(env);            // clear OOM
-        return;
+            if (messageStr != NULL) {
+                const char* messageChars = (*env)->GetStringUTFChars(env, messageStr, NULL);
+                if (messageChars != NULL) {
+                    snprintf(buf, bufLen, "%s: %s", classNameChars, messageChars);
+                    (*env)->ReleaseStringUTFChars(env, messageStr, messageChars);
+                } else {
+                    (*env)->ExceptionClear(env); // clear OOM
+                    snprintf(buf, bufLen, "%s: <error getting message>", classNameChars);
+                }
+                (*env)->DeleteLocalRef(env, messageStr);
+            } else {
+                strncpy(buf, classNameChars, bufLen);
+                buf[bufLen - 1] = '\0';
+            }
+
+            (*env)->ReleaseStringUTFChars(env, classNameStr, classNameChars);
+            success = 1;
+        }
+        (*env)->DeleteLocalRef(env, classNameStr);
+    }
+    (*env)->DeleteLocalRef(env, classClazz);
+    (*env)->DeleteLocalRef(env, exceptionClazz);
+
+    if (! success) {
+        (*env)->ExceptionClear(env);
+        snprintf(buf, bufLen, "%s", "<error getting class name>");
+    }
+}
+
+/*
+ * Formats an exception as a string with its stack trace.
+ */
+static void printStackTrace(JNIEnv* env, jthrowable exception, char* buf, size_t bufLen)
+{
+    int success = 0;
+
+    jclass stringWriterClazz = (*env)->FindClass(env, "java/io/StringWriter");
+    if (stringWriterClazz != NULL) {
+        jmethodID stringWriterCtor = (*env)->GetMethodID(env, stringWriterClazz,
+                "<init>", "()V");
+        jmethodID stringWriterToStringMethod = (*env)->GetMethodID(env, stringWriterClazz,
+                "toString", "()Ljava/lang/String;");
+
+        jclass printWriterClazz = (*env)->FindClass(env, "java/io/PrintWriter");
+        if (printWriterClazz != NULL) {
+            jmethodID printWriterCtor = (*env)->GetMethodID(env, printWriterClazz,
+                    "<init>", "(Ljava/io/Writer;)V");
+
+            jobject stringWriterObj = (*env)->NewObject(env, stringWriterClazz, stringWriterCtor);
+            if (stringWriterObj != NULL) {
+                jobject printWriterObj = (*env)->NewObject(env, printWriterClazz, printWriterCtor,
+                        stringWriterObj);
+                if (printWriterObj != NULL) {
+                    jclass exceptionClazz = (*env)->GetObjectClass(env, exception); // can't fail
+                    jmethodID printStackTraceMethod = (*env)->GetMethodID(
+                            env, exceptionClazz, "printStackTrace", "(Ljava/io/PrintWriter;)V");
+
+                    (*env)->CallVoidMethod(
+                            env, exception, printStackTraceMethod, printWriterObj);
+                    if (! (*env)->ExceptionCheck(env)) {
+                        jstring messageStr = (*env)->CallObjectMethod(
+                                env, stringWriterObj, stringWriterToStringMethod);
+                        if (messageStr != NULL) {
+                            jsize messageStrLength = (*env)->GetStringLength(env, messageStr);
+                            if (messageStrLength >= (jsize) bufLen) {
+                                messageStrLength = bufLen - 1;
+                            }
+                            (*env)->GetStringUTFRegion(env, messageStr, 0, messageStrLength, buf);
+                            (*env)->DeleteLocalRef(env, messageStr);
+                            buf[messageStrLength] = '\0';
+                            success = 1;
+                        }
+                    }
+                    (*env)->DeleteLocalRef(env, exceptionClazz);
+                    (*env)->DeleteLocalRef(env, printWriterObj);
+                }
+                (*env)->DeleteLocalRef(env, stringWriterObj);
+            }
+            (*env)->DeleteLocalRef(env, printWriterClazz);
+        }
+        (*env)->DeleteLocalRef(env, stringWriterClazz);
     }
 
-    /* if the exception has a message string, get that */
-    jmethodID getThrowableMessage =
-        (*env)->GetMethodID(env, clazz, "getMessage", "()Ljava/lang/String;");
-    jstring message = (*env)->CallObjectMethod(env, excep, getThrowableMessage);
-
-    if (message != NULL) {
-        const char* messageStr = (*env)->GetStringUTFChars(env, message, NULL);
-        snprintf(buf, bufLen, "%s: %s", nameStr, messageStr);
-        if (messageStr != NULL)
-            (*env)->ReleaseStringUTFChars(env, message, messageStr);
-        else
-            (*env)->ExceptionClear(env);        // clear OOM
-    } else {
-        strncpy(buf, nameStr, bufLen);
-        buf[bufLen-1] = '\0';
+    if (! success) {
+        (*env)->ExceptionClear(env);
+        getExceptionSummary(env, exception, buf, bufLen);
     }
-
-    (*env)->ReleaseStringUTFChars(env, className, nameStr);
 }
 
 /*
@@ -110,11 +182,14 @@
         /* TODO: consider creating the new exception with this as "cause" */
         char buf[256];
 
-        jthrowable excep = (*env)->ExceptionOccurred(env);
+        jthrowable exception = (*env)->ExceptionOccurred(env);
         (*env)->ExceptionClear(env);
-        getExceptionSummary(env, excep, buf, sizeof(buf));
-        LOGW("Discarding pending exception (%s) to throw %s\n",
-            buf, className);
+
+        if (exception != NULL) {
+            getExceptionSummary(env, exception, buf, sizeof(buf));
+            LOGW("Discarding pending exception (%s) to throw %s\n", buf, className);
+            (*env)->DeleteLocalRef(env, exception);
+        }
     }
 
     exceptionClass = (*env)->FindClass(env, className);
@@ -124,12 +199,15 @@
         return -1;
     }
 
+    int result = 0;
     if ((*env)->ThrowNew(env, exceptionClass, msg) != JNI_OK) {
         LOGE("Failed throwing '%s' '%s'\n", className, msg);
         /* an exception, most likely OOM, will now be pending */
-        return -1;
+        result = -1;
     }
-    return 0;
+
+    (*env)->DeleteLocalRef(env, exceptionClass);
+    return result;
 }
 
 /*
@@ -158,6 +236,33 @@
     return jniThrowException(env, "java/io/IOException", message);
 }
 
+/*
+ * Log an exception.
+ * If exception is NULL, logs the current exception in the JNI environment, if any.
+ */
+void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable exception)
+{
+    int currentException = 0;
+    if (exception == NULL) {
+        exception = (*env)->ExceptionOccurred(env);
+        if (exception == NULL) {
+            return;
+        }
+
+        (*env)->ExceptionClear(env);
+        currentException = 1;
+    }
+
+    char buffer[1024];
+    printStackTrace(env, exception, buffer, sizeof(buffer));
+    __android_log_write(priority, tag, buffer);
+
+    if (currentException) {
+        (*env)->Throw(env, exception); // rethrow
+        (*env)->DeleteLocalRef(env, exception);
+    }
+}
+
 const char* jniStrError(int errnum, char* buf, size_t buflen)
 {
     // note: glibc has a nonstandard strerror_r that returns char* rather
diff --git a/include/nativehelper/JNIHelp.h b/include/nativehelper/JNIHelp.h
index 59c2620..585d1c7 100644
--- a/include/nativehelper/JNIHelp.h
+++ b/include/nativehelper/JNIHelp.h
@@ -91,6 +91,12 @@
  */
 void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value);
 
+/*
+ * Log a message and an exception.
+ * If exception is NULL, logs the current exception in the JNI environment.
+ */
+void jniLogException(C_JNIEnv* env, int priority, const char* tag, jthrowable exception);
+
 #ifdef __cplusplus
 }
 #endif
@@ -135,10 +141,28 @@
 inline void jniSetFileDescriptorOfFD(JNIEnv* env, jobject fileDescriptor,
     int value)
 {
-    return jniSetFileDescriptorOfFD(&env->functions, fileDescriptor, value);
+    jniSetFileDescriptorOfFD(&env->functions, fileDescriptor, value);
+}
+inline void jniLogException(JNIEnv* env, int priority, const char* tag,
+        jthrowable exception = NULL)
+{
+    jniLogException(&env->functions, priority, tag, exception);
 }
 #endif
 
+/* Logging macros.
+ *
+ * Logs an exception.  If the exception is omitted or NULL, logs the current exception
+ * from the JNI environment, if any.
+ */
+#define LOG_EX(env, priority, tag, ...) \
+    IF_LOG(priority, tag) jniLogException(env, ANDROID_##priority, tag, ##__VA_ARGS__)
+#define LOGV_EX(env, ...) LOG_EX(env, LOG_VERBOSE, LOG_TAG, ##__VA_ARGS__)
+#define LOGD_EX(env, ...) LOG_EX(env, LOG_DEBUG, LOG_TAG, ##__VA_ARGS__)
+#define LOGI_EX(env, ...) LOG_EX(env, LOG_INFO, LOG_TAG, ##__VA_ARGS__)
+#define LOGW_EX(env, ...) LOG_EX(env, LOG_WARN, LOG_TAG, ##__VA_ARGS__)
+#define LOGE_EX(env, ...) LOG_EX(env, LOG_ERROR, LOG_TAG, ##__VA_ARGS__)
+
 /*
  * TEMP_FAILURE_RETRY is defined by some, but not all, versions of
  * <unistd.h>. (Alas, it is not as standard as we'd hoped!) So, if it's