• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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