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