/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "include/nativehelper/JNIHelp.h" #include #include #include #include #include #include #define LOG_TAG "JNIHelp" #include "ALog-priv.h" #include "ExpandableString.h" // // Helper methods // static const char* platformStrError(int errnum, char* buf, size_t buflen) { #ifdef _WIN32 strerror_s(buf, buflen, errnum); return buf; #elif defined(__USE_GNU) && __ANDROID_API__ >= 23 // char *strerror_r(int errnum, char *buf, size_t buflen); /* GNU-specific */ return strerror_r(errnum, buf, buflen); #else // int strerror_r(int errnum, char *buf, size_t buflen); /* XSI-compliant */ int rc = strerror_r(errnum, buf, buflen); if (rc != 0) { snprintf(buf, buflen, "errno %d", errnum); } return buf; #endif } static jmethodID FindMethod(JNIEnv* env, const char* className, const char* methodName, const char* descriptor) { // This method is only valid for classes in the core library which are // not unloaded during the lifetime of managed code execution. jclass clazz = (*env)->FindClass(env, className); jmethodID methodId = (*env)->GetMethodID(env, clazz, methodName, descriptor); (*env)->DeleteLocalRef(env, clazz); return methodId; } static bool AppendJString(JNIEnv* env, jstring text, struct ExpandableString* dst) { const char* utfText = (*env)->GetStringUTFChars(env, text, NULL); if (utfText == NULL) { return false; } bool success = ExpandableStringAppend(dst, utfText); (*env)->ReleaseStringUTFChars(env, text, utfText); return success; } /* * Returns a human-readable summary of an exception object. The buffer will * be populated with the "binary" class name and, if present, the * exception message. */ static bool GetExceptionSummary(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) { // Summary is ": " jclass exceptionClass = (*env)->GetObjectClass(env, thrown); // Always succeeds jmethodID getName = FindMethod(env, "java/lang/Class", "getName", "()Ljava/lang/String;"); jstring className = (jstring) (*env)->CallObjectMethod(env, exceptionClass, getName); if (className == NULL) { ExpandableStringAssign(dst, ""); (*env)->ExceptionClear(env); (*env)->DeleteLocalRef(env, exceptionClass); return false; } (*env)->DeleteLocalRef(env, exceptionClass); exceptionClass = NULL; if (!AppendJString(env, className, dst)) { ExpandableStringAssign(dst, ""); (*env)->ExceptionClear(env); (*env)->DeleteLocalRef(env, className); return false; } (*env)->DeleteLocalRef(env, className); className = NULL; jmethodID getMessage = FindMethod(env, "java/lang/Throwable", "getMessage", "()Ljava/lang/String;"); jstring message = (jstring) (*env)->CallObjectMethod(env, thrown, getMessage); if (message == NULL) { return true; } bool success = (ExpandableStringAppend(dst, ": ") && AppendJString(env, message, dst)); if (!success) { // Two potential reasons for reaching here: // // 1. managed heap allocation failure (OOME). // 2. native heap allocation failure for the storage in |dst|. // // Attempt to append failure notification, okay to fail, |dst| contains the class name // of |thrown|. ExpandableStringAppend(dst, ""); // Clear OOME if present. (*env)->ExceptionClear(env); } (*env)->DeleteLocalRef(env, message); message = NULL; return success; } static jobject NewStringWriter(JNIEnv* env) { jclass clazz = (*env)->FindClass(env, "java/io/StringWriter"); jmethodID init = (*env)->GetMethodID(env, clazz, "", "()V"); jobject instance = (*env)->NewObject(env, clazz, init); (*env)->DeleteLocalRef(env, clazz); return instance; } static jstring StringWriterToString(JNIEnv* env, jobject stringWriter) { jmethodID toString = FindMethod(env, "java/io/StringWriter", "toString", "()Ljava/lang/String;"); return (jstring) (*env)->CallObjectMethod(env, stringWriter, toString); } static jobject NewPrintWriter(JNIEnv* env, jobject writer) { jclass clazz = (*env)->FindClass(env, "java/io/PrintWriter"); jmethodID init = (*env)->GetMethodID(env, clazz, "", "(Ljava/io/Writer;)V"); jobject instance = (*env)->NewObject(env, clazz, init, writer); (*env)->DeleteLocalRef(env, clazz); return instance; } static bool GetStackTrace(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) { // This function is equivalent to the following Java snippet: // StringWriter sw = new StringWriter(); // PrintWriter pw = new PrintWriter(sw); // thrown.printStackTrace(pw); // String trace = sw.toString(); // return trace; jobject sw = NewStringWriter(env); if (sw == NULL) { return false; } jobject pw = NewPrintWriter(env, sw); if (pw == NULL) { (*env)->DeleteLocalRef(env, sw); return false; } jmethodID printStackTrace = FindMethod(env, "java/lang/Throwable", "printStackTrace", "(Ljava/io/PrintWriter;)V"); (*env)->CallVoidMethod(env, thrown, printStackTrace, pw); jstring trace = StringWriterToString(env, sw); (*env)->DeleteLocalRef(env, pw); pw = NULL; (*env)->DeleteLocalRef(env, sw); sw = NULL; if (trace == NULL) { return false; } bool success = AppendJString(env, trace, dst); (*env)->DeleteLocalRef(env, trace); return success; } static void GetStackTraceOrSummary(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) { // This method attempts to get a stack trace or summary info for an exception. // The exception may be provided in the |thrown| argument to this function. // If |thrown| is NULL, then any pending exception is used if it exists. // Save pending exception, callees may raise other exceptions. Any pending exception is // rethrown when this function exits. jthrowable pendingException = (*env)->ExceptionOccurred(env); if (pendingException != NULL) { (*env)->ExceptionClear(env); } if (thrown == NULL) { if (pendingException == NULL) { ExpandableStringAssign(dst, ""); return; } thrown = pendingException; } if (!GetStackTrace(env, thrown, dst)) { // GetStackTrace may have raised an exception, clear it since it's not for the caller. (*env)->ExceptionClear(env); GetExceptionSummary(env, thrown, dst); } if (pendingException != NULL) { // Re-throw the pending exception present when this method was called. (*env)->Throw(env, pendingException); (*env)->DeleteLocalRef(env, pendingException); } } static void DiscardPendingException(JNIEnv* env, const char* className) { jthrowable exception = (*env)->ExceptionOccurred(env); (*env)->ExceptionClear(env); if (exception == NULL) { return; } struct ExpandableString summary; ExpandableStringInitialize(&summary); GetExceptionSummary(env, exception, &summary); const char* details = (summary.data != NULL) ? summary.data : "Unknown"; ALOGW("Discarding pending exception (%s) to throw %s", details, className); ExpandableStringRelease(&summary); (*env)->DeleteLocalRef(env, exception); } static int ThrowException(JNIEnv* env, const char* className, const char* ctorSig, ...) { int status = -1; jclass exceptionClass = NULL; va_list args; va_start(args, ctorSig); DiscardPendingException(env, className); { /* We want to clean up local references before returning from this function, so, * regardless of return status, the end block must run. Have the work done in a * nested block to avoid using any uninitialized variables in the end block. */ exceptionClass = (*env)->FindClass(env, className); if (exceptionClass == NULL) { ALOGE("Unable to find exception class %s", className); /* an exception, most likely ClassNotFoundException, will now be pending */ goto end; } jmethodID init = (*env)->GetMethodID(env, exceptionClass, "", ctorSig); if(init == NULL) { ALOGE("Failed to find constructor for '%s' '%s'", className, ctorSig); goto end; } jobject instance = (*env)->NewObjectV(env, exceptionClass, init, args); if (instance == NULL) { ALOGE("Failed to construct '%s'", className); goto end; } if ((*env)->Throw(env, (jthrowable)instance) != JNI_OK) { ALOGE("Failed to throw '%s'", className); /* an exception, most likely OOM, will now be pending */ goto end; } /* everything worked fine, just update status to success and clean up */ status = 0; } end: va_end(args); if (exceptionClass != NULL) { (*env)->DeleteLocalRef(env, exceptionClass); } return status; } static jstring CreateExceptionMsg(JNIEnv* env, const char* msg) { jstring detailMessage = (*env)->NewStringUTF(env, msg); if (detailMessage == NULL) { /* Not really much we can do here. We're probably dead in the water, but let's try to stumble on... */ (*env)->ExceptionClear(env); } return detailMessage; } /* Helper macro to deal with conversion of the exception message from a C string * to jstring. * * This is useful because most exceptions have a message as the first parameter * and delegating the conversion to all the callers of ThrowException results in * code duplication. However, since we try to allow variable number of arguments * for the exception constructor we'd either need to do the conversion inside * the macro, or manipulate the va_list to replace the C string to a jstring. * This seems like the cleaner solution. */ #define THROW_EXCEPTION_WITH_MESSAGE(env, className, ctorSig, msg, ...) ({ \ jstring _detailMessage = CreateExceptionMsg(env, msg); \ int _status = ThrowException(env, className, ctorSig, _detailMessage, ## __VA_ARGS__); \ if (_detailMessage != NULL) { \ (*env)->DeleteLocalRef(env, _detailMessage); \ } \ _status; }) // // JNIHelp external API // int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* methods, int numMethods) { ALOGV("Registering %s's %d native methods...", className, numMethods); jclass clazz = (*env)->FindClass(env, className); ALOG_ALWAYS_FATAL_IF(clazz == NULL, "Native registration unable to find class '%s'; aborting...", className); int result = (*env)->RegisterNatives(env, clazz, methods, numMethods); (*env)->DeleteLocalRef(env, clazz); if (result == 0) { return 0; } // Failure to register natives is fatal. Try to report the corresponding exception, // otherwise abort with generic failure message. jthrowable thrown = (*env)->ExceptionOccurred(env); if (thrown != NULL) { struct ExpandableString summary; ExpandableStringInitialize(&summary); if (GetExceptionSummary(env, thrown, &summary)) { ALOGF("%s", summary.data); } ExpandableStringRelease(&summary); (*env)->DeleteLocalRef(env, thrown); } ALOGF("RegisterNatives failed for '%s'; aborting...", className); return result; } void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable thrown) { struct ExpandableString summary; ExpandableStringInitialize(&summary); GetStackTraceOrSummary(env, thrown, &summary); const char* details = (summary.data != NULL) ? summary.data : "No memory to report exception"; __android_log_write(priority, tag, details); ExpandableStringRelease(&summary); } int jniThrowException(JNIEnv* env, const char* className, const char* message) { return THROW_EXCEPTION_WITH_MESSAGE(env, className, "(Ljava/lang/String;)V", message); } int jniThrowExceptionFmt(JNIEnv* env, const char* className, const char* fmt, va_list args) { char msgBuf[512]; vsnprintf(msgBuf, sizeof(msgBuf), fmt, args); return jniThrowException(env, className, msgBuf); } int jniThrowNullPointerException(JNIEnv* env, const char* msg) { return jniThrowException(env, "java/lang/NullPointerException", msg); } int jniThrowRuntimeException(JNIEnv* env, const char* msg) { return jniThrowException(env, "java/lang/RuntimeException", msg); } int jniThrowIOException(JNIEnv* env, int errno_value) { char buffer[80]; const char* message = platformStrError(errno_value, buffer, sizeof(buffer)); return jniThrowException(env, "java/io/IOException", message); } int jniThrowErrnoException(JNIEnv* env, const char* functionName, int errno_value) { return THROW_EXCEPTION_WITH_MESSAGE(env, "android/system/ErrnoException", "(Ljava/lang/String;I)V", functionName, errno_value); } jstring jniCreateString(JNIEnv* env, const jchar* unicodeChars, jsize len) { return (*env)->NewString(env, unicodeChars, len); }