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