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
forceFallback(jthrowable potentialException)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
initializeFallbackError(JNIEnv * jnienv)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
mapAllCheckedToInternalErrorMapper(JNIEnv * jnienv,jthrowable throwableToMap)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
checkForThrowable(JNIEnv * jnienv)99 checkForThrowable(  JNIEnv*     jnienv) {
100     return (*jnienv)->ExceptionCheck(jnienv);
101 }
102 
103 jboolean
isSafeForJNICalls(JNIEnv * jnienv)104 isSafeForJNICalls(  JNIEnv * jnienv) {
105     return !(*jnienv)->ExceptionCheck(jnienv);
106 }
107 
108 
109 void
logThrowable(JNIEnv * jnienv)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
createThrowable(JNIEnv * jnienv,const char * className,jstring message)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
createInternalError(JNIEnv * jnienv,jstring message)159 createInternalError(JNIEnv * jnienv, jstring message) {
160     return createThrowable( jnienv,
161                             "java/lang/InternalError",
162                             message);
163 }
164 
165 jthrowable
createThrowableFromJVMTIErrorCode(JNIEnv * jnienv,jvmtiError errorCode)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
getMessageFromThrowable(JNIEnv * jnienv,jthrowable exception)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
isUnchecked(JNIEnv * jnienv,jthrowable exception)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
preserveThrowable(JNIEnv * jnienv)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
restoreThrowable(JNIEnv * jnienv,jthrowable preservedException)349 restoreThrowable(   JNIEnv *    jnienv,
350                     jthrowable  preservedException) {
351     throwThrowable( jnienv,
352                     preservedException);
353     return;
354 }
355 
356 void
throwThrowable(JNIEnv * jnienv,jthrowable exception)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
checkForAndClearThrowable(JNIEnv * jnienv)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
createAndThrowInternalError(JNIEnv * jnienv)382 createAndThrowInternalError(JNIEnv * jnienv) {
383     jthrowable internalError = createInternalError( jnienv, NULL);
384     throwThrowable(jnienv, forceFallback(internalError));
385 }
386 
387 void
createAndThrowThrowableFromJVMTIErrorCode(JNIEnv * jnienv,jvmtiError errorCode)388 createAndThrowThrowableFromJVMTIErrorCode(JNIEnv * jnienv, jvmtiError errorCode) {
389     jthrowable throwable = createThrowableFromJVMTIErrorCode(jnienv, errorCode);
390     throwThrowable(jnienv, forceFallback(throwable));
391 }
392 
393 void
mapThrownThrowableIfNecessary(JNIEnv * jnienv,CheckedExceptionMapper mapper)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