1 | /* |
2 | * Copyright (c) 2003, 2018, 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 | /* |
27 | * Copyright 2003 Wily Technology, Inc. |
28 | */ |
29 | |
30 | #include <jni.h> |
31 | #include <jvmti.h> |
32 | |
33 | #include "JPLISAssert.h" |
34 | #include "Utilities.h" |
35 | #include "JavaExceptions.h" |
36 | |
37 | /** |
38 | * This module contains utility routines for manipulating Java throwables |
39 | * and JNIEnv throwable state from native code. |
40 | */ |
41 | |
42 | static jthrowable sFallbackInternalError = NULL; |
43 | |
44 | /* |
45 | * Local forward declarations. |
46 | */ |
47 | |
48 | /* insist on having a throwable. If we already have one, return it. |
49 | * If not, map to fallback |
50 | */ |
51 | jthrowable |
52 | forceFallback(jthrowable potentialException); |
53 | |
54 | |
55 | jthrowable |
56 | forceFallback(jthrowable potentialException) { |
57 | if ( potentialException == NULL ) { |
58 | return sFallbackInternalError; |
59 | } |
60 | else { |
61 | return potentialException; |
62 | } |
63 | } |
64 | |
65 | /** |
66 | * Returns true if it properly sets up a fallback exception |
67 | */ |
68 | jboolean |
69 | initializeFallbackError(JNIEnv* jnienv) { |
70 | jplis_assert(isSafeForJNICalls(jnienv)); |
71 | sFallbackInternalError = createInternalError(jnienv, NULL); |
72 | jplis_assert(isSafeForJNICalls(jnienv)); |
73 | return (sFallbackInternalError != NULL); |
74 | } |
75 | |
76 | |
77 | /* |
78 | * Map everything to InternalError. |
79 | */ |
80 | jthrowable |
81 | mapAllCheckedToInternalErrorMapper( JNIEnv * jnienv, |
82 | jthrowable throwableToMap) { |
83 | jthrowable mappedThrowable = NULL; |
84 | jstring message = NULL; |
85 | |
86 | jplis_assert(throwableToMap != NULL); |
87 | jplis_assert(isSafeForJNICalls(jnienv)); |
88 | jplis_assert(!isUnchecked(jnienv, throwableToMap)); |
89 | |
90 | message = getMessageFromThrowable(jnienv, throwableToMap); |
91 | mappedThrowable = createInternalError(jnienv, message); |
92 | |
93 | jplis_assert(isSafeForJNICalls(jnienv)); |
94 | return mappedThrowable; |
95 | } |
96 | |
97 | |
98 | jboolean |
99 | checkForThrowable( JNIEnv* jnienv) { |
100 | return (*jnienv)->ExceptionCheck(jnienv); |
101 | } |
102 | |
103 | jboolean |
104 | isSafeForJNICalls( JNIEnv * jnienv) { |
105 | return !(*jnienv)->ExceptionCheck(jnienv); |
106 | } |
107 | |
108 | |
109 | void |
110 | logThrowable( JNIEnv * jnienv) { |
111 | if ( checkForThrowable(jnienv) ) { |
112 | (*jnienv)->ExceptionDescribe(jnienv); |
113 | } |
114 | } |
115 | |
116 | |
117 | |
118 | /** |
119 | * Creates an exception or error with the fully qualified classname (ie java/lang/Error) |
120 | * and message passed to its constructor |
121 | */ |
122 | jthrowable |
123 | createThrowable( JNIEnv * jnienv, |
124 | const char * className, |
125 | jstring message) { |
126 | jthrowable exception = NULL; |
127 | jmethodID constructor = NULL; |
128 | jclass exceptionClass = NULL; |
129 | jboolean errorOutstanding = JNI_FALSE; |
130 | |
131 | jplis_assert(className != NULL); |
132 | jplis_assert(isSafeForJNICalls(jnienv)); |
133 | |
134 | /* create new VMError with message from exception */ |
135 | exceptionClass = (*jnienv)->FindClass(jnienv, className); |
136 | errorOutstanding = checkForAndClearThrowable(jnienv); |
137 | jplis_assert(!errorOutstanding); |
138 | |
139 | if (!errorOutstanding) { |
140 | constructor = (*jnienv)->GetMethodID( jnienv, |
141 | exceptionClass, |
142 | "<init>" , |
143 | "(Ljava/lang/String;)V" ); |
144 | errorOutstanding = checkForAndClearThrowable(jnienv); |
145 | jplis_assert(!errorOutstanding); |
146 | } |
147 | |
148 | if (!errorOutstanding) { |
149 | exception = (*jnienv)->NewObject(jnienv, exceptionClass, constructor, message); |
150 | errorOutstanding = checkForAndClearThrowable(jnienv); |
151 | jplis_assert(!errorOutstanding); |
152 | } |
153 | |
154 | jplis_assert(isSafeForJNICalls(jnienv)); |
155 | return exception; |
156 | } |
157 | |
158 | jthrowable |
159 | createInternalError(JNIEnv * jnienv, jstring message) { |
160 | return createThrowable( jnienv, |
161 | "java/lang/InternalError" , |
162 | message); |
163 | } |
164 | |
165 | jthrowable |
166 | createThrowableFromJVMTIErrorCode(JNIEnv * jnienv, jvmtiError errorCode) { |
167 | const char * throwableClassName = NULL; |
168 | const char * message = NULL; |
169 | jstring messageString = NULL; |
170 | |
171 | switch ( errorCode ) { |
172 | case JVMTI_ERROR_NULL_POINTER: |
173 | throwableClassName = "java/lang/NullPointerException" ; |
174 | break; |
175 | |
176 | case JVMTI_ERROR_ILLEGAL_ARGUMENT: |
177 | throwableClassName = "java/lang/IllegalArgumentException" ; |
178 | break; |
179 | |
180 | case JVMTI_ERROR_OUT_OF_MEMORY: |
181 | throwableClassName = "java/lang/OutOfMemoryError" ; |
182 | break; |
183 | |
184 | case JVMTI_ERROR_CIRCULAR_CLASS_DEFINITION: |
185 | throwableClassName = "java/lang/ClassCircularityError" ; |
186 | break; |
187 | |
188 | case JVMTI_ERROR_FAILS_VERIFICATION: |
189 | throwableClassName = "java/lang/VerifyError" ; |
190 | break; |
191 | |
192 | case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_ADDED: |
193 | throwableClassName = "java/lang/UnsupportedOperationException" ; |
194 | message = "class redefinition failed: attempted to add a method" ; |
195 | break; |
196 | |
197 | case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED: |
198 | throwableClassName = "java/lang/UnsupportedOperationException" ; |
199 | message = "class redefinition failed: attempted to change the schema (add/remove fields)" ; |
200 | break; |
201 | |
202 | case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED: |
203 | throwableClassName = "java/lang/UnsupportedOperationException" ; |
204 | message = "class redefinition failed: attempted to change superclass or interfaces" ; |
205 | break; |
206 | |
207 | case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_DELETED: |
208 | throwableClassName = "java/lang/UnsupportedOperationException" ; |
209 | message = "class redefinition failed: attempted to delete a method" ; |
210 | break; |
211 | |
212 | case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED: |
213 | throwableClassName = "java/lang/UnsupportedOperationException" ; |
214 | message = "class redefinition failed: attempted to change the class modifiers" ; |
215 | break; |
216 | |
217 | case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_CLASS_ATTRIBUTE_CHANGED: |
218 | throwableClassName = "java/lang/UnsupportedOperationException" ; |
219 | message = "class redefinition failed: attempted to change the class NestHost or NestMembers attribute" ; |
220 | break; |
221 | |
222 | case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED: |
223 | throwableClassName = "java/lang/UnsupportedOperationException" ; |
224 | message = "class redefinition failed: attempted to change method modifiers" ; |
225 | break; |
226 | |
227 | case JVMTI_ERROR_UNSUPPORTED_VERSION: |
228 | throwableClassName = "java/lang/UnsupportedClassVersionError" ; |
229 | break; |
230 | |
231 | case JVMTI_ERROR_NAMES_DONT_MATCH: |
232 | throwableClassName = "java/lang/NoClassDefFoundError" ; |
233 | message = "class names don't match" ; |
234 | break; |
235 | |
236 | case JVMTI_ERROR_INVALID_CLASS_FORMAT: |
237 | throwableClassName = "java/lang/ClassFormatError" ; |
238 | break; |
239 | |
240 | case JVMTI_ERROR_UNMODIFIABLE_CLASS: |
241 | throwableClassName = "java/lang/instrument/UnmodifiableClassException" ; |
242 | break; |
243 | |
244 | case JVMTI_ERROR_INVALID_CLASS: |
245 | throwableClassName = "java/lang/InternalError" ; |
246 | message = "class redefinition failed: invalid class" ; |
247 | break; |
248 | |
249 | case JVMTI_ERROR_CLASS_LOADER_UNSUPPORTED: |
250 | throwableClassName = "java/lang/UnsupportedOperationException" ; |
251 | message = "unsupported operation" ; |
252 | break; |
253 | |
254 | case JVMTI_ERROR_INTERNAL: |
255 | default: |
256 | throwableClassName = "java/lang/InternalError" ; |
257 | break; |
258 | } |
259 | |
260 | if ( message != NULL ) { |
261 | jboolean errorOutstanding; |
262 | |
263 | messageString = (*jnienv)->NewStringUTF(jnienv, message); |
264 | errorOutstanding = checkForAndClearThrowable(jnienv); |
265 | jplis_assert_msg(!errorOutstanding, "can't create exception java string" ); |
266 | } |
267 | return createThrowable( jnienv, |
268 | throwableClassName, |
269 | messageString); |
270 | |
271 | } |
272 | |
273 | |
274 | /** |
275 | * Calls toString() on the given message which is the same call made by |
276 | * Exception when passed a throwable to its constructor |
277 | */ |
278 | jstring |
279 | getMessageFromThrowable( JNIEnv* jnienv, |
280 | jthrowable exception) { |
281 | jclass exceptionClass = NULL; |
282 | jmethodID method = NULL; |
283 | jstring message = NULL; |
284 | jboolean errorOutstanding = JNI_FALSE; |
285 | |
286 | jplis_assert(isSafeForJNICalls(jnienv)); |
287 | |
288 | /* call getMessage on exception */ |
289 | exceptionClass = (*jnienv)->GetObjectClass(jnienv, exception); |
290 | errorOutstanding = checkForAndClearThrowable(jnienv); |
291 | jplis_assert(!errorOutstanding); |
292 | |
293 | if (!errorOutstanding) { |
294 | method = (*jnienv)->GetMethodID(jnienv, |
295 | exceptionClass, |
296 | "toString" , |
297 | "()Ljava/lang/String;" ); |
298 | errorOutstanding = checkForAndClearThrowable(jnienv); |
299 | jplis_assert(!errorOutstanding); |
300 | } |
301 | |
302 | if (!errorOutstanding) { |
303 | message = (*jnienv)->CallObjectMethod(jnienv, exception, method); |
304 | errorOutstanding = checkForAndClearThrowable(jnienv); |
305 | jplis_assert(!errorOutstanding); |
306 | } |
307 | |
308 | jplis_assert(isSafeForJNICalls(jnienv)); |
309 | |
310 | return message; |
311 | } |
312 | |
313 | |
314 | /** |
315 | * Returns whether the exception given is an unchecked exception: |
316 | * a subclass of Error or RuntimeException |
317 | */ |
318 | jboolean |
319 | isUnchecked( JNIEnv* jnienv, |
320 | jthrowable exception) { |
321 | jboolean result = JNI_FALSE; |
322 | |
323 | jplis_assert(isSafeForJNICalls(jnienv)); |
324 | result = (exception == NULL) || |
325 | isInstanceofClassName(jnienv, exception, "java/lang/Error" ) || |
326 | isInstanceofClassName(jnienv, exception, "java/lang/RuntimeException" ); |
327 | jplis_assert(isSafeForJNICalls(jnienv)); |
328 | return result; |
329 | } |
330 | |
331 | /* |
332 | * Returns the current throwable, if any. Clears the throwable state. |
333 | * Clients can use this to preserve the current throwable state on the stack. |
334 | */ |
335 | jthrowable |
336 | preserveThrowable(JNIEnv * jnienv) { |
337 | jthrowable result = (*jnienv)->ExceptionOccurred(jnienv); |
338 | if ( result != NULL ) { |
339 | (*jnienv)->ExceptionClear(jnienv); |
340 | } |
341 | return result; |
342 | } |
343 | |
344 | /* |
345 | * Installs the supplied throwable into the JNIEnv if the throwable is not null. |
346 | * Clients can use this to preserve the current throwable state on the stack. |
347 | */ |
348 | void |
349 | restoreThrowable( JNIEnv * jnienv, |
350 | jthrowable preservedException) { |
351 | throwThrowable( jnienv, |
352 | preservedException); |
353 | return; |
354 | } |
355 | |
356 | void |
357 | throwThrowable( JNIEnv * jnienv, |
358 | jthrowable exception) { |
359 | if ( exception != NULL ) { |
360 | jint result = (*jnienv)->Throw(jnienv, exception); |
361 | jplis_assert_msg(result == JNI_OK, "throwThrowable failed to re-throw" ); |
362 | } |
363 | return; |
364 | } |
365 | |
366 | |
367 | /* |
368 | * Always clears the JNIEnv throwable state. Returns true if an exception was present |
369 | * before the clearing operation. |
370 | */ |
371 | jboolean |
372 | checkForAndClearThrowable( JNIEnv * jnienv) { |
373 | jboolean result = (*jnienv)->ExceptionCheck(jnienv); |
374 | if ( result ) { |
375 | (*jnienv)->ExceptionClear(jnienv); |
376 | } |
377 | return result; |
378 | } |
379 | |
380 | /* creates a java.lang.InternalError and installs it into the JNIEnv */ |
381 | void |
382 | createAndThrowInternalError(JNIEnv * jnienv) { |
383 | jthrowable internalError = createInternalError( jnienv, NULL); |
384 | throwThrowable(jnienv, forceFallback(internalError)); |
385 | } |
386 | |
387 | void |
388 | createAndThrowThrowableFromJVMTIErrorCode(JNIEnv * jnienv, jvmtiError errorCode) { |
389 | jthrowable throwable = createThrowableFromJVMTIErrorCode(jnienv, errorCode); |
390 | throwThrowable(jnienv, forceFallback(throwable)); |
391 | } |
392 | |
393 | void |
394 | mapThrownThrowableIfNecessary( JNIEnv * jnienv, |
395 | CheckedExceptionMapper mapper) { |
396 | jthrowable originalThrowable = NULL; |
397 | jthrowable resultThrowable = NULL; |
398 | |
399 | originalThrowable = preserveThrowable(jnienv); |
400 | |
401 | /* the throwable is now cleared, so JNI calls are safe */ |
402 | if ( originalThrowable != NULL ) { |
403 | /* if there is an exception: we can just throw it if it is unchecked. If checked, |
404 | * we need to map it (mapper is conditional, will vary by usage, hence the callback) |
405 | */ |
406 | if ( isUnchecked(jnienv, originalThrowable) ) { |
407 | resultThrowable = originalThrowable; |
408 | } |
409 | else { |
410 | resultThrowable = (*mapper) (jnienv, originalThrowable); |
411 | } |
412 | } |
413 | |
414 | /* re-establish the correct throwable */ |
415 | if ( resultThrowable != NULL ) { |
416 | throwThrowable(jnienv, forceFallback(resultThrowable)); |
417 | } |
418 | |
419 | } |
420 | |