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