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
jni_detach_env(void * data)39 static void jni_detach_env(void *data)
40 {
41 if (java_vm) {
42 (*java_vm)->DetachCurrentThread(java_vm);
43 }
44 }
45
jni_create_pthread_key(void)46 static void jni_create_pthread_key(void)
47 {
48 pthread_key_create(¤t_env, jni_detach_env);
49 }
50
ff_jni_get_env(void * log_ctx)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\n");
89 break;
90 }
91
92 done:
93 pthread_mutex_unlock(&lock);
94 return env;
95 }
96
ff_jni_jstring_to_utf_chars(JNIEnv * env,jstring string,void * log_ctx)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, ©);
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
ff_jni_utf_chars_to_jstring(JNIEnv * env,const char * utf_chars,void * log_ctx)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
ff_jni_exception_get_summary(JNIEnv * env,jthrowable exception,char ** error,void * log_ctx)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
ff_jni_exception_check(JNIEnv * env,int log,void * log_ctx)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
ff_jni_init_jfields(JNIEnv * env,void * jfields,const struct FFJniField * jfields_mapping,int global,void * log_ctx)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
307 if (global) {
308 (*env)->DeleteLocalRef(env, clazz);
309 }
310
311 } else {
312
313 if (!last_clazz) {
314 ret = AVERROR_EXTERNAL;
315 break;
316 }
317
318 switch(type) {
319 case FF_JNI_FIELD: {
320 jfieldID field_id = (*env)->GetFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
321 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
322 goto done;
323 }
324
325 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
326 break;
327 }
328 case FF_JNI_STATIC_FIELD: {
329 jfieldID field_id = (*env)->GetStaticFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
330 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
331 goto done;
332 }
333
334 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
335 break;
336 }
337 case FF_JNI_METHOD: {
338 jmethodID method_id = (*env)->GetMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
339 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
340 goto done;
341 }
342
343 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
344 break;
345 }
346 case FF_JNI_STATIC_METHOD: {
347 jmethodID method_id = (*env)->GetStaticMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
348 if ((ret = ff_jni_exception_check(env, mandatory, log_ctx)) < 0 && mandatory) {
349 goto done;
350 }
351
352 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
353 break;
354 }
355 default:
356 av_log(log_ctx, AV_LOG_ERROR, "Unknown JNI field type\n");
357 ret = AVERROR(EINVAL);
358 goto done;
359 }
360
361 ret = 0;
362 }
363 }
364
365 done:
366 if (ret < 0) {
367 /* reset jfields in case of failure so it does not leak references */
368 ff_jni_reset_jfields(env, jfields, jfields_mapping, global, log_ctx);
369 }
370
371 return ret;
372 }
373
ff_jni_reset_jfields(JNIEnv * env,void * jfields,const struct FFJniField * jfields_mapping,int global,void * log_ctx)374 int ff_jni_reset_jfields(JNIEnv *env, void *jfields, const struct FFJniField *jfields_mapping, int global, void *log_ctx)
375 {
376 int i;
377
378 for (i = 0; jfields_mapping[i].name; i++) {
379 enum FFJniFieldType type = jfields_mapping[i].type;
380
381 switch(type) {
382 case FF_JNI_CLASS: {
383 jclass clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset);
384 if (!clazz)
385 continue;
386
387 if (global) {
388 (*env)->DeleteGlobalRef(env, clazz);
389 } else {
390 (*env)->DeleteLocalRef(env, clazz);
391 }
392
393 *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
394 break;
395 }
396 case FF_JNI_FIELD: {
397 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
398 break;
399 }
400 case FF_JNI_STATIC_FIELD: {
401 *(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
402 break;
403 }
404 case FF_JNI_METHOD: {
405 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
406 break;
407 }
408 case FF_JNI_STATIC_METHOD: {
409 *(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
410 break;
411 }
412 default:
413 av_log(log_ctx, AV_LOG_ERROR, "Unknown JNI field type\n");
414 }
415 }
416
417 return 0;
418 }
419