/* * 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. */ #if defined(__ANDROID__) /* libnativehelper is built by NDK 19 in one variant, which doesn't yet have the GNU strerror_r. */ #undef _GNU_SOURCE /* ...but this code uses asprintf, which is a BSD/GNU extension. */ #define _BSD_SOURCE #endif #define LOG_TAG "JNIHelp" #include "JniConstants.h" #include "JNIHelp.h" #include "ALog-priv.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <string> /** * Equivalent to ScopedLocalRef, but for C_JNIEnv instead. (And slightly more powerful.) */ template<typename T> class scoped_local_ref { public: explicit scoped_local_ref(C_JNIEnv* env, T localRef = NULL) : mEnv(env), mLocalRef(localRef) { } ~scoped_local_ref() { reset(); } void reset(T localRef = NULL) { if (mLocalRef != NULL) { (*mEnv)->DeleteLocalRef(reinterpret_cast<JNIEnv*>(mEnv), mLocalRef); mLocalRef = localRef; } } T get() const { return mLocalRef; } private: C_JNIEnv* const mEnv; T mLocalRef; DISALLOW_COPY_AND_ASSIGN(scoped_local_ref); }; static jclass findClass(C_JNIEnv* env, const char* className) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); return (*env)->FindClass(e, className); } extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); ALOGV("Registering %s's %d native methods...", className, numMethods); scoped_local_ref<jclass> c(env, findClass(env, className)); if (c.get() == NULL) { char* tmp; const char* msg; if (asprintf(&tmp, "Native registration unable to find class '%s'; aborting...", className) == -1) { // Allocation failed, print default warning. msg = "Native registration unable to find class; aborting..."; } else { msg = tmp; } e->FatalError(msg); } if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { char* tmp; const char* msg; if (asprintf(&tmp, "RegisterNatives failed for '%s'; aborting...", className) == -1) { // Allocation failed, print default warning. msg = "RegisterNatives failed; aborting..."; } else { msg = tmp; } e->FatalError(msg); } return 0; } /* * 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(C_JNIEnv* env, jthrowable exception, std::string& result) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); /* get the name of the exception's class */ scoped_local_ref<jclass> exceptionClass(env, (*env)->GetObjectClass(e, exception)); // can't fail scoped_local_ref<jclass> classClass(env, (*env)->GetObjectClass(e, exceptionClass.get())); // java.lang.Class, can't fail jmethodID classGetNameMethod = (*env)->GetMethodID(e, classClass.get(), "getName", "()Ljava/lang/String;"); scoped_local_ref<jstring> classNameStr(env, (jstring) (*env)->CallObjectMethod(e, exceptionClass.get(), classGetNameMethod)); if (classNameStr.get() == NULL) { (*env)->ExceptionClear(e); result = "<error getting class name>"; return false; } const char* classNameChars = (*env)->GetStringUTFChars(e, classNameStr.get(), NULL); if (classNameChars == NULL) { (*env)->ExceptionClear(e); result = "<error getting class name UTF-8>"; return false; } result += classNameChars; (*env)->ReleaseStringUTFChars(e, classNameStr.get(), classNameChars); /* if the exception has a detail message, get that */ jmethodID getMessage = (*env)->GetMethodID(e, exceptionClass.get(), "getMessage", "()Ljava/lang/String;"); scoped_local_ref<jstring> messageStr(env, (jstring) (*env)->CallObjectMethod(e, exception, getMessage)); if (messageStr.get() == NULL) { return true; } result += ": "; const char* messageChars = (*env)->GetStringUTFChars(e, messageStr.get(), NULL); if (messageChars != NULL) { result += messageChars; (*env)->ReleaseStringUTFChars(e, messageStr.get(), messageChars); } else { result += "<error getting message>"; (*env)->ExceptionClear(e); // clear OOM } return true; } /* * Returns an exception (with stack trace) as a string. */ static bool getStackTrace(C_JNIEnv* env, jthrowable exception, std::string& result) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); scoped_local_ref<jclass> stringWriterClass(env, findClass(env, "java/io/StringWriter")); if (stringWriterClass.get() == NULL) { return false; } jmethodID stringWriterCtor = (*env)->GetMethodID(e, stringWriterClass.get(), "<init>", "()V"); jmethodID stringWriterToStringMethod = (*env)->GetMethodID(e, stringWriterClass.get(), "toString", "()Ljava/lang/String;"); scoped_local_ref<jclass> printWriterClass(env, findClass(env, "java/io/PrintWriter")); if (printWriterClass.get() == NULL) { return false; } jmethodID printWriterCtor = (*env)->GetMethodID(e, printWriterClass.get(), "<init>", "(Ljava/io/Writer;)V"); scoped_local_ref<jobject> stringWriter(env, (*env)->NewObject(e, stringWriterClass.get(), stringWriterCtor)); if (stringWriter.get() == NULL) { return false; } scoped_local_ref<jobject> printWriter(env, (*env)->NewObject(e, printWriterClass.get(), printWriterCtor, stringWriter.get())); if (printWriter.get() == NULL) { return false; } scoped_local_ref<jclass> exceptionClass(env, (*env)->GetObjectClass(e, exception)); // can't fail jmethodID printStackTraceMethod = (*env)->GetMethodID(e, exceptionClass.get(), "printStackTrace", "(Ljava/io/PrintWriter;)V"); (*env)->CallVoidMethod(e, exception, printStackTraceMethod, printWriter.get()); if ((*env)->ExceptionCheck(e)) { return false; } scoped_local_ref<jstring> messageStr(env, (jstring) (*env)->CallObjectMethod(e, stringWriter.get(), stringWriterToStringMethod)); if (messageStr.get() == NULL) { return false; } const char* utfChars = (*env)->GetStringUTFChars(e, messageStr.get(), NULL); if (utfChars == NULL) { return false; } result = utfChars; (*env)->ReleaseStringUTFChars(e, messageStr.get(), utfChars); return true; } extern "C" int jniThrowException(C_JNIEnv* env, const char* className, const char* msg) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); if ((*env)->ExceptionCheck(e)) { /* TODO: consider creating the new exception with this as "cause" */ scoped_local_ref<jthrowable> exception(env, (*env)->ExceptionOccurred(e)); (*env)->ExceptionClear(e); if (exception.get() != NULL) { std::string text; getExceptionSummary(env, exception.get(), text); ALOGW("Discarding pending exception (%s) to throw %s", text.c_str(), className); } } scoped_local_ref<jclass> exceptionClass(env, findClass(env, className)); if (exceptionClass.get() == NULL) { ALOGE("Unable to find exception class %s", className); /* ClassNotFoundException now pending */ return -1; } if ((*env)->ThrowNew(e, exceptionClass.get(), msg) != JNI_OK) { ALOGE("Failed throwing '%s' '%s'", className, msg); /* an exception, most likely OOM, will now be pending */ return -1; } return 0; } int jniThrowExceptionFmt(C_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(C_JNIEnv* env, const char* msg) { return jniThrowException(env, "java/lang/NullPointerException", msg); } int jniThrowRuntimeException(C_JNIEnv* env, const char* msg) { return jniThrowException(env, "java/lang/RuntimeException", msg); } int jniThrowIOException(C_JNIEnv* env, int errnum) { char buffer[80]; const char* message = jniStrError(errnum, buffer, sizeof(buffer)); return jniThrowException(env, "java/io/IOException", message); } static std::string jniGetStackTrace(C_JNIEnv* env, jthrowable exception) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); scoped_local_ref<jthrowable> currentException(env, (*env)->ExceptionOccurred(e)); if (exception == NULL) { exception = currentException.get(); if (exception == NULL) { return "<no pending exception>"; } } if (currentException.get() != NULL) { (*env)->ExceptionClear(e); } std::string trace; if (!getStackTrace(env, exception, trace)) { (*env)->ExceptionClear(e); getExceptionSummary(env, exception, trace); } if (currentException.get() != NULL) { (*env)->Throw(e, currentException.get()); // rethrow } return trace; } void jniLogException(C_JNIEnv* env, int priority, const char* tag, jthrowable exception) { std::string trace(jniGetStackTrace(env, exception)); __android_log_write(priority, tag, trace.c_str()); } const char* jniStrError(int errnum, char* buf, size_t buflen) { #if __GLIBC__ // 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); return strerror_r(errnum, buf, buflen); #else int rc = strerror_r(errnum, buf, buflen); if (rc != 0) { // (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.) snprintf(buf, buflen, "errno %d", errnum); } return buf; #endif } jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); JniConstants::init(e); static jmethodID ctor = e->GetMethodID(JniConstants::fileDescriptorClass, "<init>", "()V"); jobject fileDescriptor = (*env)->NewObject(e, JniConstants::fileDescriptorClass, ctor); // NOTE: NewObject ensures that an OutOfMemoryError will be seen by the Java // caller if the alloc fails, so we just return NULL when that happens. if (fileDescriptor != NULL) { jniSetFileDescriptorOfFD(env, fileDescriptor, fd); } return fileDescriptor; } int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); JniConstants::init(e); static jfieldID fid = e->GetFieldID(JniConstants::fileDescriptorClass, "descriptor", "I"); if (fileDescriptor != NULL) { return (*env)->GetIntField(e, fileDescriptor, fid); } else { return -1; } } void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); JniConstants::init(e); static jfieldID fid = e->GetFieldID(JniConstants::fileDescriptorClass, "descriptor", "I"); (*env)->SetIntField(e, fileDescriptor, fid, value); } jobject jniGetReferent(C_JNIEnv* env, jobject ref) { JNIEnv* e = reinterpret_cast<JNIEnv*>(env); JniConstants::init(e); static jmethodID get = e->GetMethodID(JniConstants::referenceClass, "get", "()Ljava/lang/Object;"); return (*env)->CallObjectMethod(e, ref, get); }