JNIHelp.cpp revision 87f62a8c3826070d807817238a1ee8afd11ec34c
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#define LOG_TAG "JNIHelp"
18
19#include "JNIHelp.h"
20#include "utils/Log.h"
21
22#include <stdlib.h>
23#include <string.h>
24#include <assert.h>
25
26/**
27 * Equivalent to ScopedLocalRef, but for C_JNIEnv instead. (And slightly more powerful.)
28 */
29template<typename T>
30class scoped_local_ref {
31public:
32    scoped_local_ref(C_JNIEnv* env, T localRef = NULL)
33    : mEnv(env), mLocalRef(localRef)
34    {
35    }
36
37    ~scoped_local_ref() {
38        reset();
39    }
40
41    void reset(T localRef = NULL) {
42        if (mLocalRef != NULL) {
43            (*mEnv)->DeleteLocalRef(reinterpret_cast<JNIEnv*>(mEnv), mLocalRef);
44            mLocalRef = localRef;
45        }
46    }
47
48    T get() const {
49        return mLocalRef;
50    }
51
52private:
53    C_JNIEnv* mEnv;
54    T mLocalRef;
55
56    // Disallow copy and assignment.
57    scoped_local_ref(const scoped_local_ref&);
58    void operator=(const scoped_local_ref&);
59};
60
61static jclass findClass(C_JNIEnv* env, const char* className) {
62    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
63    return (*env)->FindClass(e, className);
64}
65
66extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
67    const JNINativeMethod* gMethods, int numMethods)
68{
69    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
70
71    LOGV("Registering %s natives", className);
72
73    scoped_local_ref<jclass> c(env, findClass(env, className));
74    if (c.get() == NULL) {
75        LOGE("Native registration unable to find class '%s', aborting", className);
76        abort();
77    }
78
79    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
80        LOGE("RegisterNatives failed for '%s', aborting", className);
81        abort();
82    }
83
84    return 0;
85}
86
87/*
88 * Returns a human-readable summary of an exception object.  The buffer will
89 * be populated with the "binary" class name and, if present, the
90 * exception message.
91 */
92static char* getExceptionSummary0(C_JNIEnv* env, jthrowable exception) {
93    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
94
95    /* get the name of the exception's class */
96    scoped_local_ref<jclass> exceptionClass(env, (*env)->GetObjectClass(e, exception)); // can't fail
97    scoped_local_ref<jclass> classClass(env,
98            (*env)->GetObjectClass(e, exceptionClass.get())); // java.lang.Class, can't fail
99    jmethodID classGetNameMethod =
100            (*env)->GetMethodID(e, classClass.get(), "getName", "()Ljava/lang/String;");
101    scoped_local_ref<jstring> classNameStr(env,
102            (jstring) (*env)->CallObjectMethod(e, exceptionClass.get(), classGetNameMethod));
103    if (classNameStr.get() == NULL) {
104        return NULL;
105    }
106
107    /* get printable string */
108    const char* classNameChars = (*env)->GetStringUTFChars(e, classNameStr.get(), NULL);
109    if (classNameChars == NULL) {
110        return NULL;
111    }
112
113    /* if the exception has a detail message, get that */
114    jmethodID getMessage =
115            (*env)->GetMethodID(e, exceptionClass.get(), "getMessage", "()Ljava/lang/String;");
116    scoped_local_ref<jstring> messageStr(env,
117            (jstring) (*env)->CallObjectMethod(e, exception, getMessage));
118    if (messageStr.get() == NULL) {
119        return strdup(classNameChars);
120    }
121
122    char* result = NULL;
123    const char* messageChars = (*env)->GetStringUTFChars(e, messageStr.get(), NULL);
124    if (messageChars != NULL) {
125        asprintf(&result, "%s: %s", classNameChars, messageChars);
126        (*env)->ReleaseStringUTFChars(e, messageStr.get(), messageChars);
127    } else {
128        (*env)->ExceptionClear(e); // clear OOM
129        asprintf(&result, "%s: <error getting message>", classNameChars);
130    }
131
132    (*env)->ReleaseStringUTFChars(e, classNameStr.get(), classNameChars);
133    return result;
134}
135
136static char* getExceptionSummary(C_JNIEnv* env, jthrowable exception) {
137    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
138    char* result = getExceptionSummary0(env, exception);
139    if (result == NULL) {
140        (*env)->ExceptionClear(e);
141        result = strdup("<error getting class name>");
142    }
143    return result;
144}
145
146/*
147 * Returns an exception (with stack trace) as a string.
148 */
149static char* getStackTrace(C_JNIEnv* env, jthrowable exception) {
150    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
151
152    scoped_local_ref<jclass> stringWriterClass(env, findClass(env, "java/io/StringWriter"));
153    if (stringWriterClass.get() == NULL) {
154        return NULL;
155    }
156
157    jmethodID stringWriterCtor = (*env)->GetMethodID(e, stringWriterClass.get(), "<init>", "()V");
158    jmethodID stringWriterToStringMethod =
159            (*env)->GetMethodID(e, stringWriterClass.get(), "toString", "()Ljava/lang/String;");
160
161    scoped_local_ref<jclass> printWriterClass(env, findClass(env, "java/io/PrintWriter"));
162    if (printWriterClass.get() == NULL) {
163        return NULL;
164    }
165
166    jmethodID printWriterCtor =
167            (*env)->GetMethodID(e, printWriterClass.get(), "<init>", "(Ljava/io/Writer;)V");
168
169    scoped_local_ref<jobject> stringWriter(env,
170            (*env)->NewObject(e, stringWriterClass.get(), stringWriterCtor));
171    if (stringWriter.get() == NULL) {
172        return NULL;
173    }
174
175    jobject printWriter =
176            (*env)->NewObject(e, printWriterClass.get(), printWriterCtor, stringWriter.get());
177    if (printWriter == NULL) {
178        return NULL;
179    }
180
181    scoped_local_ref<jclass> exceptionClass(env, (*env)->GetObjectClass(e, exception)); // can't fail
182    jmethodID printStackTraceMethod =
183            (*env)->GetMethodID(e, exceptionClass.get(), "printStackTrace", "(Ljava/io/PrintWriter;)V");
184    (*env)->CallVoidMethod(e, exception, printStackTraceMethod, printWriter);
185
186    if ((*env)->ExceptionCheck(e)) {
187        return NULL;
188    }
189
190    scoped_local_ref<jstring> messageStr(env,
191            (jstring) (*env)->CallObjectMethod(e, stringWriter.get(), stringWriterToStringMethod));
192    if (messageStr.get() == NULL) {
193        return NULL;
194    }
195
196    const char* utfChars = (*env)->GetStringUTFChars(e, messageStr.get(), NULL);
197    if (utfChars == NULL) {
198        return NULL;
199    }
200
201    char* result = strdup(utfChars);
202    (*env)->ReleaseStringUTFChars(e, messageStr.get(), utfChars);
203    return result;
204}
205
206extern "C" int jniThrowException(C_JNIEnv* env, const char* className, const char* msg) {
207    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
208
209    if ((*env)->ExceptionCheck(e)) {
210        /* TODO: consider creating the new exception with this as "cause" */
211        scoped_local_ref<jthrowable> exception(env, (*env)->ExceptionOccurred(e));
212        (*env)->ExceptionClear(e);
213
214        if (exception.get() != NULL) {
215            char* text = getExceptionSummary(env, exception.get());
216            LOGW("Discarding pending exception (%s) to throw %s", text, className);
217            free(text);
218        }
219    }
220
221    scoped_local_ref<jclass> exceptionClass(env, findClass(env, className));
222    if (exceptionClass.get() == NULL) {
223        LOGE("Unable to find exception class %s", className);
224        /* ClassNotFoundException now pending */
225        return -1;
226    }
227
228    if ((*env)->ThrowNew(e, exceptionClass.get(), msg) != JNI_OK) {
229        LOGE("Failed throwing '%s' '%s'", className, msg);
230        /* an exception, most likely OOM, will now be pending */
231        return -1;
232    }
233
234    return 0;
235}
236
237int jniThrowExceptionFmt(C_JNIEnv* env, const char* className, const char* fmt, va_list args) {
238    char msgBuf[512];
239    vsnprintf(msgBuf, sizeof(msgBuf), fmt, args);
240    return jniThrowException(env, className, msgBuf);
241}
242
243int jniThrowNullPointerException(C_JNIEnv* env, const char* msg) {
244    return jniThrowException(env, "java/lang/NullPointerException", msg);
245}
246
247int jniThrowRuntimeException(C_JNIEnv* env, const char* msg) {
248    return jniThrowException(env, "java/lang/RuntimeException", msg);
249}
250
251int jniThrowIOException(C_JNIEnv* env, int errnum) {
252    char buffer[80];
253    const char* message = jniStrError(errnum, buffer, sizeof(buffer));
254    return jniThrowException(env, "java/io/IOException", message);
255}
256
257void jniLogException(C_JNIEnv* env, int priority, const char* tag, jthrowable exception) {
258    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
259
260    scoped_local_ref<jthrowable> currentException(env);
261    if (exception == NULL) {
262        exception = (*env)->ExceptionOccurred(e);
263        if (exception == NULL) {
264            return;
265        }
266
267        (*env)->ExceptionClear(e);
268        currentException.reset(exception);
269    }
270
271    char* buffer = getStackTrace(env, exception);
272    if (buffer == NULL) {
273        (*env)->ExceptionClear(e);
274        buffer = getExceptionSummary(env, exception);
275    }
276
277    __android_log_write(priority, tag, buffer);
278    free(buffer);
279
280    if (currentException.get() != NULL) {
281        (*env)->Throw(e, exception); // rethrow
282    }
283}
284
285const char* jniStrError(int errnum, char* buf, size_t buflen) {
286    // Note: glibc has a nonstandard strerror_r that returns char* rather than POSIX's int.
287    // char *strerror_r(int errnum, char *buf, size_t n);
288    char* ret = (char*) strerror_r(errnum, buf, buflen);
289    if (((int)ret) == 0) {
290        // POSIX strerror_r, success
291        return buf;
292    } else if (((int)ret) == -1) {
293        // POSIX strerror_r, failure
294        // (Strictly, POSIX only guarantees a value other than 0. The safest
295        // way to implement this function is to use C++ and overload on the
296        // type of strerror_r to accurately distinguish GNU from POSIX. But
297        // realistic implementations will always return -1.)
298        snprintf(buf, buflen, "errno %d", errnum);
299        return buf;
300    } else {
301        // glibc strerror_r returning a string
302        return ret;
303    }
304}
305
306static struct CachedFields {
307    jclass fileDescriptorClass;
308    jmethodID fileDescriptorCtor;
309    jfieldID descriptorField;
310} gCachedFields;
311
312int registerJniHelp(JNIEnv* env) {
313    gCachedFields.fileDescriptorClass =
314            reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass("java/io/FileDescriptor")));
315    if (gCachedFields.fileDescriptorClass == NULL) {
316        return -1;
317    }
318
319    gCachedFields.fileDescriptorCtor =
320            env->GetMethodID(gCachedFields.fileDescriptorClass, "<init>", "()V");
321    if (gCachedFields.fileDescriptorCtor == NULL) {
322        return -1;
323    }
324
325    gCachedFields.descriptorField =
326            env->GetFieldID(gCachedFields.fileDescriptorClass, "descriptor", "I");
327    if (gCachedFields.descriptorField == NULL) {
328        return -1;
329    }
330
331    return 0;
332}
333
334jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd) {
335    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
336    jobject fileDescriptor = (*env)->NewObject(e,
337            gCachedFields.fileDescriptorClass, gCachedFields.fileDescriptorCtor);
338    jniSetFileDescriptorOfFD(env, fileDescriptor, fd);
339    return fileDescriptor;
340}
341
342int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor) {
343    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
344    return (*env)->GetIntField(e, fileDescriptor, gCachedFields.descriptorField);
345}
346
347void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value) {
348    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
349    (*env)->SetIntField(e, fileDescriptor, gCachedFields.descriptorField, value);
350}
351
352/*
353 * DO NOT USE THIS FUNCTION
354 *
355 * Get a pointer to the elements of a non-movable array.
356 *
357 * The semantics are similar to GetDirectBufferAddress.  Specifically, the VM
358 * guarantees that the array will not move, and the caller must ensure that
359 * it does not continue to use the pointer after the object is collected.
360 *
361 * We currently use an illegal sequence that trips up CheckJNI when
362 * the "forcecopy" mode is enabled.  We pass in a magic value to work
363 * around the problem.
364 *
365 * Returns NULL if the array is movable.
366 */
367#define kNoCopyMagic 0xd5aab57f     /* also in CheckJni.c */
368extern "C" jbyte* jniGetNonMovableArrayElements(C_JNIEnv* env, jarray arrayObj) {
369    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
370
371    jbyteArray byteArray = reinterpret_cast<jbyteArray>(arrayObj);
372
373    /*
374     * Normally the "isCopy" parameter is for a return value only, so the
375     * non-CheckJNI VM will ignore whatever we pass in.
376     */
377    uint32_t noCopy = kNoCopyMagic;
378    jbyte* result = (*env)->GetByteArrayElements(e, byteArray, reinterpret_cast<jboolean*>(&noCopy));
379
380    /*
381     * The non-CheckJNI implementation only cares about the array object,
382     * so we can replace the element pointer with the magic value.
383     */
384    (*env)->ReleaseByteArrayElements(e, byteArray, reinterpret_cast<jbyte*>(kNoCopyMagic), 0);
385    return result;
386}
387