1 /*
2 * Copyright (C) 2006 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 #include "include/nativehelper/JNIHelp.h"
18
19 #include <stdarg.h>
20 #include <stdbool.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24
25 #include <jni.h>
26
27 #define LOG_TAG "JNIHelp"
28 #include "ALog-priv.h"
29
30 #include "ExpandableString.h"
31
32 //
33 // Helper methods
34 //
35
platformStrError(int errnum,char * buf,size_t buflen)36 static const char* platformStrError(int errnum, char* buf, size_t buflen) {
37 #ifdef _WIN32
38 strerror_s(buf, buflen, errnum);
39 return buf;
40 #elif defined(__USE_GNU) && __ANDROID_API__ >= 23
41 // char *strerror_r(int errnum, char *buf, size_t buflen); /* GNU-specific */
42 return strerror_r(errnum, buf, buflen);
43 #else
44 // int strerror_r(int errnum, char *buf, size_t buflen); /* XSI-compliant */
45 int rc = strerror_r(errnum, buf, buflen);
46 if (rc != 0) {
47 snprintf(buf, buflen, "errno %d", errnum);
48 }
49 return buf;
50 #endif
51 }
52
FindMethod(JNIEnv * env,const char * className,const char * methodName,const char * descriptor)53 static jmethodID FindMethod(JNIEnv* env,
54 const char* className,
55 const char* methodName,
56 const char* descriptor) {
57 // This method is only valid for classes in the core library which are
58 // not unloaded during the lifetime of managed code execution.
59 jclass clazz = (*env)->FindClass(env, className);
60 jmethodID methodId = (*env)->GetMethodID(env, clazz, methodName, descriptor);
61 (*env)->DeleteLocalRef(env, clazz);
62 return methodId;
63 }
64
AppendJString(JNIEnv * env,jstring text,struct ExpandableString * dst)65 static bool AppendJString(JNIEnv* env, jstring text, struct ExpandableString* dst) {
66 const char* utfText = (*env)->GetStringUTFChars(env, text, NULL);
67 if (utfText == NULL) {
68 return false;
69 }
70 bool success = ExpandableStringAppend(dst, utfText);
71 (*env)->ReleaseStringUTFChars(env, text, utfText);
72 return success;
73 }
74
75 /*
76 * Returns a human-readable summary of an exception object. The buffer will
77 * be populated with the "binary" class name and, if present, the
78 * exception message.
79 */
GetExceptionSummary(JNIEnv * env,jthrowable thrown,struct ExpandableString * dst)80 static bool GetExceptionSummary(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) {
81 // Summary is <exception_class_name> ": " <exception_message>
82 jclass exceptionClass = (*env)->GetObjectClass(env, thrown); // Always succeeds
83 jmethodID getName = FindMethod(env, "java/lang/Class", "getName", "()Ljava/lang/String;");
84 jstring className = (jstring) (*env)->CallObjectMethod(env, exceptionClass, getName);
85 if (className == NULL) {
86 ExpandableStringAssign(dst, "<error getting class name>");
87 (*env)->ExceptionClear(env);
88 (*env)->DeleteLocalRef(env, exceptionClass);
89 return false;
90 }
91 (*env)->DeleteLocalRef(env, exceptionClass);
92 exceptionClass = NULL;
93
94 if (!AppendJString(env, className, dst)) {
95 ExpandableStringAssign(dst, "<error getting class name UTF-8>");
96 (*env)->ExceptionClear(env);
97 (*env)->DeleteLocalRef(env, className);
98 return false;
99 }
100 (*env)->DeleteLocalRef(env, className);
101 className = NULL;
102
103 jmethodID getMessage =
104 FindMethod(env, "java/lang/Throwable", "getMessage", "()Ljava/lang/String;");
105 jstring message = (jstring) (*env)->CallObjectMethod(env, thrown, getMessage);
106 if (message == NULL) {
107 return true;
108 }
109
110 bool success = (ExpandableStringAppend(dst, ": ") && AppendJString(env, message, dst));
111 if (!success) {
112 // Two potential reasons for reaching here:
113 //
114 // 1. managed heap allocation failure (OOME).
115 // 2. native heap allocation failure for the storage in |dst|.
116 //
117 // Attempt to append failure notification, okay to fail, |dst| contains the class name
118 // of |thrown|.
119 ExpandableStringAppend(dst, "<error getting message>");
120 // Clear OOME if present.
121 (*env)->ExceptionClear(env);
122 }
123 (*env)->DeleteLocalRef(env, message);
124 message = NULL;
125 return success;
126 }
127
NewStringWriter(JNIEnv * env)128 static jobject NewStringWriter(JNIEnv* env) {
129 jclass clazz = (*env)->FindClass(env, "java/io/StringWriter");
130 jmethodID init = (*env)->GetMethodID(env, clazz, "<init>", "()V");
131 jobject instance = (*env)->NewObject(env, clazz, init);
132 (*env)->DeleteLocalRef(env, clazz);
133 return instance;
134 }
135
StringWriterToString(JNIEnv * env,jobject stringWriter)136 static jstring StringWriterToString(JNIEnv* env, jobject stringWriter) {
137 jmethodID toString =
138 FindMethod(env, "java/io/StringWriter", "toString", "()Ljava/lang/String;");
139 return (jstring) (*env)->CallObjectMethod(env, stringWriter, toString);
140 }
141
NewPrintWriter(JNIEnv * env,jobject writer)142 static jobject NewPrintWriter(JNIEnv* env, jobject writer) {
143 jclass clazz = (*env)->FindClass(env, "java/io/PrintWriter");
144 jmethodID init = (*env)->GetMethodID(env, clazz, "<init>", "(Ljava/io/Writer;)V");
145 jobject instance = (*env)->NewObject(env, clazz, init, writer);
146 (*env)->DeleteLocalRef(env, clazz);
147 return instance;
148 }
149
GetStackTrace(JNIEnv * env,jthrowable thrown,struct ExpandableString * dst)150 static bool GetStackTrace(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) {
151 // This function is equivalent to the following Java snippet:
152 // StringWriter sw = new StringWriter();
153 // PrintWriter pw = new PrintWriter(sw);
154 // thrown.printStackTrace(pw);
155 // String trace = sw.toString();
156 // return trace;
157 jobject sw = NewStringWriter(env);
158 if (sw == NULL) {
159 return false;
160 }
161
162 jobject pw = NewPrintWriter(env, sw);
163 if (pw == NULL) {
164 (*env)->DeleteLocalRef(env, sw);
165 return false;
166 }
167
168 jmethodID printStackTrace =
169 FindMethod(env, "java/lang/Throwable", "printStackTrace", "(Ljava/io/PrintWriter;)V");
170 (*env)->CallVoidMethod(env, thrown, printStackTrace, pw);
171
172 jstring trace = StringWriterToString(env, sw);
173
174 (*env)->DeleteLocalRef(env, pw);
175 pw = NULL;
176 (*env)->DeleteLocalRef(env, sw);
177 sw = NULL;
178
179 if (trace == NULL) {
180 return false;
181 }
182
183 bool success = AppendJString(env, trace, dst);
184 (*env)->DeleteLocalRef(env, trace);
185 return success;
186 }
187
GetStackTraceOrSummary(JNIEnv * env,jthrowable thrown,struct ExpandableString * dst)188 static void GetStackTraceOrSummary(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) {
189 // This method attempts to get a stack trace or summary info for an exception.
190 // The exception may be provided in the |thrown| argument to this function.
191 // If |thrown| is NULL, then any pending exception is used if it exists.
192
193 // Save pending exception, callees may raise other exceptions. Any pending exception is
194 // rethrown when this function exits.
195 jthrowable pendingException = (*env)->ExceptionOccurred(env);
196 if (pendingException != NULL) {
197 (*env)->ExceptionClear(env);
198 }
199
200 if (thrown == NULL) {
201 if (pendingException == NULL) {
202 ExpandableStringAssign(dst, "<no pending exception>");
203 return;
204 }
205 thrown = pendingException;
206 }
207
208 if (!GetStackTrace(env, thrown, dst)) {
209 // GetStackTrace may have raised an exception, clear it since it's not for the caller.
210 (*env)->ExceptionClear(env);
211 GetExceptionSummary(env, thrown, dst);
212 }
213
214 if (pendingException != NULL) {
215 // Re-throw the pending exception present when this method was called.
216 (*env)->Throw(env, pendingException);
217 (*env)->DeleteLocalRef(env, pendingException);
218 }
219 }
220
DiscardPendingException(JNIEnv * env,const char * className)221 static void DiscardPendingException(JNIEnv* env, const char* className) {
222 jthrowable exception = (*env)->ExceptionOccurred(env);
223 (*env)->ExceptionClear(env);
224 if (exception == NULL) {
225 return;
226 }
227
228 struct ExpandableString summary;
229 ExpandableStringInitialize(&summary);
230 GetExceptionSummary(env, exception, &summary);
231 const char* details = (summary.data != NULL) ? summary.data : "Unknown";
232 ALOGW("Discarding pending exception (%s) to throw %s", details, className);
233 ExpandableStringRelease(&summary);
234 (*env)->DeleteLocalRef(env, exception);
235 }
236
ThrowException(JNIEnv * env,const char * className,const char * ctorSig,...)237 static int ThrowException(JNIEnv* env, const char* className, const char* ctorSig, ...) {
238 int status = -1;
239 jclass exceptionClass = NULL;
240
241 va_list args;
242 va_start(args, ctorSig);
243
244 DiscardPendingException(env, className);
245
246 {
247 /* We want to clean up local references before returning from this function, so,
248 * regardless of return status, the end block must run. Have the work done in a
249 * nested block to avoid using any uninitialized variables in the end block. */
250 exceptionClass = (*env)->FindClass(env, className);
251 if (exceptionClass == NULL) {
252 ALOGE("Unable to find exception class %s", className);
253 /* an exception, most likely ClassNotFoundException, will now be pending */
254 goto end;
255 }
256
257 jmethodID init = (*env)->GetMethodID(env, exceptionClass, "<init>", ctorSig);
258 if(init == NULL) {
259 ALOGE("Failed to find constructor for '%s' '%s'", className, ctorSig);
260 goto end;
261 }
262
263 jobject instance = (*env)->NewObjectV(env, exceptionClass, init, args);
264 if (instance == NULL) {
265 ALOGE("Failed to construct '%s'", className);
266 goto end;
267 }
268
269 if ((*env)->Throw(env, (jthrowable)instance) != JNI_OK) {
270 ALOGE("Failed to throw '%s'", className);
271 /* an exception, most likely OOM, will now be pending */
272 goto end;
273 }
274
275 /* everything worked fine, just update status to success and clean up */
276 status = 0;
277 }
278
279 end:
280 va_end(args);
281 if (exceptionClass != NULL) {
282 (*env)->DeleteLocalRef(env, exceptionClass);
283 }
284 return status;
285 }
286
CreateExceptionMsg(JNIEnv * env,const char * msg)287 static jstring CreateExceptionMsg(JNIEnv* env, const char* msg) {
288 jstring detailMessage = (*env)->NewStringUTF(env, msg);
289 if (detailMessage == NULL) {
290 /* Not really much we can do here. We're probably dead in the water,
291 but let's try to stumble on... */
292 (*env)->ExceptionClear(env);
293 }
294 return detailMessage;
295 }
296
297 /* Helper macro to deal with conversion of the exception message from a C string
298 * to jstring.
299 *
300 * This is useful because most exceptions have a message as the first parameter
301 * and delegating the conversion to all the callers of ThrowException results in
302 * code duplication. However, since we try to allow variable number of arguments
303 * for the exception constructor we'd either need to do the conversion inside
304 * the macro, or manipulate the va_list to replace the C string to a jstring.
305 * This seems like the cleaner solution.
306 */
307 #define THROW_EXCEPTION_WITH_MESSAGE(env, className, ctorSig, msg, ...) ({ \
308 jstring _detailMessage = CreateExceptionMsg(env, msg); \
309 int _status = ThrowException(env, className, ctorSig, _detailMessage, ## __VA_ARGS__); \
310 if (_detailMessage != NULL) { \
311 (*env)->DeleteLocalRef(env, _detailMessage); \
312 } \
313 _status; })
314
315 //
316 // JNIHelp external API
317 //
318
jniRegisterNativeMethods(JNIEnv * env,const char * className,const JNINativeMethod * methods,int numMethods)319 int jniRegisterNativeMethods(JNIEnv* env, const char* className,
320 const JNINativeMethod* methods, int numMethods)
321 {
322 ALOGV("Registering %s's %d native methods...", className, numMethods);
323 jclass clazz = (*env)->FindClass(env, className);
324 ALOG_ALWAYS_FATAL_IF(clazz == NULL,
325 "Native registration unable to find class '%s'; aborting...",
326 className);
327 int result = (*env)->RegisterNatives(env, clazz, methods, numMethods);
328 (*env)->DeleteLocalRef(env, clazz);
329 if (result == 0) {
330 return 0;
331 }
332
333 // Failure to register natives is fatal. Try to report the corresponding exception,
334 // otherwise abort with generic failure message.
335 jthrowable thrown = (*env)->ExceptionOccurred(env);
336 if (thrown != NULL) {
337 struct ExpandableString summary;
338 ExpandableStringInitialize(&summary);
339 if (GetExceptionSummary(env, thrown, &summary)) {
340 ALOGF("%s", summary.data);
341 }
342 ExpandableStringRelease(&summary);
343 (*env)->DeleteLocalRef(env, thrown);
344 }
345 ALOGF("RegisterNatives failed for '%s'; aborting...", className);
346 return result;
347 }
348
jniLogException(JNIEnv * env,int priority,const char * tag,jthrowable thrown)349 void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable thrown) {
350 struct ExpandableString summary;
351 ExpandableStringInitialize(&summary);
352 GetStackTraceOrSummary(env, thrown, &summary);
353 const char* details = (summary.data != NULL) ? summary.data : "No memory to report exception";
354 __android_log_write(priority, tag, details);
355 ExpandableStringRelease(&summary);
356 }
357
jniThrowException(JNIEnv * env,const char * className,const char * message)358 int jniThrowException(JNIEnv* env, const char* className, const char* message) {
359 return THROW_EXCEPTION_WITH_MESSAGE(env, className, "(Ljava/lang/String;)V", message);
360 }
361
jniThrowExceptionFmt(JNIEnv * env,const char * className,const char * fmt,va_list args)362 int jniThrowExceptionFmt(JNIEnv* env, const char* className, const char* fmt, va_list args) {
363 char msgBuf[512];
364 vsnprintf(msgBuf, sizeof(msgBuf), fmt, args);
365 return jniThrowException(env, className, msgBuf);
366 }
367
jniThrowNullPointerException(JNIEnv * env,const char * msg)368 int jniThrowNullPointerException(JNIEnv* env, const char* msg) {
369 return jniThrowException(env, "java/lang/NullPointerException", msg);
370 }
371
jniThrowRuntimeException(JNIEnv * env,const char * msg)372 int jniThrowRuntimeException(JNIEnv* env, const char* msg) {
373 return jniThrowException(env, "java/lang/RuntimeException", msg);
374 }
375
jniThrowIOException(JNIEnv * env,int errno_value)376 int jniThrowIOException(JNIEnv* env, int errno_value) {
377 char buffer[80];
378 const char* message = platformStrError(errno_value, buffer, sizeof(buffer));
379 return jniThrowException(env, "java/io/IOException", message);
380 }
381
jniThrowErrnoException(JNIEnv * env,const char * functionName,int errno_value)382 int jniThrowErrnoException(JNIEnv* env, const char* functionName, int errno_value) {
383 return THROW_EXCEPTION_WITH_MESSAGE(env, "android/system/ErrnoException",
384 "(Ljava/lang/String;I)V", functionName, errno_value);
385 }
386
jniCreateString(JNIEnv * env,const jchar * unicodeChars,jsize len)387 jstring jniCreateString(JNIEnv* env, const jchar* unicodeChars, jsize len) {
388 return (*env)->NewString(env, unicodeChars, len);
389 }
390