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