blob: 6538ddf5c5f43102eec536f79dc07ae2795d8326 [file] [log] [blame]
The Android Open Source Project7f844dd2009-03-03 19:28:47 -08001/*
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 */
Orion Hodsonce487362020-04-22 19:41:46 +010023#pragma once
The Android Open Source Project7f844dd2009-03-03 19:28:47 -080024
Orion Hodson511dfd52020-06-02 12:18:23 +010025#include <sys/cdefs.h>
26
Dan Albert099d5212014-05-21 13:25:01 -070027#include <errno.h>
John Recka3d2b632021-05-18 17:00:47 -040028#include <stdarg.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
Dan Bornstein6edaa472009-10-26 13:33:22 -070032#include <unistd.h>
The Android Open Source Project7f844dd2009-03-03 19:28:47 -080033
Orion Hodson511dfd52020-06-02 12:18:23 +010034#include <jni.h>
Orion Hodsonb01e7fe2018-11-07 06:07:50 +000035
John Recka3d2b632021-05-18 17:00:47 -040036#include <android/log.h>
37
38// Avoid formatting this as it must match webview's usage (webview/graphics_utils.cpp).
39// clang-format off
The Android Open Source Project7f844dd2009-03-03 19:28:47 -080040#ifndef NELEM
Orion Hodson85cdde82019-10-24 15:48:11 +010041#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
The Android Open Source Project7f844dd2009-03-03 19:28:47 -080042#endif
John Recka3d2b632021-05-18 17:00:47 -040043// clang-format on
The Android Open Source Project7f844dd2009-03-03 19:28:47 -080044
John Recka3d2b632021-05-18 17:00:47 -040045/*
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)
Orion Hodson511dfd52020-06-02 12:18:23 +010050
John Recka3d2b632021-05-18 17:00:47 -040051namespace android::jnihelp {
52struct [[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
335end:
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
Orion Hodson511dfd52020-06-02 12:18:23 +0100353
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 */
John Recka3d2b632021-05-18 17:00:47 -0400358[[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}
Orion Hodson511dfd52020-06-02 12:18:23 +0100390
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 */
John Recka3d2b632021-05-18 17:00:47 -0400405[[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}
Orion Hodson511dfd52020-06-02 12:18:23 +0100429
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 */
John Recka3d2b632021-05-18 17:00:47 -0400444[[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}
Orion Hodson511dfd52020-06-02 12:18:23 +0100453
John Recka3d2b632021-05-18 17:00:47 -0400454[[maybe_unused]] static int jniThrowNullPointerException(JNIEnv* env, const char* msg) {
455 return jniThrowException(env, "java/lang/NullPointerException", msg);
456}
Orion Hodson511dfd52020-06-02 12:18:23 +0100457
John Recka3d2b632021-05-18 17:00:47 -0400458[[maybe_unused]] static int jniThrowRuntimeException(JNIEnv* env, const char* msg) {
459 return jniThrowException(env, "java/lang/RuntimeException", msg);
460}
Orion Hodson511dfd52020-06-02 12:18:23 +0100461
John Recka3d2b632021-05-18 17:00:47 -0400462[[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}
Orion Hodson511dfd52020-06-02 12:18:23 +0100468
469/*
Orion Hodson511dfd52020-06-02 12:18:23 +0100470 * 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 */
John Recka3d2b632021-05-18 17:00:47 -0400473[[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}
Orion Hodson511dfd52020-06-02 12:18:23 +0100482
483/*
Orion Hodson511dfd52020-06-02 12:18:23 +0100484 * Log a message and an exception.
485 * If exception is NULL, logs the current exception in the JNI environment.
486 */
John Recka3d2b632021-05-18 17:00:47 -0400487[[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);
The Android Open Source Project7f844dd2009-03-03 19:28:47 -0800496}
Elliott Hughes87f62a82011-04-22 19:22:54 -0700497
John Recka3d2b632021-05-18 17:00:47 -0400498#else // defined(__cplusplus)
Elliott Hughes2f82af12011-04-08 17:15:16 -0700499
John Recka3d2b632021-05-18 17:00:47 -0400500// ART-internal only methods (not exported), exposed for legacy C users
Elliott Hughes2f82af12011-04-08 17:15:16 -0700501
John Recka3d2b632021-05-18 17:00:47 -0400502int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods,
503 int numMethods);
Elliott Hughes87f62a82011-04-22 19:22:54 -0700504
John Recka3d2b632021-05-18 17:00:47 -0400505void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable thrown);
Elliott Hughes87f62a82011-04-22 19:22:54 -0700506
John Recka3d2b632021-05-18 17:00:47 -0400507int jniThrowException(JNIEnv* env, const char* className, const char* msg);
Elliott Hughes87f62a82011-04-22 19:22:54 -0700508
John Recka3d2b632021-05-18 17:00:47 -0400509int jniThrowNullPointerException(JNIEnv* env, const char* msg);
Sorin Bascab3663802021-03-04 14:41:10 +0000510
John Recka3d2b632021-05-18 17:00:47 -0400511#endif // defined(__cplusplus)