390 lines
14 KiB
C
390 lines
14 KiB
C
/*
|
|
* 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 <stdarg.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <jni.h>
|
|
|
|
#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 <exception_class_name> ": " <exception_message>
|
|
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, "<error getting class name>");
|
|
(*env)->ExceptionClear(env);
|
|
(*env)->DeleteLocalRef(env, exceptionClass);
|
|
return false;
|
|
}
|
|
(*env)->DeleteLocalRef(env, exceptionClass);
|
|
exceptionClass = NULL;
|
|
|
|
if (!AppendJString(env, className, dst)) {
|
|
ExpandableStringAssign(dst, "<error getting class name UTF-8>");
|
|
(*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, "<error getting message>");
|
|
// 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, "<init>", "()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, "<init>", "(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, "<no pending exception>");
|
|
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, "<init>", 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);
|
|
}
|