/*
* 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.
*/
/*
* JNI helper functions.
*/
#define LOG_TAG "JNIHelp"
#include "JNIHelp.h"
#include "utils/Log.h"
#include <string.h>
#include <assert.h>
/*
* Register native JNI-callable methods.
*
* "className" looks like "java/lang/String".
*/
int jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
LOGV("Registering %s natives\n", className);
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
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);
result = -1;
}
(*env)->DeleteLocalRef(env, clazz);
return result;
}
/*
* Get 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 void getExceptionSummary(JNIEnv* env, jthrowable exception, char* buf, size_t bufLen)
{
int success = 0;
/* 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);
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 (! success) {
(*env)->ExceptionClear(env);
getExceptionSummary(env, exception, buf, bufLen);
}
}
/*
* Throw an exception with the specified class and an optional message.
*
* If an exception is currently pending, we log a warning message and
* clear it.
*
* Returns 0 if the specified exception was successfully thrown. (Some
* sort of exception will always be pending when this returns.)
*/
int jniThrowException(JNIEnv* env, const char* className, const char* msg)
{
jclass exceptionClass;
if ((*env)->ExceptionCheck(env)) {
/* TODO: consider creating the new exception with this as "cause" */
char buf[256];
jthrowable exception = (*env)->ExceptionOccurred(env);
(*env)->ExceptionClear(env);
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);
if (exceptionClass == NULL) {
LOGE("Unable to find exception class %s\n", className);
/* ClassNotFoundException now pending */
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 */
result = -1;
}
(*env)->DeleteLocalRef(env, exceptionClass);
return result;
}
/*
* Throw a java.lang.NullPointerException, with an optional message.
*/
int jniThrowNullPointerException(JNIEnv* env, const char* msg)
{
return jniThrowException(env, "java/lang/NullPointerException", msg);
}
/*
* Throw a java.lang.RuntimeException, with an optional message.
*/
int jniThrowRuntimeException(JNIEnv* env, const char* msg)
{
return jniThrowException(env, "java/lang/RuntimeException", msg);
}
/*
* Throw a java.io.IOException, generating the message from errno.
*/
int jniThrowIOException(JNIEnv* env, int errnum)
{
char buffer[80];
const char* message = jniStrError(errnum, buffer, sizeof(buffer));
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
// than POSIX's int.
// char *strerror_r(int errnum, char *buf, size_t n);
char* ret = (char*) strerror_r(errnum, buf, buflen);
if (((int)ret) == 0) {
//POSIX strerror_r, success
return buf;
} else if (((int)ret) == -1) {
//POSIX strerror_r, failure
// (Strictly, POSIX only guarantees a value other than 0. The safest
// way to implement this function is to use C++ and overload on the
// type of strerror_r to accurately distinguish GNU from POSIX. But
// realistic implementations will always return -1.)
snprintf(buf, buflen, "errno %d", errnum);
return buf;
} else {
//glibc strerror_r returning a string
return ret;
}
}
static struct CachedFields {
jclass fileDescriptorClass;
jmethodID fileDescriptorCtor;
jfieldID descriptorField;
} gCachedFields;
int registerJniHelp(JNIEnv* env) {
gCachedFields.fileDescriptorClass =
(*env)->NewGlobalRef(env, (*env)->FindClass(env, "java/io/FileDescriptor"));
if (gCachedFields.fileDescriptorClass == NULL) {
return -1;
}
gCachedFields.fileDescriptorCtor =
(*env)->GetMethodID(env, gCachedFields.fileDescriptorClass, "<init>", "()V");
if (gCachedFields.fileDescriptorCtor == NULL) {
return -1;
}
gCachedFields.descriptorField =
(*env)->GetFieldID(env, gCachedFields.fileDescriptorClass, "descriptor", "I");
if (gCachedFields.descriptorField == NULL) {
return -1;
}
return 0;
}
/*
* Create a java.io.FileDescriptor given an integer fd
*/
jobject jniCreateFileDescriptor(JNIEnv* env, int fd) {
jobject fileDescriptor = (*env)->NewObject(env,
gCachedFields.fileDescriptorClass, gCachedFields.fileDescriptorCtor);
jniSetFileDescriptorOfFD(env, fileDescriptor, fd);
return fileDescriptor;
}
/*
* Get an int file descriptor from a java.io.FileDescriptor
*/
int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) {
return (*env)->GetIntField(env, fileDescriptor, gCachedFields.descriptorField);
}
/*
* Set the descriptor of a java.io.FileDescriptor
*/
void jniSetFileDescriptorOfFD(JNIEnv* env, jobject fileDescriptor, int value) {
(*env)->SetIntField(env, fileDescriptor, gCachedFields.descriptorField, value);
}