1 /* 2 * Copyright (C) 2007 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 /* 18 * JNI helper functions. 19 * 20 * This file may be included by C or C++ code, which is trouble because jni.h 21 * uses different typedefs for JNIEnv in each language. 22 */ 23 #pragma once 24 25 #include <sys/cdefs.h> 26 27 #include <errno.h> 28 #include <stdarg.h> 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <unistd.h> 33 34 #include <jni.h> 35 36 #include <android/log.h> 37 38 // Avoid formatting this as it must match webview's usage (webview/graphics_utils.cpp). 39 // clang-format off 40 #ifndef NELEM 41 #define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) 42 #endif 43 // clang-format on 44 45 /* 46 * For C++ code, we provide inlines that map to the C functions. g++ always 47 * inlines these, even on non-optimized builds. 48 */ 49 #if defined(__cplusplus) 50 51 namespace android::jnihelp { 52 struct [[maybe_unused]] ExpandableString { 53 size_t dataSize; // The length of the C string data (not including the null-terminator). 54 char* data; // The C string data. 55 }; 56 57 [[maybe_unused]] static void ExpandableStringInitialize(struct ExpandableString* s) { 58 memset(s, 0, sizeof(*s)); 59 } 60 61 [[maybe_unused]] static void ExpandableStringRelease(struct ExpandableString* s) { 62 free(s->data); 63 memset(s, 0, sizeof(*s)); 64 } 65 66 [[maybe_unused]] static bool ExpandableStringAppend(struct ExpandableString* s, const char* text) { 67 size_t textSize = strlen(text); 68 size_t requiredSize = s->dataSize + textSize + 1; 69 char* data = (char*)realloc(s->data, requiredSize); 70 if (data == NULL) { 71 return false; 72 } 73 s->data = data; 74 memcpy(s->data + s->dataSize, text, textSize + 1); 75 s->dataSize += textSize; 76 return true; 77 } 78 79 [[maybe_unused]] static bool ExpandableStringAssign(struct ExpandableString* s, const char* text) { 80 ExpandableStringRelease(s); 81 return ExpandableStringAppend(s, text); 82 } 83 84 [[maybe_unused]] inline char* safe_strerror(char* (*strerror_r_method)(int, char*, size_t), 85 int errnum, char* buf, size_t buflen) { 86 return strerror_r_method(errnum, buf, buflen); 87 } 88 89 [[maybe_unused]] inline char* safe_strerror(int (*strerror_r_method)(int, char*, size_t), 90 int errnum, char* buf, size_t buflen) { 91 int rc = strerror_r_method(errnum, buf, buflen); 92 if (rc != 0) { 93 snprintf(buf, buflen, "errno %d", errnum); 94 } 95 return buf; 96 } 97 98 [[maybe_unused]] static const char* platformStrError(int errnum, char* buf, size_t buflen) { 99 return safe_strerror(strerror_r, errnum, buf, buflen); 100 } 101 102 [[maybe_unused]] static jmethodID FindMethod(JNIEnv* env, const char* className, 103 const char* methodName, const char* descriptor) { 104 // This method is only valid for classes in the core library which are 105 // not unloaded during the lifetime of managed code execution. 106 jclass clazz = env->FindClass(className); 107 jmethodID methodId = env->GetMethodID(clazz, methodName, descriptor); 108 env->DeleteLocalRef(clazz); 109 return methodId; 110 } 111 112 [[maybe_unused]] static bool AppendJString(JNIEnv* env, jstring text, 113 struct ExpandableString* dst) { 114 const char* utfText = env->GetStringUTFChars(text, NULL); 115 if (utfText == NULL) { 116 return false; 117 } 118 bool success = ExpandableStringAppend(dst, utfText); 119 env->ReleaseStringUTFChars(text, utfText); 120 return success; 121 } 122 123 /* 124 * Returns a human-readable summary of an exception object. The buffer will 125 * be populated with the "binary" class name and, if present, the 126 * exception message. 127 */ 128 [[maybe_unused]] static bool GetExceptionSummary(JNIEnv* env, jthrowable thrown, 129 struct ExpandableString* dst) { 130 // Summary is <exception_class_name> ": " <exception_message> 131 jclass exceptionClass = env->GetObjectClass(thrown); // Always succeeds 132 jmethodID getName = FindMethod(env, "java/lang/Class", "getName", "()Ljava/lang/String;"); 133 jstring className = (jstring)env->CallObjectMethod(exceptionClass, getName); 134 if (className == NULL) { 135 ExpandableStringAssign(dst, "<error getting class name>"); 136 env->ExceptionClear(); 137 env->DeleteLocalRef(exceptionClass); 138 return false; 139 } 140 env->DeleteLocalRef(exceptionClass); 141 exceptionClass = NULL; 142 143 if (!AppendJString(env, className, dst)) { 144 ExpandableStringAssign(dst, "<error getting class name UTF-8>"); 145 env->ExceptionClear(); 146 env->DeleteLocalRef(className); 147 return false; 148 } 149 env->DeleteLocalRef(className); 150 className = NULL; 151 152 jmethodID getMessage = 153 FindMethod(env, "java/lang/Throwable", "getMessage", "()Ljava/lang/String;"); 154 jstring message = (jstring)env->CallObjectMethod(thrown, getMessage); 155 if (message == NULL) { 156 return true; 157 } 158 159 bool success = (ExpandableStringAppend(dst, ": ") && AppendJString(env, message, dst)); 160 if (!success) { 161 // Two potential reasons for reaching here: 162 // 163 // 1. managed heap allocation failure (OOME). 164 // 2. native heap allocation failure for the storage in |dst|. 165 // 166 // Attempt to append failure notification, okay to fail, |dst| contains the class name 167 // of |thrown|. 168 ExpandableStringAppend(dst, "<error getting message>"); 169 // Clear OOME if present. 170 env->ExceptionClear(); 171 } 172 env->DeleteLocalRef(message); 173 message = NULL; 174 return success; 175 } 176 177 [[maybe_unused]] static jobject NewStringWriter(JNIEnv* env) { 178 jclass clazz = env->FindClass("java/io/StringWriter"); 179 jmethodID init = env->GetMethodID(clazz, "<init>", "()V"); 180 jobject instance = env->NewObject(clazz, init); 181 env->DeleteLocalRef(clazz); 182 return instance; 183 } 184 185 [[maybe_unused]] static jstring StringWriterToString(JNIEnv* env, jobject stringWriter) { 186 jmethodID toString = 187 FindMethod(env, "java/io/StringWriter", "toString", "()Ljava/lang/String;"); 188 return (jstring)env->CallObjectMethod(stringWriter, toString); 189 } 190 191 [[maybe_unused]] static jobject NewPrintWriter(JNIEnv* env, jobject writer) { 192 jclass clazz = env->FindClass("java/io/PrintWriter"); 193 jmethodID init = env->GetMethodID(clazz, "<init>", "(Ljava/io/Writer;)V"); 194 jobject instance = env->NewObject(clazz, init, writer); 195 env->DeleteLocalRef(clazz); 196 return instance; 197 } 198 199 [[maybe_unused]] static bool GetStackTrace(JNIEnv* env, jthrowable thrown, 200 struct ExpandableString* dst) { 201 // This function is equivalent to the following Java snippet: 202 // StringWriter sw = new StringWriter(); 203 // PrintWriter pw = new PrintWriter(sw); 204 // thrown.printStackTrace(pw); 205 // String trace = sw.toString(); 206 // return trace; 207 jobject sw = NewStringWriter(env); 208 if (sw == NULL) { 209 return false; 210 } 211 212 jobject pw = NewPrintWriter(env, sw); 213 if (pw == NULL) { 214 env->DeleteLocalRef(sw); 215 return false; 216 } 217 218 jmethodID printStackTrace = 219 FindMethod(env, "java/lang/Throwable", "printStackTrace", "(Ljava/io/PrintWriter;)V"); 220 env->CallVoidMethod(thrown, printStackTrace, pw); 221 222 jstring trace = StringWriterToString(env, sw); 223 224 env->DeleteLocalRef(pw); 225 pw = NULL; 226 env->DeleteLocalRef(sw); 227 sw = NULL; 228 229 if (trace == NULL) { 230 return false; 231 } 232 233 bool success = AppendJString(env, trace, dst); 234 env->DeleteLocalRef(trace); 235 return success; 236 } 237 238 [[maybe_unused]] static void GetStackTraceOrSummary(JNIEnv* env, jthrowable thrown, 239 struct ExpandableString* dst) { 240 // This method attempts to get a stack trace or summary info for an exception. 241 // The exception may be provided in the |thrown| argument to this function. 242 // If |thrown| is NULL, then any pending exception is used if it exists. 243 244 // Save pending exception, callees may raise other exceptions. Any pending exception is 245 // rethrown when this function exits. 246 jthrowable pendingException = env->ExceptionOccurred(); 247 if (pendingException != NULL) { 248 env->ExceptionClear(); 249 } 250 251 if (thrown == NULL) { 252 if (pendingException == NULL) { 253 ExpandableStringAssign(dst, "<no pending exception>"); 254 return; 255 } 256 thrown = pendingException; 257 } 258 259 if (!GetStackTrace(env, thrown, dst)) { 260 // GetStackTrace may have raised an exception, clear it since it's not for the caller. 261 env->ExceptionClear(); 262 GetExceptionSummary(env, thrown, dst); 263 } 264 265 if (pendingException != NULL) { 266 // Re-throw the pending exception present when this method was called. 267 env->Throw(pendingException); 268 env->DeleteLocalRef(pendingException); 269 } 270 } 271 272 [[maybe_unused]] static void DiscardPendingException(JNIEnv* env, const char* className) { 273 jthrowable exception = env->ExceptionOccurred(); 274 env->ExceptionClear(); 275 if (exception == NULL) { 276 return; 277 } 278 279 struct ExpandableString summary; 280 ExpandableStringInitialize(&summary); 281 GetExceptionSummary(env, exception, &summary); 282 const char* details = (summary.data != NULL) ? summary.data : "Unknown"; 283 __android_log_print(ANDROID_LOG_WARN, "JNIHelp", 284 "Discarding pending exception (%s) to throw %s", details, className); 285 ExpandableStringRelease(&summary); 286 env->DeleteLocalRef(exception); 287 } 288 289 [[maybe_unused]] static int ThrowException(JNIEnv* env, const char* className, const char* ctorSig, 290 ...) { 291 int status = -1; 292 jclass exceptionClass = NULL; 293 294 va_list args; 295 va_start(args, ctorSig); 296 297 DiscardPendingException(env, className); 298 299 { 300 /* We want to clean up local references before returning from this function, so, 301 * regardless of return status, the end block must run. Have the work done in a 302 * nested block to avoid using any uninitialized variables in the end block. */ 303 exceptionClass = env->FindClass(className); 304 if (exceptionClass == NULL) { 305 __android_log_print(ANDROID_LOG_ERROR, "JNIHelp", "Unable to find exception class %s", 306 className); 307 /* an exception, most likely ClassNotFoundException, will now be pending */ 308 goto end; 309 } 310 311 jmethodID init = env->GetMethodID(exceptionClass, "<init>", ctorSig); 312 if (init == NULL) { 313 __android_log_print(ANDROID_LOG_ERROR, "JNIHelp", 314 "Failed to find constructor for '%s' '%s'", className, ctorSig); 315 goto end; 316 } 317 318 jobject instance = env->NewObjectV(exceptionClass, init, args); 319 if (instance == NULL) { 320 __android_log_print(ANDROID_LOG_ERROR, "JNIHelp", "Failed to construct '%s'", 321 className); 322 goto end; 323 } 324 325 if (env->Throw((jthrowable)instance) != JNI_OK) { 326 __android_log_print(ANDROID_LOG_ERROR, "JNIHelp", "Failed to throw '%s'", className); 327 /* an exception, most likely OOM, will now be pending */ 328 goto end; 329 } 330 331 /* everything worked fine, just update status to success and clean up */ 332 status = 0; 333 } 334 335 end: 336 va_end(args); 337 if (exceptionClass != NULL) { 338 env->DeleteLocalRef(exceptionClass); 339 } 340 return status; 341 } 342 343 [[maybe_unused]] static jstring CreateExceptionMsg(JNIEnv* env, const char* msg) { 344 jstring detailMessage = env->NewStringUTF(msg); 345 if (detailMessage == NULL) { 346 /* Not really much we can do here. We're probably dead in the water, 347 but let's try to stumble on... */ 348 env->ExceptionClear(); 349 } 350 return detailMessage; 351 } 352 } // namespace android::jnihelp 353 354 /* 355 * Register one or more native methods with a particular class. "className" looks like 356 * "java/lang/String". Aborts on failure, returns 0 on success. 357 */ 358 [[maybe_unused]] static int jniRegisterNativeMethods(JNIEnv* env, const char* className, 359 const JNINativeMethod* methods, 360 int numMethods) { 361 using namespace android::jnihelp; 362 jclass clazz = env->FindClass(className); 363 if (clazz == NULL) { 364 __android_log_assert("clazz == NULL", "JNIHelp", 365 "Native registration unable to find class '%s'; aborting...", 366 className); 367 } 368 int result = env->RegisterNatives(clazz, methods, numMethods); 369 env->DeleteLocalRef(clazz); 370 if (result == 0) { 371 return 0; 372 } 373 374 // Failure to register natives is fatal. Try to report the corresponding exception, 375 // otherwise abort with generic failure message. 376 jthrowable thrown = env->ExceptionOccurred(); 377 if (thrown != NULL) { 378 struct ExpandableString summary; 379 ExpandableStringInitialize(&summary); 380 if (GetExceptionSummary(env, thrown, &summary)) { 381 __android_log_print(ANDROID_LOG_FATAL, "JNIHelp", "%s", summary.data); 382 } 383 ExpandableStringRelease(&summary); 384 env->DeleteLocalRef(thrown); 385 } 386 __android_log_print(ANDROID_LOG_FATAL, "JNIHelp", 387 "RegisterNatives failed for '%s'; aborting...", className); 388 return result; 389 } 390 391 /* 392 * Throw an exception with the specified class and an optional message. 393 * 394 * The "className" argument will be passed directly to FindClass, which 395 * takes strings with slashes (e.g. "java/lang/Object"). 396 * 397 * If an exception is currently pending, we log a warning message and 398 * clear it. 399 * 400 * Returns 0 on success, nonzero if something failed (e.g. the exception 401 * class couldn't be found, so *an* exception will still be pending). 402 * 403 * Currently aborts the VM if it can't throw the exception. 404 */ 405 [[maybe_unused]] static int jniThrowException(JNIEnv* env, const char* className, const char* msg) { 406 using namespace android::jnihelp; 407 jstring _detailMessage = CreateExceptionMsg(env, msg); 408 int _status = ThrowException(env, className, "(Ljava/lang/String;)V", _detailMessage); 409 if (_detailMessage != NULL) { 410 env->DeleteLocalRef(_detailMessage); 411 } 412 return _status; 413 } 414 415 /* 416 * Throw an android.system.ErrnoException, with the given function name and errno value. 417 */ 418 [[maybe_unused]] static int jniThrowErrnoException(JNIEnv* env, const char* functionName, 419 int errnum) { 420 using namespace android::jnihelp; 421 jstring _detailMessage = CreateExceptionMsg(env, functionName); 422 int _status = ThrowException(env, "android/system/ErrnoException", "(Ljava/lang/String;I)V", 423 _detailMessage, errnum); 424 if (_detailMessage != NULL) { 425 env->DeleteLocalRef(_detailMessage); 426 } 427 return _status; 428 } 429 430 /* 431 * Throw an exception with the specified class and formatted error message. 432 * 433 * The "className" argument will be passed directly to FindClass, which 434 * takes strings with slashes (e.g. "java/lang/Object"). 435 * 436 * If an exception is currently pending, we log a warning message and 437 * clear it. 438 * 439 * Returns 0 on success, nonzero if something failed (e.g. the exception 440 * class couldn't be found, so *an* exception will still be pending). 441 * 442 * Currently aborts the VM if it can't throw the exception. 443 */ 444 [[maybe_unused]] static int jniThrowExceptionFmt(JNIEnv* env, const char* className, 445 const char* fmt, ...) { 446 va_list args; 447 va_start(args, fmt); 448 char msgBuf[512]; 449 vsnprintf(msgBuf, sizeof(msgBuf), fmt, args); 450 va_end(args); 451 return jniThrowException(env, className, msgBuf); 452 } 453 454 [[maybe_unused]] static int jniThrowNullPointerException(JNIEnv* env, const char* msg) { 455 return jniThrowException(env, "java/lang/NullPointerException", msg); 456 } 457 458 [[maybe_unused]] static int jniThrowRuntimeException(JNIEnv* env, const char* msg) { 459 return jniThrowException(env, "java/lang/RuntimeException", msg); 460 } 461 462 [[maybe_unused]] static int jniThrowIOException(JNIEnv* env, int errno_value) { 463 using namespace android::jnihelp; 464 char buffer[80]; 465 const char* message = platformStrError(errno_value, buffer, sizeof(buffer)); 466 return jniThrowException(env, "java/io/IOException", message); 467 } 468 469 /* 470 * Returns a Java String object created from UTF-16 data either from jchar or, 471 * if called from C++11, char16_t (a bitwise identical distinct type). 472 */ 473 [[maybe_unused]] static inline jstring jniCreateString(JNIEnv* env, const jchar* unicodeChars, 474 jsize len) { 475 return env->NewString(unicodeChars, len); 476 } 477 478 [[maybe_unused]] static inline jstring jniCreateString(JNIEnv* env, const char16_t* unicodeChars, 479 jsize len) { 480 return jniCreateString(env, reinterpret_cast<const jchar*>(unicodeChars), len); 481 } 482 483 /* 484 * Log a message and an exception. 485 * If exception is NULL, logs the current exception in the JNI environment. 486 */ 487 [[maybe_unused]] static void jniLogException(JNIEnv* env, int priority, const char* tag, 488 jthrowable exception = NULL) { 489 using namespace android::jnihelp; 490 struct ExpandableString summary; 491 ExpandableStringInitialize(&summary); 492 GetStackTraceOrSummary(env, exception, &summary); 493 const char* details = (summary.data != NULL) ? summary.data : "No memory to report exception"; 494 __android_log_write(priority, tag, details); 495 ExpandableStringRelease(&summary); 496 } 497 498 #else // defined(__cplusplus) 499 500 // ART-internal only methods (not exported), exposed for legacy C users 501 502 int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, 503 int numMethods); 504 505 void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable thrown); 506 507 int jniThrowException(JNIEnv* env, const char* className, const char* msg); 508 509 int jniThrowNullPointerException(JNIEnv* env, const char* msg); 510 511 #endif // defined(__cplusplus) 512