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