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#if defined(__ANDROID__)
18/* libnativehelper is built by NDK 19 in one variant, which doesn't yet have the GNU strerror_r. */
19#undef _GNU_SOURCE
20/* ...but this code uses asprintf, which is a BSD/GNU extension. */
21#define _BSD_SOURCE
22#endif
23
24#define LOG_TAG "JNIHelp"
25
26#include "JniConstants.h"
27#include "JNIHelp.h"
28#include "ALog-priv.h"
29
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <assert.h>
34
35#include <string>
36
37/**
38 * Equivalent to ScopedLocalRef, but for C_JNIEnv instead. (And slightly more powerful.)
39 */
40template<typename T>
41class scoped_local_ref {
42public:
43    explicit scoped_local_ref(C_JNIEnv* env, T localRef = NULL)
44    : mEnv(env), mLocalRef(localRef)
45    {
46    }
47
48    ~scoped_local_ref() {
49        reset();
50    }
51
52    void reset(T localRef = NULL) {
53        if (mLocalRef != NULL) {
54            (*mEnv)->DeleteLocalRef(reinterpret_cast<JNIEnv*>(mEnv), mLocalRef);
55            mLocalRef = localRef;
56        }
57    }
58
59    T get() const {
60        return mLocalRef;
61    }
62
63private:
64    C_JNIEnv* const mEnv;
65    T mLocalRef;
66
67    DISALLOW_COPY_AND_ASSIGN(scoped_local_ref);
68};
69
70static jclass findClass(C_JNIEnv* env, const char* className) {
71    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
72    return (*env)->FindClass(e, className);
73}
74
75extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
76    const JNINativeMethod* gMethods, int numMethods)
77{
78    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
79
80    ALOGV("Registering %s's %d native methods...", className, numMethods);
81
82    scoped_local_ref<jclass> c(env, findClass(env, className));
83    if (c.get() == NULL) {
84        char* tmp;
85        const char* msg;
86        if (asprintf(&tmp,
87                     "Native registration unable to find class '%s'; aborting...",
88                     className) == -1) {
89            // Allocation failed, print default warning.
90            msg = "Native registration unable to find class; aborting...";
91        } else {
92            msg = tmp;
93        }
94        e->FatalError(msg);
95    }
96
97    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
98        char* tmp;
99        const char* msg;
100        if (asprintf(&tmp, "RegisterNatives failed for '%s'; aborting...", className) == -1) {
101            // Allocation failed, print default warning.
102            msg = "RegisterNatives failed; aborting...";
103        } else {
104            msg = tmp;
105        }
106        e->FatalError(msg);
107    }
108
109    return 0;
110}
111
112/*
113 * Returns a human-readable summary of an exception object.  The buffer will
114 * be populated with the "binary" class name and, if present, the
115 * exception message.
116 */
117static bool getExceptionSummary(C_JNIEnv* env, jthrowable exception, std::string& result) {
118    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
119
120    /* get the name of the exception's class */
121    scoped_local_ref<jclass> exceptionClass(env, (*env)->GetObjectClass(e, exception)); // can't fail
122    scoped_local_ref<jclass> classClass(env,
123            (*env)->GetObjectClass(e, exceptionClass.get())); // java.lang.Class, can't fail
124    jmethodID classGetNameMethod =
125            (*env)->GetMethodID(e, classClass.get(), "getName", "()Ljava/lang/String;");
126    scoped_local_ref<jstring> classNameStr(env,
127            (jstring) (*env)->CallObjectMethod(e, exceptionClass.get(), classGetNameMethod));
128    if (classNameStr.get() == NULL) {
129        (*env)->ExceptionClear(e);
130        result = "<error getting class name>";
131        return false;
132    }
133    const char* classNameChars = (*env)->GetStringUTFChars(e, classNameStr.get(), NULL);
134    if (classNameChars == NULL) {
135        (*env)->ExceptionClear(e);
136        result = "<error getting class name UTF-8>";
137        return false;
138    }
139    result += classNameChars;
140    (*env)->ReleaseStringUTFChars(e, classNameStr.get(), classNameChars);
141
142    /* if the exception has a detail message, get that */
143    jmethodID getMessage =
144            (*env)->GetMethodID(e, exceptionClass.get(), "getMessage", "()Ljava/lang/String;");
145    scoped_local_ref<jstring> messageStr(env,
146            (jstring) (*env)->CallObjectMethod(e, exception, getMessage));
147    if (messageStr.get() == NULL) {
148        return true;
149    }
150
151    result += ": ";
152
153    const char* messageChars = (*env)->GetStringUTFChars(e, messageStr.get(), NULL);
154    if (messageChars != NULL) {
155        result += messageChars;
156        (*env)->ReleaseStringUTFChars(e, messageStr.get(), messageChars);
157    } else {
158        result += "<error getting message>";
159        (*env)->ExceptionClear(e); // clear OOM
160    }
161
162    return true;
163}
164
165/*
166 * Returns an exception (with stack trace) as a string.
167 */
168static bool getStackTrace(C_JNIEnv* env, jthrowable exception, std::string& result) {
169    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
170
171    scoped_local_ref<jclass> stringWriterClass(env, findClass(env, "java/io/StringWriter"));
172    if (stringWriterClass.get() == NULL) {
173        return false;
174    }
175
176    jmethodID stringWriterCtor = (*env)->GetMethodID(e, stringWriterClass.get(), "<init>", "()V");
177    jmethodID stringWriterToStringMethod =
178            (*env)->GetMethodID(e, stringWriterClass.get(), "toString", "()Ljava/lang/String;");
179
180    scoped_local_ref<jclass> printWriterClass(env, findClass(env, "java/io/PrintWriter"));
181    if (printWriterClass.get() == NULL) {
182        return false;
183    }
184
185    jmethodID printWriterCtor =
186            (*env)->GetMethodID(e, printWriterClass.get(), "<init>", "(Ljava/io/Writer;)V");
187
188    scoped_local_ref<jobject> stringWriter(env,
189            (*env)->NewObject(e, stringWriterClass.get(), stringWriterCtor));
190    if (stringWriter.get() == NULL) {
191        return false;
192    }
193
194    scoped_local_ref<jobject> printWriter(env,
195            (*env)->NewObject(e, printWriterClass.get(), printWriterCtor, stringWriter.get()));
196    if (printWriter.get() == NULL) {
197        return false;
198    }
199
200    scoped_local_ref<jclass> exceptionClass(env, (*env)->GetObjectClass(e, exception)); // can't fail
201    jmethodID printStackTraceMethod =
202            (*env)->GetMethodID(e, exceptionClass.get(), "printStackTrace", "(Ljava/io/PrintWriter;)V");
203    (*env)->CallVoidMethod(e, exception, printStackTraceMethod, printWriter.get());
204
205    if ((*env)->ExceptionCheck(e)) {
206        return false;
207    }
208
209    scoped_local_ref<jstring> messageStr(env,
210            (jstring) (*env)->CallObjectMethod(e, stringWriter.get(), stringWriterToStringMethod));
211    if (messageStr.get() == NULL) {
212        return false;
213    }
214
215    const char* utfChars = (*env)->GetStringUTFChars(e, messageStr.get(), NULL);
216    if (utfChars == NULL) {
217        return false;
218    }
219
220    result = utfChars;
221
222    (*env)->ReleaseStringUTFChars(e, messageStr.get(), utfChars);
223    return true;
224}
225
226extern "C" int jniThrowException(C_JNIEnv* env, const char* className, const char* msg) {
227    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
228
229    if ((*env)->ExceptionCheck(e)) {
230        /* TODO: consider creating the new exception with this as "cause" */
231        scoped_local_ref<jthrowable> exception(env, (*env)->ExceptionOccurred(e));
232        (*env)->ExceptionClear(e);
233
234        if (exception.get() != NULL) {
235            std::string text;
236            getExceptionSummary(env, exception.get(), text);
237            ALOGW("Discarding pending exception (%s) to throw %s", text.c_str(), className);
238        }
239    }
240
241    scoped_local_ref<jclass> exceptionClass(env, findClass(env, className));
242    if (exceptionClass.get() == NULL) {
243        ALOGE("Unable to find exception class %s", className);
244        /* ClassNotFoundException now pending */
245        return -1;
246    }
247
248    if ((*env)->ThrowNew(e, exceptionClass.get(), msg) != JNI_OK) {
249        ALOGE("Failed throwing '%s' '%s'", className, msg);
250        /* an exception, most likely OOM, will now be pending */
251        return -1;
252    }
253
254    return 0;
255}
256
257int jniThrowExceptionFmt(C_JNIEnv* env, const char* className, const char* fmt, va_list args) {
258    char msgBuf[512];
259    vsnprintf(msgBuf, sizeof(msgBuf), fmt, args);
260    return jniThrowException(env, className, msgBuf);
261}
262
263int jniThrowNullPointerException(C_JNIEnv* env, const char* msg) {
264    return jniThrowException(env, "java/lang/NullPointerException", msg);
265}
266
267int jniThrowRuntimeException(C_JNIEnv* env, const char* msg) {
268    return jniThrowException(env, "java/lang/RuntimeException", msg);
269}
270
271int jniThrowIOException(C_JNIEnv* env, int errnum) {
272    char buffer[80];
273    const char* message = jniStrError(errnum, buffer, sizeof(buffer));
274    return jniThrowException(env, "java/io/IOException", message);
275}
276
277static std::string jniGetStackTrace(C_JNIEnv* env, jthrowable exception) {
278    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
279
280    scoped_local_ref<jthrowable> currentException(env, (*env)->ExceptionOccurred(e));
281    if (exception == NULL) {
282        exception = currentException.get();
283        if (exception == NULL) {
284          return "<no pending exception>";
285        }
286    }
287
288    if (currentException.get() != NULL) {
289        (*env)->ExceptionClear(e);
290    }
291
292    std::string trace;
293    if (!getStackTrace(env, exception, trace)) {
294        (*env)->ExceptionClear(e);
295        getExceptionSummary(env, exception, trace);
296    }
297
298    if (currentException.get() != NULL) {
299        (*env)->Throw(e, currentException.get()); // rethrow
300    }
301
302    return trace;
303}
304
305void jniLogException(C_JNIEnv* env, int priority, const char* tag, jthrowable exception) {
306    std::string trace(jniGetStackTrace(env, exception));
307    __android_log_write(priority, tag, trace.c_str());
308}
309
310const char* jniStrError(int errnum, char* buf, size_t buflen) {
311#if __GLIBC__
312    // Note: glibc has a nonstandard strerror_r that returns char* rather than POSIX's int.
313    // char *strerror_r(int errnum, char *buf, size_t n);
314    return strerror_r(errnum, buf, buflen);
315#else
316    int rc = strerror_r(errnum, buf, buflen);
317    if (rc != 0) {
318        // (POSIX only guarantees a value other than 0. The safest
319        // way to implement this function is to use C++ and overload on the
320        // type of strerror_r to accurately distinguish GNU from POSIX.)
321        snprintf(buf, buflen, "errno %d", errnum);
322    }
323    return buf;
324#endif
325}
326
327jobject jniCreateFileDescriptor(C_JNIEnv* env, int fd) {
328    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
329    JniConstants::init(e);
330    static jmethodID ctor = e->GetMethodID(JniConstants::fileDescriptorClass, "<init>", "()V");
331    jobject fileDescriptor = (*env)->NewObject(e, JniConstants::fileDescriptorClass, ctor);
332    // NOTE: NewObject ensures that an OutOfMemoryError will be seen by the Java
333    // caller if the alloc fails, so we just return NULL when that happens.
334    if (fileDescriptor != NULL)  {
335        jniSetFileDescriptorOfFD(env, fileDescriptor, fd);
336    }
337    return fileDescriptor;
338}
339
340int jniGetFDFromFileDescriptor(C_JNIEnv* env, jobject fileDescriptor) {
341    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
342    JniConstants::init(e);
343    static jfieldID fid = e->GetFieldID(JniConstants::fileDescriptorClass, "descriptor", "I");
344    if (fileDescriptor != NULL) {
345        return (*env)->GetIntField(e, fileDescriptor, fid);
346    } else {
347        return -1;
348    }
349}
350
351void jniSetFileDescriptorOfFD(C_JNIEnv* env, jobject fileDescriptor, int value) {
352    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
353    JniConstants::init(e);
354    static jfieldID fid = e->GetFieldID(JniConstants::fileDescriptorClass, "descriptor", "I");
355    (*env)->SetIntField(e, fileDescriptor, fid, value);
356}
357
358jobject jniGetReferent(C_JNIEnv* env, jobject ref) {
359    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
360    JniConstants::init(e);
361    static jmethodID get = e->GetMethodID(JniConstants::referenceClass, "get", "()Ljava/lang/Object;");
362    return (*env)->CallObjectMethod(e, ref, get);
363}
364
365