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