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