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);
 | |
| }
 | |
| 
 |