342 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			342 lines
		
	
	
		
			11 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. | ||
|  |  */ | ||
|  | 
 | ||
|  | #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: | ||
|  |     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* mEnv; | ||
|  |     T mLocalRef; | ||
|  | 
 | ||
|  |     // Disallow copy and assignment.
 | ||
|  |     scoped_local_ref(const scoped_local_ref&); | ||
|  |     void operator=(const 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* msg; | ||
|  |         asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className); | ||
|  |         e->FatalError(msg); | ||
|  |     } | ||
|  | 
 | ||
|  |     if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { | ||
|  |         char* msg; | ||
|  |         asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className); | ||
|  |         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; | ||
|  |     } | ||
|  | 
 | ||
|  |     jobject printWriter = | ||
|  |             (*env)->NewObject(e, printWriterClass.get(), printWriterCtor, stringWriter.get()); | ||
|  |     if (printWriter == 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); | ||
|  | 
 | ||
|  |     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); | ||
|  |     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); | ||
|  |     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); | ||
|  |     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); | ||
|  |     static jmethodID get = e->GetMethodID(JniConstants::referenceClass, "get", "()Ljava/lang/Object;"); | ||
|  |     return (*env)->CallObjectMethod(e, ref, get); | ||
|  | } | ||
|  | 
 |