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(¤t_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, ©);
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