1/*
2 * Copyright (c) 2003, 2006, 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
42static 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 */
51jthrowable
52forceFallback(jthrowable potentialException);
53
54
55jthrowable
56forceFallback(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 */
68jboolean
69initializeFallbackError(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 */
80jthrowable
81mapAllCheckedToInternalErrorMapper( 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
98jboolean
99checkForThrowable(  JNIEnv*     jnienv) {
100    return (*jnienv)->ExceptionCheck(jnienv);
101}
102
103jboolean
104isSafeForJNICalls(  JNIEnv * jnienv) {
105    return !(*jnienv)->ExceptionCheck(jnienv);
106}
107
108
109void
110logThrowable(   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 */
122jthrowable
123createThrowable(    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
158jthrowable
159createInternalError(JNIEnv * jnienv, jstring message) {
160    return createThrowable( jnienv,
161                            "java/lang/InternalError",
162                            message);
163}
164
165jthrowable
166createThrowableFromJVMTIErrorCode(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_METHOD_MODIFIERS_CHANGED:
218                throwableClassName = "java/lang/UnsupportedOperationException";
219                message = "class redefinition failed: attempted to change method modifiers";
220                break;
221
222        case JVMTI_ERROR_UNSUPPORTED_VERSION:
223                throwableClassName = "java/lang/UnsupportedClassVersionError";
224                break;
225
226        case JVMTI_ERROR_NAMES_DONT_MATCH:
227                throwableClassName = "java/lang/NoClassDefFoundError";
228                message = "class names don't match";
229                break;
230
231        case JVMTI_ERROR_INVALID_CLASS_FORMAT:
232                throwableClassName = "java/lang/ClassFormatError";
233                break;
234
235        case JVMTI_ERROR_UNMODIFIABLE_CLASS:
236                throwableClassName = "java/lang/instrument/UnmodifiableClassException";
237                break;
238
239        case JVMTI_ERROR_INVALID_CLASS:
240                throwableClassName = "java/lang/InternalError";
241                message = "class redefinition failed: invalid class";
242                break;
243
244        case JVMTI_ERROR_CLASS_LOADER_UNSUPPORTED:
245                throwableClassName = "java/lang/UnsupportedOperationException";
246                message = "unsupported operation";
247                break;
248
249        case JVMTI_ERROR_INTERNAL:
250        default:
251                throwableClassName = "java/lang/InternalError";
252                break;
253        }
254
255    if ( message != NULL ) {
256        jboolean errorOutstanding;
257
258        messageString = (*jnienv)->NewStringUTF(jnienv, message);
259        errorOutstanding = checkForAndClearThrowable(jnienv);
260        jplis_assert_msg(!errorOutstanding, "can't create exception java string");
261    }
262    return createThrowable( jnienv,
263                            throwableClassName,
264                            messageString);
265
266}
267
268
269/**
270 *  Calls toString() on the given message which is the same call made by
271 *  Exception when passed a throwable to its constructor
272 */
273jstring
274getMessageFromThrowable(    JNIEnv*     jnienv,
275                            jthrowable  exception) {
276    jclass      exceptionClass      = NULL;
277    jmethodID   method              = NULL;
278    jstring     message             = NULL;
279    jboolean    errorOutstanding    = JNI_FALSE;
280
281    jplis_assert(isSafeForJNICalls(jnienv));
282
283    /* call getMessage on exception */
284    exceptionClass = (*jnienv)->GetObjectClass(jnienv, exception);
285    errorOutstanding = checkForAndClearThrowable(jnienv);
286    jplis_assert(!errorOutstanding);
287
288    if (!errorOutstanding) {
289        method = (*jnienv)->GetMethodID(jnienv,
290                                        exceptionClass,
291                                        "toString",
292                                        "()Ljava/lang/String;");
293        errorOutstanding = checkForAndClearThrowable(jnienv);
294        jplis_assert(!errorOutstanding);
295    }
296
297    if (!errorOutstanding) {
298        message = (*jnienv)->CallObjectMethod(jnienv, exception, method);
299        errorOutstanding = checkForAndClearThrowable(jnienv);
300        jplis_assert(!errorOutstanding);
301    }
302
303    jplis_assert(isSafeForJNICalls(jnienv));
304
305    return message;
306}
307
308
309/**
310 *  Returns whether the exception given is an unchecked exception:
311 *  a subclass of Error or RuntimeException
312 */
313jboolean
314isUnchecked(    JNIEnv*     jnienv,
315                jthrowable  exception) {
316    jboolean result = JNI_FALSE;
317
318    jplis_assert(isSafeForJNICalls(jnienv));
319    result =    (exception == NULL) ||
320                isInstanceofClassName(jnienv, exception, "java/lang/Error") ||
321                isInstanceofClassName(jnienv, exception, "java/lang/RuntimeException");
322    jplis_assert(isSafeForJNICalls(jnienv));
323    return result;
324}
325
326/*
327 *  Returns the current throwable, if any. Clears the throwable state.
328 *  Clients can use this to preserve the current throwable state on the stack.
329 */
330jthrowable
331preserveThrowable(JNIEnv * jnienv) {
332    jthrowable result = (*jnienv)->ExceptionOccurred(jnienv);
333    if ( result != NULL ) {
334        (*jnienv)->ExceptionClear(jnienv);
335    }
336    return result;
337}
338
339/*
340 *  Installs the supplied throwable into the JNIEnv if the throwable is not null.
341 *  Clients can use this to preserve the current throwable state on the stack.
342 */
343void
344restoreThrowable(   JNIEnv *    jnienv,
345                    jthrowable  preservedException) {
346    throwThrowable( jnienv,
347                    preservedException);
348    return;
349}
350
351void
352throwThrowable(     JNIEnv *    jnienv,
353                    jthrowable  exception) {
354    if ( exception != NULL ) {
355        jint result = (*jnienv)->Throw(jnienv, exception);
356        jplis_assert_msg(result == JNI_OK, "throwThrowable failed to re-throw");
357    }
358    return;
359}
360
361
362/*
363 *  Always clears the JNIEnv throwable state. Returns true if an exception was present
364 *  before the clearing operation.
365 */
366jboolean
367checkForAndClearThrowable(  JNIEnv *    jnienv) {
368    jboolean result = (*jnienv)->ExceptionCheck(jnienv);
369    if ( result ) {
370        (*jnienv)->ExceptionClear(jnienv);
371    }
372    return result;
373}
374
375/* creates a java.lang.InternalError and installs it into the JNIEnv */
376void
377createAndThrowInternalError(JNIEnv * jnienv) {
378    jthrowable internalError = createInternalError( jnienv, NULL);
379    throwThrowable(jnienv, forceFallback(internalError));
380}
381
382void
383createAndThrowThrowableFromJVMTIErrorCode(JNIEnv * jnienv, jvmtiError errorCode) {
384    jthrowable throwable = createThrowableFromJVMTIErrorCode(jnienv, errorCode);
385    throwThrowable(jnienv, forceFallback(throwable));
386}
387
388void
389mapThrownThrowableIfNecessary(  JNIEnv *                jnienv,
390                                CheckedExceptionMapper  mapper) {
391    jthrowable  originalThrowable   = NULL;
392    jthrowable  resultThrowable     = NULL;
393
394    originalThrowable = preserveThrowable(jnienv);
395
396    /* the throwable is now cleared, so JNI calls are safe */
397    if ( originalThrowable != NULL ) {
398        /* if there is an exception: we can just throw it if it is unchecked. If checked,
399         * we need to map it (mapper is conditional, will vary by usage, hence the callback)
400         */
401        if ( isUnchecked(jnienv, originalThrowable) ) {
402            resultThrowable = originalThrowable;
403        }
404        else {
405            resultThrowable = (*mapper) (jnienv, originalThrowable);
406        }
407    }
408
409    /* re-establish the correct throwable */
410    if ( resultThrowable != NULL ) {
411        throwThrowable(jnienv, forceFallback(resultThrowable));
412    }
413
414}
415