1 /*
2  * JNI utility functions
3  *
4  * Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com>
5  *
6  * This file is part of FFmpeg.
7  *
8  * FFmpeg is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * FFmpeg is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with FFmpeg; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 #include <jni.h>
24 #include <pthread.h>
25 #include <stdlib.h>
26 
27 #include "libavutil/bprint.h"
28 #include "libavutil/log.h"
29 #include "libavutil/mem.h"
30 
31 #include "config.h"
32 #include "jni.h"
33 #include "ffjni.h"
34 
35 static JavaVM *java_vm;
36 static pthread_key_t current_env;
37 static pthread_once_t once = PTHREAD_ONCE_INIT;
38 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
39 
jni_detach_env(void *data)40 static void jni_detach_env(void *data)
41 {
42     if (java_vm) {
43         (*java_vm)->DetachCurrentThread(java_vm);
44     }
45 }
46 
jni_create_pthread_key(void)47 static void jni_create_pthread_key(void)
48 {
49     pthread_key_create(&current_env, jni_detach_env);
50 }
51 
ff_jni_get_env(void *log_ctx)52 JNIEnv *ff_jni_get_env(void *log_ctx)
53 {
54     int ret = 0;
55     JNIEnv *env = NULL;
56 
57     pthread_mutex_lock(&lock);
58     if (java_vm == NULL) {
59         java_vm = av_jni_get_java_vm(log_ctx);
60     }
61 
62     if (!java_vm) {
63         av_log(log_ctx, AV_LOG_ERROR, "No Java virtual machine has been registered\n");
64         goto done;
65     }
66 
67     pthread_once(&once, jni_create_pthread_key);
68 
69     if ((env = pthread_getspecific(current_env)) != NULL) {
70         goto done;
71     }
72 
73     ret = (*java_vm)->GetEnv(java_vm, (void **)&env, JNI_VERSION_1_6);
74     switch(ret) {
75     case JNI_EDETACHED:
76         if ((*java_vm)->AttachCurrentThread(java_vm, &env, NULL) != 0) {
77             av_log(log_ctx, AV_LOG_ERROR, "Failed to attach the JNI environment to the current thread\n");
78             env = NULL;
79         } else {
80             pthread_setspecific(current_env, env);
81         }
82         break;
83     case JNI_OK:
84         break;
85     case JNI_EVERSION:
86         av_log(log_ctx, AV_LOG_ERROR, "The specified JNI version is not supported\n");
87         break;
88     default:
89         av_log(log_ctx, AV_LOG_ERROR, "Failed to get the JNI environment attached to this thread\n");
90         break;
91     }
92 
93 done:
94     pthread_mutex_unlock(&lock);
95     return env;
96 }
97 
ff_jni_jstring_to_utf_chars(JNIEnv *env, jstring string, void *log_ctx)98 char *ff_jni_jstring_to_utf_chars(JNIEnv *env, jstring string, void *log_ctx)
99 {
100     char *ret = NULL;
101     const char *utf_chars = NULL;
102 
103     jboolean copy = 0;
104 
105     if (!string) {
106         return NULL;
107     }
108 
109     utf_chars = (*env)->GetStringUTFChars(env, string, &copy);
110     if ((*env)->ExceptionCheck(env)) {
111         (*env)->ExceptionClear(env);
112         av_log(log_ctx, AV_LOG_ERROR, "String.getStringUTFChars() threw an exception\n");
113         return NULL;
114     }
115 
116     ret = av_strdup(utf_chars);
117 
118     (*env)->ReleaseStringUTFChars(env, string, utf_chars);
119     if ((*env)->ExceptionCheck(env)) {
120         (*env)->ExceptionClear(env);
121         av_log(log_ctx, AV_LOG_ERROR, "String.releaseStringUTFChars() threw an exception\n");
122         return NULL;
123     }
124 
125     return ret;
126 }
127 
ff_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, void *log_ctx)128 jstring ff_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, void *log_ctx)
129 {
130     jstring ret;
131 
132     ret = (*env)->NewStringUTF(env, utf_chars);
133     if ((*env)->ExceptionCheck(env)) {
134         (*env)->ExceptionClear(env);
135         av_log(log_ctx, AV_LOG_ERROR, "NewStringUTF() threw an exception\n");
136         return NULL;
137     }
138 
139     return ret;
140 }
141 
ff_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error, void *log_ctx)142 int ff_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error, void *log_ctx)
143 {
144     int ret = 0;
145 
146     AVBPrint bp;
147 
148     char *name = NULL;
149     char *message = NULL;
150 
151     jclass class_class = NULL;
152     jmethodID get_name_id = NULL;
153 
154     jclass exception_class = NULL;
155     jmethodID get_message_id = NULL;
156 
157     jstring string = NULL;
158 
159     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
160 
161     exception_class = (*env)->GetObjectClass(env, exception);
162     if ((*env)->ExceptionCheck(env)) {
163         (*env)->ExceptionClear(env);
164         av_log(log_ctx, AV_LOG_ERROR, "Could not find Throwable class\n");
165         ret = AVERROR_EXTERNAL;
166         goto done;
167     }
168 
169     class_class = (*env)->GetObjectClass(env, exception_class);
170     if ((*env)->ExceptionCheck(env)) {
171         (*env)->ExceptionClear(env);
172         av_log(log_ctx, AV_LOG_ERROR, "Could not find Throwable class's class\n");
173         ret = AVERROR_EXTERNAL;
174         goto done;
175     }
176 
177     get_name_id = (*env)->GetMethodID(env, class_class, "getName", "()Ljava/lang/String;");
178     if ((*env)->ExceptionCheck(env)) {
179         (*env)->ExceptionClear(env);
180         av_log(log_ctx, AV_LOG_ERROR, "Could not find method Class.getName()\n");
181         ret = AVERROR_EXTERNAL;
182         goto done;
183     }
184 
185     string = (*env)->CallObjectMethod(env, exception_class, get_name_id);
186     if ((*env)->ExceptionCheck(env)) {
187         (*env)->ExceptionClear(env);
188         av_log(log_ctx, AV_LOG_ERROR, "Class.getName() threw an exception\n");
189         ret = AVERROR_EXTERNAL;
190         goto done;
191     }
192 
193     if (string) {
194         name = ff_jni_jstring_to_utf_chars(env, string, log_ctx);
195         (*env)->DeleteLocalRef(env, string);
196         string = NULL;
197     }
198 
199     get_message_id = (*env)->GetMethodID(env, exception_class, "getMessage", "()Ljava/lang/String;");
200     if ((*env)->ExceptionCheck(env)) {
201         (*env)->ExceptionClear(env);
202         av_log(log_ctx, AV_LOG_ERROR, "Could not find method java/lang/Throwable.getMessage()\n");
203         ret = AVERROR_EXTERNAL;
204         goto done;
205     }
206 
207     string = (*env)->CallObjectMethod(env, exception, get_message_id);
208     if ((*env)->ExceptionCheck(env)) {
209         (*env)->ExceptionClear(env);
210         av_log(log_ctx, AV_LOG_ERROR, "Throwable.getMessage() threw an exception\n");
211         ret = AVERROR_EXTERNAL;
212         goto done;
213     }
214 
215     if (string) {
216         message = ff_jni_jstring_to_utf_chars(env, string, log_ctx);
217         (*env)->DeleteLocalRef(env, string);
218         string = NULL;
219     }
220 
221     if (name && message) {
222         av_bprintf(&bp, "%s: %s", name, message);
223     } else if (name && !message) {
224         av_bprintf(&bp, "%s occurred", name);
225     } else if (!name && message) {
226         av_bprintf(&bp, "Exception: %s", message);
227     } else {
228         av_log(log_ctx, AV_LOG_WARNING, "Could not retrieve exception name and message\n");
229         av_bprintf(&bp, "Exception occurred");
230     }
231 
232     ret = av_bprint_finalize(&bp, error);
233 done:
234 
235     av_free(name);
236     av_free(message);
237 
238     if (class_class) {
239         (*env)->DeleteLocalRef(env, class_class);
240     }
241 
242     if (exception_class) {
243         (*env)->DeleteLocalRef(env, exception_class);
244     }
245 
246     if (string) {
247         (*env)->DeleteLocalRef(env, string);
248     }
249 
250     return ret;
251 }
252 
ff_jni_exception_check(JNIEnv *env, int log, void *log_ctx)253 int ff_jni_exception_check(JNIEnv *env, int log, void *log_ctx)
254 {
255     int ret;
256 
257     jthrowable exception;
258 
259     char *message = NULL;
260 
261     if (!(*(env))->ExceptionCheck((env))) {
262         return 0;
263     }
264 
265     if (!log) {
266         (*(env))->ExceptionClear((env));
267         return -1;
268     }
269 
270     exception = (*env)->ExceptionOccurred(env);
271     (*(env))->ExceptionClear((env));
272 
273     if ((ret = ff_jni_exception_get_summary(env, exception, &message, log_ctx)) < 0) {
274         (*env)->DeleteLocalRef(env, exception);
275         return ret;
276     }
277 
278     (*env)->DeleteLocalRef(env, exception);
279 
280     av_log(log_ctx, AV_LOG_ERROR, "%s\n", message);
281     av_free(message);
282 
283     return -1;
284 }
285 
ff_jni_init_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)286 int ff_jni_init_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)
287 {
288     int i, ret = 0;
289     jclass last_clazz = NULL;
290 
291     for (i = 0; jfields_mapping[i].name; i++) {
292         int mandatory = jfields_mapping[i].mandatory;
293         enum FFJniFieldType type = jfields_mapping[i].type;
294 
295         if (type == FF_JNI_CLASS) {
296             jclass clazz;
297 
298             last_clazz = NULL;
299 
300             clazz = (*env)->FindClass(env, jfields_mapping[i].name);
301             if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
302                 goto done;
303             }
304 
305             last_clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) =
306                     global ? (*env)->NewGlobalRef(env, clazz) : clazz;
307 
308             if (global) {
309                 (*env)->DeleteLocalRef(env, clazz);
310             }
311 
312         } else {
313 
314             if (!last_clazz) {
315                 ret = AVERROR_EXTERNAL;
316                 break;
317             }
318 
319             switch(type) {
320             case FF_JNI_FIELD: {
321                 jfieldID field_id = (*env)->GetFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
322                 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
323                     goto done;
324                 }
325 
326                 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
327                 break;
328             }
329             case FF_JNI_STATIC_FIELD: {
330                 jfieldID field_id = (*env)->GetStaticFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
331                 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
332                     goto done;
333                 }
334 
335                 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
336                 break;
337             }
338             case FF_JNI_METHOD: {
339                 jmethodID method_id = (*env)->GetMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
340                 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
341                     goto done;
342                 }
343 
344                 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
345                 break;
346             }
347             case FF_JNI_STATIC_METHOD: {
348                 jmethodID method_id = (*env)->GetStaticMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
349                 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
350                     goto done;
351                 }
352 
353                 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
354                 break;
355             }
356             default:
357                 av_log(log_ctx, AV_LOG_ERROR, "Unknown JNI field type\n");
358                 ret = AVERROR(EINVAL);
359                 goto done;
360             }
361 
362             ret = 0;
363         }
364     }
365 
366 done:
367     if (ret < 0) {
368         /* reset jfields in case of failure so it does not leak references */
369         ff_jni_reset_jfields(env, jfields, jfields_mapping, global, log_ctx);
370     }
371 
372     return ret;
373 }
374 
ff_jni_reset_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)375 int ff_jni_reset_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)
376 {
377     int i;
378 
379     for (i = 0; jfields_mapping[i].name; i++) {
380         enum FFJniFieldType type = jfields_mapping[i].type;
381 
382         switch(type) {
383         case FF_JNI_CLASS: {
384             jclass clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset);
385             if (!clazz)
386                 continue;
387 
388             if (global) {
389                 (*env)->DeleteGlobalRef(env, clazz);
390             } else {
391                 (*env)->DeleteLocalRef(env, clazz);
392             }
393 
394             *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
395             break;
396         }
397         case FF_JNI_FIELD: {
398             *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
399             break;
400         }
401         case FF_JNI_STATIC_FIELD: {
402             *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
403             break;
404         }
405         case FF_JNI_METHOD: {
406             *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
407             break;
408         }
409         case FF_JNI_STATIC_METHOD: {
410             *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
411             break;
412         }
413         default:
414             av_log(log_ctx, AV_LOG_ERROR, "Unknown JNI field type\n");
415         }
416     }
417 
418     return 0;
419 }
420