• 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     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