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 <stdbool.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 
24 #include <jni.h>
25 
26 #define LOG_TAG "JNIHelp"
27 #include "ALog-priv.h"
28 
29 #include "ExpandableString.h"
30 
31 //
32 // Helper methods
33 //
34 
platformStrError(int errnum,char * buf,size_t buflen)35 static const char* platformStrError(int errnum, char* buf, size_t buflen) {
36 #ifdef _WIN32
37     strerror_s(buf, buflen, errnum);
38     return buf;
39 #elif defined(__USE_GNU) && __ANDROID_API__ >= 23
40     // char *strerror_r(int errnum, char *buf, size_t buflen);  /* GNU-specific */
41     return strerror_r(errnum, buf, buflen);
42 #else
43     // int strerror_r(int errnum, char *buf, size_t buflen);    /* XSI-compliant */
44     int rc = strerror_r(errnum, buf, buflen);
45     if (rc != 0) {
46         snprintf(buf, buflen, "errno %d", errnum);
47     }
48     return buf;
49 #endif
50 }
51 
FindMethod(JNIEnv * env,const char * className,const char * methodName,const char * descriptor)52 static jmethodID FindMethod(JNIEnv* env,
53                             const char* className,
54                             const char* methodName,
55                             const char* descriptor) {
56     // This method is only valid for classes in the core library which are
57     // not unloaded during the lifetime of managed code execution.
58     jclass clazz = (*env)->FindClass(env, className);
59     jmethodID methodId = (*env)->GetMethodID(env, clazz, methodName, descriptor);
60     (*env)->DeleteLocalRef(env, clazz);
61     return methodId;
62 }
63 
AppendJString(JNIEnv * env,jstring text,struct ExpandableString * dst)64 static bool AppendJString(JNIEnv* env, jstring text, struct ExpandableString* dst) {
65     const char* utfText = (*env)->GetStringUTFChars(env, text, NULL);
66     if (utfText == NULL) {
67         return false;
68     }
69     bool success = ExpandableStringAppend(dst, utfText);
70     (*env)->ReleaseStringUTFChars(env, text, utfText);
71     return success;
72 }
73 
74 /*
75  * Returns a human-readable summary of an exception object.  The buffer will
76  * be populated with the "binary" class name and, if present, the
77  * exception message.
78  */
GetExceptionSummary(JNIEnv * env,jthrowable thrown,struct ExpandableString * dst)79 static bool GetExceptionSummary(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) {
80     // Summary is <exception_class_name> ": " <exception_message>
81     jclass exceptionClass = (*env)->GetObjectClass(env, thrown);  // Always succeeds
82     jmethodID getName = FindMethod(env, "java/lang/Class", "getName", "()Ljava/lang/String;");
83     jstring className = (jstring) (*env)->CallObjectMethod(env, exceptionClass, getName);
84     if (className == NULL) {
85         ExpandableStringAssign(dst, "<error getting class name>");
86         (*env)->ExceptionClear(env);
87         (*env)->DeleteLocalRef(env, exceptionClass);
88         return false;
89     }
90     (*env)->DeleteLocalRef(env, exceptionClass);
91     exceptionClass = NULL;
92 
93     if (!AppendJString(env, className, dst)) {
94         ExpandableStringAssign(dst,  "<error getting class name UTF-8>");
95         (*env)->ExceptionClear(env);
96         (*env)->DeleteLocalRef(env, className);
97         return false;
98     }
99     (*env)->DeleteLocalRef(env, className);
100     className = NULL;
101 
102     jmethodID getMessage =
103         FindMethod(env, "java/lang/Throwable", "getMessage", "()Ljava/lang/String;");
104     jstring message = (jstring) (*env)->CallObjectMethod(env, thrown, getMessage);
105     if (message == NULL) {
106         return true;
107     }
108 
109     bool success = (ExpandableStringAppend(dst, ": ") && AppendJString(env, message, dst));
110     if (!success) {
111         // Two potential reasons for reaching here:
112         //
113         // 1. managed heap allocation failure (OOME).
114         // 2. native heap allocation failure for the storage in |dst|.
115         //
116         // Attempt to append failure notification, okay to fail, |dst| contains the class name
117         // of |thrown|.
118         ExpandableStringAppend(dst, "<error getting message>");
119         // Clear OOME if present.
120         (*env)->ExceptionClear(env);
121     }
122     (*env)->DeleteLocalRef(env, message);
123     message = NULL;
124     return success;
125 }
126 
NewStringWriter(JNIEnv * env)127 static jobject NewStringWriter(JNIEnv* env) {
128     jclass clazz = (*env)->FindClass(env, "java/io/StringWriter");
129     jmethodID init = (*env)->GetMethodID(env, clazz, "<init>", "()V");
130     jobject instance = (*env)->NewObject(env, clazz, init);
131     (*env)->DeleteLocalRef(env, clazz);
132     return instance;
133 }
134 
StringWriterToString(JNIEnv * env,jobject stringWriter)135 static jstring StringWriterToString(JNIEnv* env, jobject stringWriter) {
136     jmethodID toString =
137         FindMethod(env, "java/io/StringWriter", "toString", "()Ljava/lang/String;");
138     return (jstring) (*env)->CallObjectMethod(env, stringWriter, toString);
139 }
140 
NewPrintWriter(JNIEnv * env,jobject writer)141 static jobject NewPrintWriter(JNIEnv* env, jobject writer) {
142     jclass clazz = (*env)->FindClass(env, "java/io/PrintWriter");
143     jmethodID init = (*env)->GetMethodID(env, clazz, "<init>", "(Ljava/io/Writer;)V");
144     jobject instance = (*env)->NewObject(env, clazz, init, writer);
145     (*env)->DeleteLocalRef(env, clazz);
146     return instance;
147 }
148 
GetStackTrace(JNIEnv * env,jthrowable thrown,struct ExpandableString * dst)149 static bool GetStackTrace(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) {
150     // This function is equivalent to the following Java snippet:
151     //   StringWriter sw = new StringWriter();
152     //   PrintWriter pw = new PrintWriter(sw);
153     //   thrown.printStackTrace(pw);
154     //   String trace = sw.toString();
155     //   return trace;
156     jobject sw = NewStringWriter(env);
157     if (sw == NULL) {
158         return false;
159     }
160 
161     jobject pw = NewPrintWriter(env, sw);
162     if (pw == NULL) {
163         (*env)->DeleteLocalRef(env, sw);
164         return false;
165     }
166 
167     jmethodID printStackTrace =
168         FindMethod(env, "java/lang/Throwable", "printStackTrace", "(Ljava/io/PrintWriter;)V");
169     (*env)->CallVoidMethod(env, thrown, printStackTrace, pw);
170 
171     jstring trace = StringWriterToString(env, sw);
172 
173     (*env)->DeleteLocalRef(env, pw);
174     pw = NULL;
175     (*env)->DeleteLocalRef(env, sw);
176     sw = NULL;
177 
178     if (trace == NULL) {
179         return false;
180     }
181 
182     bool success = AppendJString(env, trace, dst);
183     (*env)->DeleteLocalRef(env, trace);
184     return success;
185 }
186 
GetStackTraceOrSummary(JNIEnv * env,jthrowable thrown,struct ExpandableString * dst)187 static void GetStackTraceOrSummary(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) {
188     // This method attempts to get a stack trace or summary info for an exception.
189     // The exception may be provided in the |thrown| argument to this function.
190     // If |thrown| is NULL, then any pending exception is used if it exists.
191 
192     // Save pending exception, callees may raise other exceptions. Any pending exception is
193     // rethrown when this function exits.
194     jthrowable pendingException = (*env)->ExceptionOccurred(env);
195     if (pendingException != NULL) {
196         (*env)->ExceptionClear(env);
197     }
198 
199     if (thrown == NULL) {
200         if (pendingException == NULL) {
201             ExpandableStringAssign(dst,  "<no pending exception>");
202             return;
203         }
204         thrown = pendingException;
205     }
206 
207     if (!GetStackTrace(env, thrown, dst)) {
208         // GetStackTrace may have raised an exception, clear it since it's not for the caller.
209         (*env)->ExceptionClear(env);
210         GetExceptionSummary(env, thrown, dst);
211     }
212 
213     if (pendingException != NULL) {
214         // Re-throw the pending exception present when this method was called.
215         (*env)->Throw(env, pendingException);
216         (*env)->DeleteLocalRef(env, pendingException);
217     }
218 }
219 
DiscardPendingException(JNIEnv * env,const char * className)220 static void DiscardPendingException(JNIEnv* env, const char* className) {
221     jthrowable exception = (*env)->ExceptionOccurred(env);
222     (*env)->ExceptionClear(env);
223     if (exception == NULL) {
224         return;
225     }
226 
227     struct ExpandableString summary;
228     ExpandableStringInitialize(&summary);
229     GetExceptionSummary(env, exception, &summary);
230     const char* details = (summary.data != NULL) ? summary.data : "Unknown";
231     ALOGW("Discarding pending exception (%s) to throw %s", details, className);
232     ExpandableStringRelease(&summary);
233     (*env)->DeleteLocalRef(env, exception);
234 }
235 
236 //
237 // JNIHelp external API
238 //
239 
jniRegisterNativeMethods(JNIEnv * env,const char * className,const JNINativeMethod * methods,int numMethods)240 int jniRegisterNativeMethods(JNIEnv* env, const char* className,
241     const JNINativeMethod* methods, int numMethods)
242 {
243     ALOGV("Registering %s's %d native methods...", className, numMethods);
244     jclass clazz = (*env)->FindClass(env, className);
245     ALOG_ALWAYS_FATAL_IF(clazz == NULL,
246                          "Native registration unable to find class '%s'; aborting...",
247                          className);
248     int result = (*env)->RegisterNatives(env, clazz, methods, numMethods);
249     (*env)->DeleteLocalRef(env, clazz);
250     if (result == 0) {
251         return 0;
252     }
253 
254     // Failure to register natives is fatal. Try to report the corresponding exception,
255     // otherwise abort with generic failure message.
256     jthrowable thrown = (*env)->ExceptionOccurred(env);
257     if (thrown != NULL) {
258         struct ExpandableString summary;
259         ExpandableStringInitialize(&summary);
260         if (GetExceptionSummary(env, thrown, &summary)) {
261             ALOGF("%s", summary.data);
262         }
263         ExpandableStringRelease(&summary);
264         (*env)->DeleteLocalRef(env, thrown);
265     }
266     ALOGF("RegisterNatives failed for '%s'; aborting...", className);
267     return result;
268 }
269 
jniLogException(JNIEnv * env,int priority,const char * tag,jthrowable thrown)270 void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable thrown) {
271     struct ExpandableString summary;
272     ExpandableStringInitialize(&summary);
273     GetStackTraceOrSummary(env, thrown, &summary);
274     const char* details = (summary.data != NULL) ? summary.data : "No memory to report exception";
275     __android_log_write(priority, tag, details);
276     ExpandableStringRelease(&summary);
277 }
278 
jniThrowException(JNIEnv * env,const char * className,const char * message)279 int jniThrowException(JNIEnv* env, const char* className, const char* message) {
280     DiscardPendingException(env, className);
281 
282     jclass exceptionClass = (*env)->FindClass(env, className);
283     if (exceptionClass == NULL) {
284         ALOGE("Unable to find exception class %s", className);
285         /* ClassNotFoundException now pending */
286         return -1;
287     }
288 
289     int status = 0;
290     if ((*env)->ThrowNew(env, exceptionClass, message) != JNI_OK) {
291         ALOGE("Failed throwing '%s' '%s'", className, message);
292         /* an exception, most likely OOM, will now be pending */
293         status = -1;
294     }
295     (*env)->DeleteLocalRef(env, exceptionClass);
296 
297     return status;
298 }
299 
jniThrowExceptionFmt(JNIEnv * env,const char * className,const char * fmt,va_list args)300 int jniThrowExceptionFmt(JNIEnv* env, const char* className, const char* fmt, va_list args) {
301     char msgBuf[512];
302     vsnprintf(msgBuf, sizeof(msgBuf), fmt, args);
303     return jniThrowException(env, className, msgBuf);
304 }
305 
jniThrowNullPointerException(JNIEnv * env,const char * msg)306 int jniThrowNullPointerException(JNIEnv* env, const char* msg) {
307     return jniThrowException(env, "java/lang/NullPointerException", msg);
308 }
309 
jniThrowRuntimeException(JNIEnv * env,const char * msg)310 int jniThrowRuntimeException(JNIEnv* env, const char* msg) {
311     return jniThrowException(env, "java/lang/RuntimeException", msg);
312 }
313 
jniThrowIOException(JNIEnv * env,int errnum)314 int jniThrowIOException(JNIEnv* env, int errnum) {
315     char buffer[80];
316     const char* message = platformStrError(errnum, buffer, sizeof(buffer));
317     return jniThrowException(env, "java/io/IOException", message);
318 }
319 
jniGetReferent(JNIEnv * env,jobject ref)320 jobject jniGetReferent(JNIEnv* env, jobject ref) {
321     jmethodID get = FindMethod(env, "java/lang/ref/Reference", "get", "()Ljava/lang/Object;");
322     return (*env)->CallObjectMethod(env, ref, get);
323 }
324 
jniCreateString(JNIEnv * env,const jchar * unicodeChars,jsize len)325 jstring jniCreateString(JNIEnv* env, const jchar* unicodeChars, jsize len) {
326     return (*env)->NewString(env, unicodeChars, len);
327 }
328 
jniCreateStringArray(C_JNIEnv * env,size_t count)329 jobjectArray jniCreateStringArray(C_JNIEnv* env, size_t count) {
330     jclass stringClass = (*env)->FindClass(env,  "java/lang/String");
331     jobjectArray result = (*env)->NewObjectArray(env, count, stringClass, NULL);
332     (*env)->DeleteLocalRef(env, stringClass);
333     return result;
334 }
335