1 /* 2 * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 #ifndef __JNIUTILITIES_H 27 #define __JNIUTILITIES_H 28 29 #include "jni.h" 30 #include "jni_util.h" 31 32 #import <Cocoa/Cocoa.h> 33 34 /******** LOGGING SUPPORT *********/ 35 36 #define LOG_NULL(dst_var, name) \ 37 if (dst_var == NULL) { \ 38 NSLog(@"Bad JNI lookup %s\n", name); \ 39 NSLog(@"%@",[NSThread callStackSymbols]); \ 40 if ([NSThread isMainThread] == NO) { \ 41 if ((*env)->ExceptionOccurred(env) == NULL) { \ 42 JNU_ThrowInternalError(env, "Bad JNI Lookup"); \ 43 } \ 44 } else { \ 45 if ((*env)->ExceptionOccurred(env) != NULL) { \ 46 (*env)->ExceptionDescribe(env); \ 47 } \ 48 } \ 49 [NSException raise:NSGenericException format:@"JNI Lookup Exception"]; \ 50 } 51 52 /******** GET CLASS SUPPORT *********/ 53 54 #define GET_CLASS(dst_var, cls) \ 55 if (dst_var == NULL) { \ 56 dst_var = (*env)->FindClass(env, cls); \ 57 if (dst_var != NULL) dst_var = (*env)->NewGlobalRef(env, dst_var); \ 58 } \ 59 LOG_NULL(dst_var, cls); \ 60 CHECK_NULL(dst_var); 61 62 #define DECLARE_CLASS(dst_var, cls) \ 63 static jclass dst_var = NULL; \ 64 GET_CLASS(dst_var, cls); 65 66 #define GET_CLASS_RETURN(dst_var, cls, ret) \ 67 if (dst_var == NULL) { \ 68 dst_var = (*env)->FindClass(env, cls); \ 69 if (dst_var != NULL) dst_var = (*env)->NewGlobalRef(env, dst_var); \ 70 } \ 71 LOG_NULL(dst_var, cls); \ 72 CHECK_NULL_RETURN(dst_var, ret); 73 74 #define DECLARE_CLASS_RETURN(dst_var, cls, ret) \ 75 static jclass dst_var = NULL; \ 76 GET_CLASS_RETURN(dst_var, cls, ret); 77 78 79 /******** GET METHOD SUPPORT *********/ 80 81 #define GET_METHOD(dst_var, cls, name, signature) \ 82 if (dst_var == NULL) { \ 83 dst_var = (*env)->GetMethodID(env, cls, name, signature); \ 84 } \ 85 LOG_NULL(dst_var, name); \ 86 CHECK_NULL(dst_var); 87 88 #define DECLARE_METHOD(dst_var, cls, name, signature) \ 89 static jmethodID dst_var = NULL; \ 90 GET_METHOD(dst_var, cls, name, signature); 91 92 #define GET_METHOD_RETURN(dst_var, cls, name, signature, ret) \ 93 if (dst_var == NULL) { \ 94 dst_var = (*env)->GetMethodID(env, cls, name, signature); \ 95 } \ 96 LOG_NULL(dst_var, name); \ 97 CHECK_NULL_RETURN(dst_var, ret); 98 99 #define DECLARE_METHOD_RETURN(dst_var, cls, name, signature, ret) \ 100 static jmethodID dst_var = NULL; \ 101 GET_METHOD_RETURN(dst_var, cls, name, signature, ret); 102 103 #define GET_STATIC_METHOD(dst_var, cls, name, signature) \ 104 if (dst_var == NULL) { \ 105 dst_var = (*env)->GetStaticMethodID(env, cls, name, signature); \ 106 } \ 107 LOG_NULL(dst_var, name); \ 108 CHECK_NULL(dst_var); 109 110 #define DECLARE_STATIC_METHOD(dst_var, cls, name, signature) \ 111 static jmethodID dst_var = NULL; \ 112 GET_STATIC_METHOD(dst_var, cls, name, signature); 113 114 #define GET_STATIC_METHOD_RETURN(dst_var, cls, name, signature, ret) \ 115 if (dst_var == NULL) { \ 116 dst_var = (*env)->GetStaticMethodID(env, cls, name, signature); \ 117 } \ 118 LOG_NULL(dst_var, name); \ 119 CHECK_NULL_RETURN(dst_var, ret); 120 121 #define DECLARE_STATIC_METHOD_RETURN(dst_var, cls, name, signature, ret) \ 122 static jmethodID dst_var = NULL; \ 123 GET_STATIC_METHOD_RETURN(dst_var, cls, name, signature, ret); 124 125 /******** GET FIELD SUPPORT *********/ 126 127 128 #define GET_FIELD(dst_var, cls, name, signature) \ 129 if (dst_var == NULL) { \ 130 dst_var = (*env)->GetFieldID(env, cls, name, signature); \ 131 } \ 132 LOG_NULL(dst_var, name); \ 133 CHECK_NULL(dst_var); 134 135 #define DECLARE_FIELD(dst_var, cls, name, signature) \ 136 static jfieldID dst_var = NULL; \ 137 GET_FIELD(dst_var, cls, name, signature); 138 139 #define GET_FIELD_RETURN(dst_var, cls, name, signature, ret) \ 140 if (dst_var == NULL) { \ 141 dst_var = (*env)->GetFieldID(env, cls, name, signature); \ 142 } \ 143 LOG_NULL(dst_var, name); \ 144 CHECK_NULL_RETURN(dst_var, ret); 145 146 #define DECLARE_FIELD_RETURN(dst_var, cls, name, signature, ret) \ 147 static jfieldID dst_var = NULL; \ 148 GET_FIELD_RETURN(dst_var, cls, name, signature, ret); 149 150 #define GET_STATIC_FIELD_RETURN(dst_var, cls, name, signature, ret) \ 151 if (dst_var == NULL) { \ 152 dst_var = (*env)->GetStaticFieldID(env, cls, name, signature); \ 153 } \ 154 LOG_NULL(dst_var, name); \ 155 CHECK_NULL_RETURN(dst_var, ret); 156 157 #define DECLARE_STATIC_FIELD_RETURN(dst_var, cls, name, signature, ret) \ 158 static jfieldID dst_var = NULL; \ 159 GET_STATIC_FIELD_RETURN(dst_var, cls, name, signature, ret); 160 161 /********* EXCEPTION_HANDLING *********/ 162 163 /* 164 * Some explanation to set context of the bigger picture. 165 * Before returning to Java from JNI, NSExceptions are caught - so long as 166 * the body of the native method is wrapped in the ENTER/EXIT macros. 167 * So if we want to directly return to Java from some nested Objective-C 168 * function when detecting a Java exception, we just need to raise an 169 * NSException. Then clear that right before returning to Java, 170 * leaving the Java exception to be seen back in Java-land. 171 * 172 * But if the current thread is the Appkit thread we might as well clear 173 * the Java Exception right now since there's nothing to receive it. 174 * In such a case control will propagate back to the run loop which might 175 * terminate the application. One drawback of that is that the location of 176 * termination does not show where the NSException originated. 177 * And for whatever reason, something swallows that exception. 178 * So as a debugging aid, when on the AppKit thread we can provide a 179 * way (via an env. var.) to log the location. 180 * Additionally provide a similar way to prevent the NSException being 181 * raised and instead just clear the Java Exception. 182 * Together these provide alternate behaviours for more debugging info 183 * or maybe a way for the app to continue running depending on the exact 184 * nature of the problem that has been detected and how survivable it is. 185 */ 186 #define CHECK_EXCEPTION() \ 187 if ((*env)->ExceptionOccurred(env) != NULL) { \ 188 if ([NSThread isMainThread] == YES) { \ 189 if (getenv("JNU_APPKIT_TRACE")) { \ 190 (*env)->ExceptionDescribe(env); \ 191 NSLog(@"%@",[NSThread callStackSymbols]); \ 192 } else { \ 193 (*env)->ExceptionClear(env); \ 194 } \ 195 } \ 196 if (getenv("JNU_NO_COCOA_EXCEPTION") == NULL) { \ 197 [NSException raise:NSGenericException format:@"Java Exception"]; \ 198 } else { \ 199 (*env)->ExceptionClear(env); \ 200 } \ 201 }; 202 203 #define CHECK_EXCEPTION_NULL_RETURN(x, y) \ 204 CHECK_EXCEPTION(); \ 205 if ((x) == NULL) { \ 206 return y; \ 207 }; 208 209 /* Create a pool and initiate a try block to catch any exception */ 210 #define JNI_COCOA_ENTER(env) \ 211 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; \ 212 @try { 213 214 /* Don't allow NSExceptions to escape to Java. 215 * If there is a Java exception that has been thrown that should escape. 216 * And ensure we drain the auto-release pool. 217 */ 218 #define JNI_COCOA_EXIT(env) \ 219 } \ 220 @catch (NSException *e) { \ 221 NSLog(@"%@", [e callStackSymbols]); \ 222 } \ 223 @finally { \ 224 [pool drain]; \ 225 }; 226 227 /* Same as above but adds a clean up action. 228 * Requires that whatever is being cleaned up is in scope. 229 */ 230 #define JNI_COCOA_EXIT_WITH_ACTION(env, action) \ 231 } \ 232 @catch (NSException *e) { \ 233 { action; }; \ 234 NSLog(@"%@", [e callStackSymbols]); \ 235 } \ 236 @finally { \ 237 [pool drain]; \ 238 }; 239 240 /******** STRING CONVERSION SUPPORT *********/ 241 242 JNIEXPORT NSString* JavaStringToNSString(JNIEnv *env, jstring jstr); 243 244 JNIEXPORT jstring NSStringToJavaString(JNIEnv* env, NSString *str); 245 246 JNIEXPORT NSString* NormalizedPathNSStringFromJavaString(JNIEnv *env, jstring pathStr); 247 248 JNIEXPORT jstring NormalizedPathJavaStringFromNSString(JNIEnv* env, NSString *str); 249 250 #endif /* __JNIUTILITIES_H */