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/*
18 * JNI helper functions.
19 */
20#define LOG_TAG "JNIHelp"
21#include "JNIHelp.h"
22#include "utils/Log.h"
23
24#include <string.h>
25#include <assert.h>
26
27/*
28 * Register native JNI-callable methods.
29 *
30 * "className" looks like "java/lang/String".
31 */
32int jniRegisterNativeMethods(JNIEnv* env, const char* className,
33    const JNINativeMethod* gMethods, int numMethods)
34{
35    jclass clazz;
36
37    LOGV("Registering %s natives\n", className);
38    clazz = (*env)->FindClass(env, className);
39    if (clazz == NULL) {
40        LOGE("Native registration unable to find class '%s'\n", className);
41        return -1;
42    }
43
44    int result = 0;
45    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
46        LOGE("RegisterNatives failed for '%s'\n", className);
47        result = -1;
48    }
49
50    (*env)->DeleteLocalRef(env, clazz);
51    return result;
52}
53
54/*
55 * Get a human-readable summary of an exception object.  The buffer will
56 * be populated with the "binary" class name and, if present, the
57 * exception message.
58 */
59static void getExceptionSummary(JNIEnv* env, jthrowable exception, char* buf, size_t bufLen)
60{
61    int success = 0;
62
63    /* get the name of the exception's class */
64    jclass exceptionClazz = (*env)->GetObjectClass(env, exception); // can't fail
65    jclass classClazz = (*env)->GetObjectClass(env, exceptionClazz); // java.lang.Class, can't fail
66    jmethodID classGetNameMethod = (*env)->GetMethodID(
67            env, classClazz, "getName", "()Ljava/lang/String;");
68    jstring classNameStr = (*env)->CallObjectMethod(env, exceptionClazz, classGetNameMethod);
69    if (classNameStr != NULL) {
70        /* get printable string */
71        const char* classNameChars = (*env)->GetStringUTFChars(env, classNameStr, NULL);
72        if (classNameChars != NULL) {
73            /* if the exception has a message string, get that */
74            jmethodID throwableGetMessageMethod = (*env)->GetMethodID(
75                    env, exceptionClazz, "getMessage", "()Ljava/lang/String;");
76            jstring messageStr = (*env)->CallObjectMethod(
77                    env, exception, throwableGetMessageMethod);
78
79            if (messageStr != NULL) {
80                const char* messageChars = (*env)->GetStringUTFChars(env, messageStr, NULL);
81                if (messageChars != NULL) {
82                    snprintf(buf, bufLen, "%s: %s", classNameChars, messageChars);
83                    (*env)->ReleaseStringUTFChars(env, messageStr, messageChars);
84                } else {
85                    (*env)->ExceptionClear(env); // clear OOM
86                    snprintf(buf, bufLen, "%s: <error getting message>", classNameChars);
87                }
88                (*env)->DeleteLocalRef(env, messageStr);
89            } else {
90                strncpy(buf, classNameChars, bufLen);
91                buf[bufLen - 1] = '\0';
92            }
93
94            (*env)->ReleaseStringUTFChars(env, classNameStr, classNameChars);
95            success = 1;
96        }
97        (*env)->DeleteLocalRef(env, classNameStr);
98    }
99    (*env)->DeleteLocalRef(env, classClazz);
100    (*env)->DeleteLocalRef(env, exceptionClazz);
101
102    if (! success) {
103        (*env)->ExceptionClear(env);
104        snprintf(buf, bufLen, "%s", "<error getting class name>");
105    }
106}
107
108/*
109 * Formats an exception as a string with its stack trace.
110 */
111static void printStackTrace(JNIEnv* env, jthrowable exception, char* buf, size_t bufLen)
112{
113    int success = 0;
114
115    jclass stringWriterClazz = (*env)->FindClass(env, "java/io/StringWriter");
116    if (stringWriterClazz != NULL) {
117        jmethodID stringWriterCtor = (*env)->GetMethodID(env, stringWriterClazz,
118                "<init>", "()V");
119        jmethodID stringWriterToStringMethod = (*env)->GetMethodID(env, stringWriterClazz,
120                "toString", "()Ljava/lang/String;");
121
122        jclass printWriterClazz = (*env)->FindClass(env, "java/io/PrintWriter");
123        if (printWriterClazz != NULL) {
124            jmethodID printWriterCtor = (*env)->GetMethodID(env, printWriterClazz,
125                    "<init>", "(Ljava/io/Writer;)V");
126
127            jobject stringWriterObj = (*env)->NewObject(env, stringWriterClazz, stringWriterCtor);
128            if (stringWriterObj != NULL) {
129                jobject printWriterObj = (*env)->NewObject(env, printWriterClazz, printWriterCtor,
130                        stringWriterObj);
131                if (printWriterObj != NULL) {
132                    jclass exceptionClazz = (*env)->GetObjectClass(env, exception); // can't fail
133                    jmethodID printStackTraceMethod = (*env)->GetMethodID(
134                            env, exceptionClazz, "printStackTrace", "(Ljava/io/PrintWriter;)V");
135
136                    (*env)->CallVoidMethod(
137                            env, exception, printStackTraceMethod, printWriterObj);
138                    if (! (*env)->ExceptionCheck(env)) {
139                        jstring messageStr = (*env)->CallObjectMethod(
140                                env, stringWriterObj, stringWriterToStringMethod);
141                        if (messageStr != NULL) {
142                            jsize messageStrLength = (*env)->GetStringLength(env, messageStr);
143                            if (messageStrLength >= (jsize) bufLen) {
144                                messageStrLength = bufLen - 1;
145                            }
146                            (*env)->GetStringUTFRegion(env, messageStr, 0, messageStrLength, buf);
147                            (*env)->DeleteLocalRef(env, messageStr);
148                            buf[messageStrLength] = '\0';
149                            success = 1;
150                        }
151                    }
152                    (*env)->DeleteLocalRef(env, exceptionClazz);
153                    (*env)->DeleteLocalRef(env, printWriterObj);
154                }
155                (*env)->DeleteLocalRef(env, stringWriterObj);
156            }
157            (*env)->DeleteLocalRef(env, printWriterClazz);
158        }
159        (*env)->DeleteLocalRef(env, stringWriterClazz);
160    }
161
162    if (! success) {
163        (*env)->ExceptionClear(env);
164        getExceptionSummary(env, exception, buf, bufLen);
165    }
166}
167
168/*
169 * Throw an exception with the specified class and an optional message.
170 *
171 * If an exception is currently pending, we log a warning message and
172 * clear it.
173 *
174 * Returns 0 if the specified exception was successfully thrown.  (Some
175 * sort of exception will always be pending when this returns.)
176 */
177int jniThrowException(JNIEnv* env, const char* className, const char* msg)
178{
179    jclass exceptionClass;
180
181    if ((*env)->ExceptionCheck(env)) {
182        /* TODO: consider creating the new exception with this as "cause" */
183        char buf[256];
184
185        jthrowable exception = (*env)->ExceptionOccurred(env);
186        (*env)->ExceptionClear(env);
187
188        if (exception != NULL) {
189            getExceptionSummary(env, exception, buf, sizeof(buf));
190            LOGW("Discarding pending exception (%s) to throw %s\n", buf, className);
191            (*env)->DeleteLocalRef(env, exception);
192        }
193    }
194
195    exceptionClass = (*env)->FindClass(env, className);
196    if (exceptionClass == NULL) {
197        LOGE("Unable to find exception class %s\n", className);
198        /* ClassNotFoundException now pending */
199        return -1;
200    }
201
202    int result = 0;
203    if ((*env)->ThrowNew(env, exceptionClass, msg) != JNI_OK) {
204        LOGE("Failed throwing '%s' '%s'\n", className, msg);
205        /* an exception, most likely OOM, will now be pending */
206        result = -1;
207    }
208
209    (*env)->DeleteLocalRef(env, exceptionClass);
210    return result;
211}
212
213/*
214 * Throw a java.lang.NullPointerException, with an optional message.
215 */
216int jniThrowNullPointerException(JNIEnv* env, const char* msg)
217{
218    return jniThrowException(env, "java/lang/NullPointerException", msg);
219}
220
221/*
222 * Throw a java.lang.RuntimeException, with an optional message.
223 */
224int jniThrowRuntimeException(JNIEnv* env, const char* msg)
225{
226    return jniThrowException(env, "java/lang/RuntimeException", msg);
227}
228
229/*
230 * Throw a java.io.IOException, generating the message from errno.
231 */
232int jniThrowIOException(JNIEnv* env, int errnum)
233{
234    char buffer[80];
235    const char* message = jniStrError(errnum, buffer, sizeof(buffer));
236    return jniThrowException(env, "java/io/IOException", message);
237}
238
239/*
240 * Log an exception.
241 * If exception is NULL, logs the current exception in the JNI environment, if any.
242 */
243void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable exception)
244{
245    int currentException = 0;
246    if (exception == NULL) {
247        exception = (*env)->ExceptionOccurred(env);
248        if (exception == NULL) {
249            return;
250        }
251
252        (*env)->ExceptionClear(env);
253        currentException = 1;
254    }
255
256    char buffer[1024];
257    printStackTrace(env, exception, buffer, sizeof(buffer));
258    __android_log_write(priority, tag, buffer);
259
260    if (currentException) {
261        (*env)->Throw(env, exception); // rethrow
262        (*env)->DeleteLocalRef(env, exception);
263    }
264}
265
266const char* jniStrError(int errnum, char* buf, size_t buflen)
267{
268    // note: glibc has a nonstandard strerror_r that returns char* rather
269    // than POSIX's int.
270    // char *strerror_r(int errnum, char *buf, size_t n);
271    char* ret = (char*) strerror_r(errnum, buf, buflen);
272    if (((int)ret) == 0) {
273        //POSIX strerror_r, success
274        return buf;
275    } else if (((int)ret) == -1) {
276        //POSIX strerror_r, failure
277        // (Strictly, POSIX only guarantees a value other than 0. The safest
278        // way to implement this function is to use C++ and overload on the
279        // type of strerror_r to accurately distinguish GNU from POSIX. But
280        // realistic implementations will always return -1.)
281        snprintf(buf, buflen, "errno %d", errnum);
282        return buf;
283    } else {
284        //glibc strerror_r returning a string
285        return ret;
286    }
287}
288
289static struct CachedFields {
290    jclass fileDescriptorClass;
291    jmethodID fileDescriptorCtor;
292    jfieldID descriptorField;
293} gCachedFields;
294
295int registerJniHelp(JNIEnv* env) {
296    gCachedFields.fileDescriptorClass =
297            (*env)->NewGlobalRef(env, (*env)->FindClass(env, "java/io/FileDescriptor"));
298    if (gCachedFields.fileDescriptorClass == NULL) {
299        return -1;
300    }
301
302    gCachedFields.fileDescriptorCtor =
303            (*env)->GetMethodID(env, gCachedFields.fileDescriptorClass, "<init>", "()V");
304    if (gCachedFields.fileDescriptorCtor == NULL) {
305        return -1;
306    }
307
308    gCachedFields.descriptorField =
309            (*env)->GetFieldID(env, gCachedFields.fileDescriptorClass, "descriptor", "I");
310    if (gCachedFields.descriptorField == NULL) {
311        return -1;
312    }
313
314    return 0;
315}
316
317/*
318 * Create a java.io.FileDescriptor given an integer fd
319 */
320jobject jniCreateFileDescriptor(JNIEnv* env, int fd) {
321    jobject fileDescriptor = (*env)->NewObject(env,
322            gCachedFields.fileDescriptorClass, gCachedFields.fileDescriptorCtor);
323    jniSetFileDescriptorOfFD(env, fileDescriptor, fd);
324    return fileDescriptor;
325}
326
327/*
328 * Get an int file descriptor from a java.io.FileDescriptor
329 */
330int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) {
331    return (*env)->GetIntField(env, fileDescriptor, gCachedFields.descriptorField);
332}
333
334/*
335 * Set the descriptor of a java.io.FileDescriptor
336 */
337void jniSetFileDescriptorOfFD(JNIEnv* env, jobject fileDescriptor, int value) {
338    (*env)->SetIntField(env, fileDescriptor, gCachedFields.descriptorField, value);
339}
340