1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "include/nativehelper/JNIHelp.h" 18 19 #include <stdarg.h> 20 #include <stdbool.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <string.h> 24 25 #include <jni.h> 26 27 #define LOG_TAG "JNIHelp" 28 #include "ALog-priv.h" 29 30 #include "ExpandableString.h" 31 32 // 33 // Helper methods 34 // 35 36 static const char* platformStrError(int errnum, char* buf, size_t buflen) { 37 #ifdef _WIN32 38 strerror_s(buf, buflen, errnum); 39 return buf; 40 #elif defined(__USE_GNU) && __ANDROID_API__ >= 23 41 // char *strerror_r(int errnum, char *buf, size_t buflen); /* GNU-specific */ 42 return strerror_r(errnum, buf, buflen); 43 #else 44 // int strerror_r(int errnum, char *buf, size_t buflen); /* XSI-compliant */ 45 int rc = strerror_r(errnum, buf, buflen); 46 if (rc != 0) { 47 snprintf(buf, buflen, "errno %d", errnum); 48 } 49 return buf; 50 #endif 51 } 52 53 static jmethodID FindMethod(JNIEnv* env, 54 const char* className, 55 const char* methodName, 56 const char* descriptor) { 57 // This method is only valid for classes in the core library which are 58 // not unloaded during the lifetime of managed code execution. 59 jclass clazz = (*env)->FindClass(env, className); 60 jmethodID methodId = (*env)->GetMethodID(env, clazz, methodName, descriptor); 61 (*env)->DeleteLocalRef(env, clazz); 62 return methodId; 63 } 64 65 static bool AppendJString(JNIEnv* env, jstring text, struct ExpandableString* dst) { 66 const char* utfText = (*env)->GetStringUTFChars(env, text, NULL); 67 if (utfText == NULL) { 68 return false; 69 } 70 bool success = ExpandableStringAppend(dst, utfText); 71 (*env)->ReleaseStringUTFChars(env, text, utfText); 72 return success; 73 } 74 75 /* 76 * Returns a human-readable summary of an exception object. The buffer will 77 * be populated with the "binary" class name and, if present, the 78 * exception message. 79 */ 80 static bool GetExceptionSummary(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) { 81 // Summary is <exception_class_name> ": " <exception_message> 82 jclass exceptionClass = (*env)->GetObjectClass(env, thrown); // Always succeeds 83 jmethodID getName = FindMethod(env, "java/lang/Class", "getName", "()Ljava/lang/String;"); 84 jstring className = (jstring) (*env)->CallObjectMethod(env, exceptionClass, getName); 85 if (className == NULL) { 86 ExpandableStringAssign(dst, "<error getting class name>"); 87 (*env)->ExceptionClear(env); 88 (*env)->DeleteLocalRef(env, exceptionClass); 89 return false; 90 } 91 (*env)->DeleteLocalRef(env, exceptionClass); 92 exceptionClass = NULL; 93 94 if (!AppendJString(env, className, dst)) { 95 ExpandableStringAssign(dst, "<error getting class name UTF-8>"); 96 (*env)->ExceptionClear(env); 97 (*env)->DeleteLocalRef(env, className); 98 return false; 99 } 100 (*env)->DeleteLocalRef(env, className); 101 className = NULL; 102 103 jmethodID getMessage = 104 FindMethod(env, "java/lang/Throwable", "getMessage", "()Ljava/lang/String;"); 105 jstring message = (jstring) (*env)->CallObjectMethod(env, thrown, getMessage); 106 if (message == NULL) { 107 return true; 108 } 109 110 bool success = (ExpandableStringAppend(dst, ": ") && AppendJString(env, message, dst)); 111 if (!success) { 112 // Two potential reasons for reaching here: 113 // 114 // 1. managed heap allocation failure (OOME). 115 // 2. native heap allocation failure for the storage in |dst|. 116 // 117 // Attempt to append failure notification, okay to fail, |dst| contains the class name 118 // of |thrown|. 119 ExpandableStringAppend(dst, "<error getting message>"); 120 // Clear OOME if present. 121 (*env)->ExceptionClear(env); 122 } 123 (*env)->DeleteLocalRef(env, message); 124 message = NULL; 125 return success; 126 } 127 128 static jobject NewStringWriter(JNIEnv* env) { 129 jclass clazz = (*env)->FindClass(env, "java/io/StringWriter"); 130 jmethodID init = (*env)->GetMethodID(env, clazz, "<init>", "()V"); 131 jobject instance = (*env)->NewObject(env, clazz, init); 132 (*env)->DeleteLocalRef(env, clazz); 133 return instance; 134 } 135 136 static jstring StringWriterToString(JNIEnv* env, jobject stringWriter) { 137 jmethodID toString = 138 FindMethod(env, "java/io/StringWriter", "toString", "()Ljava/lang/String;"); 139 return (jstring) (*env)->CallObjectMethod(env, stringWriter, toString); 140 } 141 142 static jobject NewPrintWriter(JNIEnv* env, jobject writer) { 143 jclass clazz = (*env)->FindClass(env, "java/io/PrintWriter"); 144 jmethodID init = (*env)->GetMethodID(env, clazz, "<init>", "(Ljava/io/Writer;)V"); 145 jobject instance = (*env)->NewObject(env, clazz, init, writer); 146 (*env)->DeleteLocalRef(env, clazz); 147 return instance; 148 } 149 150 static bool GetStackTrace(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) { 151 // This function is equivalent to the following Java snippet: 152 // StringWriter sw = new StringWriter(); 153 // PrintWriter pw = new PrintWriter(sw); 154 // thrown.printStackTrace(pw); 155 // String trace = sw.toString(); 156 // return trace; 157 jobject sw = NewStringWriter(env); 158 if (sw == NULL) { 159 return false; 160 } 161 162 jobject pw = NewPrintWriter(env, sw); 163 if (pw == NULL) { 164 (*env)->DeleteLocalRef(env, sw); 165 return false; 166 } 167 168 jmethodID printStackTrace = 169 FindMethod(env, "java/lang/Throwable", "printStackTrace", "(Ljava/io/PrintWriter;)V"); 170 (*env)->CallVoidMethod(env, thrown, printStackTrace, pw); 171 172 jstring trace = StringWriterToString(env, sw); 173 174 (*env)->DeleteLocalRef(env, pw); 175 pw = NULL; 176 (*env)->DeleteLocalRef(env, sw); 177 sw = NULL; 178 179 if (trace == NULL) { 180 return false; 181 } 182 183 bool success = AppendJString(env, trace, dst); 184 (*env)->DeleteLocalRef(env, trace); 185 return success; 186 } 187 188 static void GetStackTraceOrSummary(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) { 189 // This method attempts to get a stack trace or summary info for an exception. 190 // The exception may be provided in the |thrown| argument to this function. 191 // If |thrown| is NULL, then any pending exception is used if it exists. 192 193 // Save pending exception, callees may raise other exceptions. Any pending exception is 194 // rethrown when this function exits. 195 jthrowable pendingException = (*env)->ExceptionOccurred(env); 196 if (pendingException != NULL) { 197 (*env)->ExceptionClear(env); 198 } 199 200 if (thrown == NULL) { 201 if (pendingException == NULL) { 202 ExpandableStringAssign(dst, "<no pending exception>"); 203 return; 204 } 205 thrown = pendingException; 206 } 207 208 if (!GetStackTrace(env, thrown, dst)) { 209 // GetStackTrace may have raised an exception, clear it since it's not for the caller. 210 (*env)->ExceptionClear(env); 211 GetExceptionSummary(env, thrown, dst); 212 } 213 214 if (pendingException != NULL) { 215 // Re-throw the pending exception present when this method was called. 216 (*env)->Throw(env, pendingException); 217 (*env)->DeleteLocalRef(env, pendingException); 218 } 219 } 220 221 static void DiscardPendingException(JNIEnv* env, const char* className) { 222 jthrowable exception = (*env)->ExceptionOccurred(env); 223 (*env)->ExceptionClear(env); 224 if (exception == NULL) { 225 return; 226 } 227 228 struct ExpandableString summary; 229 ExpandableStringInitialize(&summary); 230 GetExceptionSummary(env, exception, &summary); 231 const char* details = (summary.data != NULL) ? summary.data : "Unknown"; 232 ALOGW("Discarding pending exception (%s) to throw %s", details, className); 233 ExpandableStringRelease(&summary); 234 (*env)->DeleteLocalRef(env, exception); 235 } 236 237 static int ThrowException(JNIEnv* env, const char* className, const char* ctorSig, ...) { 238 int status = -1; 239 jclass exceptionClass = NULL; 240 241 va_list args; 242 va_start(args, ctorSig); 243 244 DiscardPendingException(env, className); 245 246 { 247 /* We want to clean up local references before returning from this function, so, 248 * regardless of return status, the end block must run. Have the work done in a 249 * nested block to avoid using any uninitialized variables in the end block. */ 250 exceptionClass = (*env)->FindClass(env, className); 251 if (exceptionClass == NULL) { 252 ALOGE("Unable to find exception class %s", className); 253 /* an exception, most likely ClassNotFoundException, will now be pending */ 254 goto end; 255 } 256 257 jmethodID init = (*env)->GetMethodID(env, exceptionClass, "<init>", ctorSig); 258 if(init == NULL) { 259 ALOGE("Failed to find constructor for '%s' '%s'", className, ctorSig); 260 goto end; 261 } 262 263 jobject instance = (*env)->NewObjectV(env, exceptionClass, init, args); 264 if (instance == NULL) { 265 ALOGE("Failed to construct '%s'", className); 266 goto end; 267 } 268 269 if ((*env)->Throw(env, (jthrowable)instance) != JNI_OK) { 270 ALOGE("Failed to throw '%s'", className); 271 /* an exception, most likely OOM, will now be pending */ 272 goto end; 273 } 274 275 /* everything worked fine, just update status to success and clean up */ 276 status = 0; 277 } 278 279 end: 280 va_end(args); 281 if (exceptionClass != NULL) { 282 (*env)->DeleteLocalRef(env, exceptionClass); 283 } 284 return status; 285 } 286 287 static jstring CreateExceptionMsg(JNIEnv* env, const char* msg) { 288 jstring detailMessage = (*env)->NewStringUTF(env, msg); 289 if (detailMessage == NULL) { 290 /* Not really much we can do here. We're probably dead in the water, 291 but let's try to stumble on... */ 292 (*env)->ExceptionClear(env); 293 } 294 return detailMessage; 295 } 296 297 /* Helper macro to deal with conversion of the exception message from a C string 298 * to jstring. 299 * 300 * This is useful because most exceptions have a message as the first parameter 301 * and delegating the conversion to all the callers of ThrowException results in 302 * code duplication. However, since we try to allow variable number of arguments 303 * for the exception constructor we'd either need to do the conversion inside 304 * the macro, or manipulate the va_list to replace the C string to a jstring. 305 * This seems like the cleaner solution. 306 */ 307 #define THROW_EXCEPTION_WITH_MESSAGE(env, className, ctorSig, msg, ...) ({ \ 308 jstring _detailMessage = CreateExceptionMsg(env, msg); \ 309 int _status = ThrowException(env, className, ctorSig, _detailMessage, ## __VA_ARGS__); \ 310 if (_detailMessage != NULL) { \ 311 (*env)->DeleteLocalRef(env, _detailMessage); \ 312 } \ 313 _status; }) 314 315 // 316 // JNIHelp external API 317 // 318 319 int jniRegisterNativeMethods(JNIEnv* env, const char* className, 320 const JNINativeMethod* methods, int numMethods) 321 { 322 ALOGV("Registering %s's %d native methods...", className, numMethods); 323 jclass clazz = (*env)->FindClass(env, className); 324 ALOG_ALWAYS_FATAL_IF(clazz == NULL, 325 "Native registration unable to find class '%s'; aborting...", 326 className); 327 int result = (*env)->RegisterNatives(env, clazz, methods, numMethods); 328 (*env)->DeleteLocalRef(env, clazz); 329 if (result == 0) { 330 return 0; 331 } 332 333 // Failure to register natives is fatal. Try to report the corresponding exception, 334 // otherwise abort with generic failure message. 335 jthrowable thrown = (*env)->ExceptionOccurred(env); 336 if (thrown != NULL) { 337 struct ExpandableString summary; 338 ExpandableStringInitialize(&summary); 339 if (GetExceptionSummary(env, thrown, &summary)) { 340 ALOGF("%s", summary.data); 341 } 342 ExpandableStringRelease(&summary); 343 (*env)->DeleteLocalRef(env, thrown); 344 } 345 ALOGF("RegisterNatives failed for '%s'; aborting...", className); 346 return result; 347 } 348 349 void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable thrown) { 350 struct ExpandableString summary; 351 ExpandableStringInitialize(&summary); 352 GetStackTraceOrSummary(env, thrown, &summary); 353 const char* details = (summary.data != NULL) ? summary.data : "No memory to report exception"; 354 __android_log_write(priority, tag, details); 355 ExpandableStringRelease(&summary); 356 } 357 358 int jniThrowException(JNIEnv* env, const char* className, const char* message) { 359 return THROW_EXCEPTION_WITH_MESSAGE(env, className, "(Ljava/lang/String;)V", message); 360 } 361 362 int jniThrowExceptionFmt(JNIEnv* env, const char* className, const char* fmt, va_list args) { 363 char msgBuf[512]; 364 vsnprintf(msgBuf, sizeof(msgBuf), fmt, args); 365 return jniThrowException(env, className, msgBuf); 366 } 367 368 int jniThrowNullPointerException(JNIEnv* env, const char* msg) { 369 return jniThrowException(env, "java/lang/NullPointerException", msg); 370 } 371 372 int jniThrowRuntimeException(JNIEnv* env, const char* msg) { 373 return jniThrowException(env, "java/lang/RuntimeException", msg); 374 } 375 376 int jniThrowIOException(JNIEnv* env, int errno_value) { 377 char buffer[80]; 378 const char* message = platformStrError(errno_value, buffer, sizeof(buffer)); 379 return jniThrowException(env, "java/io/IOException", message); 380 } 381 382 int jniThrowErrnoException(JNIEnv* env, const char* functionName, int errno_value) { 383 return THROW_EXCEPTION_WITH_MESSAGE(env, "android/system/ErrnoException", 384 "(Ljava/lang/String;I)V", functionName, errno_value); 385 } 386 387 jstring jniCreateString(JNIEnv* env, const jchar* unicodeChars, jsize len) { 388 return (*env)->NewString(env, unicodeChars, len); 389 } 390