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