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 35static JavaVM *java_vm; 36static pthread_key_t current_env; 37static pthread_once_t once = PTHREAD_ONCE_INIT; 38static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 39 40static void jni_detach_env(void *data) 41{ 42 if (java_vm) { 43 (*java_vm)->DetachCurrentThread(java_vm); 44 } 45} 46 47static void jni_create_pthread_key(void) 48{ 49 pthread_key_create(¤t_env, jni_detach_env); 50} 51 52JNIEnv *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 93done: 94 pthread_mutex_unlock(&lock); 95 return env; 96} 97 98char *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 128jstring 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 142int 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); 233done: 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 253int 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 286int 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 366done: 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 375int 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