/* * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * Copyright 2003 Wily Technology, Inc. */ #include #include #include "JPLISAssert.h" #include "Utilities.h" #include "JavaExceptions.h" /** * This module contains utility routines for manipulating Java throwables * and JNIEnv throwable state from native code. */ static jthrowable sFallbackInternalError = NULL; /* * Local forward declarations. */ /* insist on having a throwable. If we already have one, return it. * If not, map to fallback */ jthrowable forceFallback(jthrowable potentialException); jthrowable forceFallback(jthrowable potentialException) { if ( potentialException == NULL ) { return sFallbackInternalError; } else { return potentialException; } } /** * Returns true if it properly sets up a fallback exception */ jboolean initializeFallbackError(JNIEnv* jnienv) { jplis_assert(isSafeForJNICalls(jnienv)); sFallbackInternalError = createInternalError(jnienv, NULL); jplis_assert(isSafeForJNICalls(jnienv)); return (sFallbackInternalError != NULL); } /* * Map everything to InternalError. */ jthrowable mapAllCheckedToInternalErrorMapper( JNIEnv * jnienv, jthrowable throwableToMap) { jthrowable mappedThrowable = NULL; jstring message = NULL; jplis_assert(throwableToMap != NULL); jplis_assert(isSafeForJNICalls(jnienv)); jplis_assert(!isUnchecked(jnienv, throwableToMap)); message = getMessageFromThrowable(jnienv, throwableToMap); mappedThrowable = createInternalError(jnienv, message); jplis_assert(isSafeForJNICalls(jnienv)); return mappedThrowable; } jboolean checkForThrowable( JNIEnv* jnienv) { return (*jnienv)->ExceptionCheck(jnienv); } jboolean isSafeForJNICalls( JNIEnv * jnienv) { return !(*jnienv)->ExceptionCheck(jnienv); } void logThrowable( JNIEnv * jnienv) { if ( checkForThrowable(jnienv) ) { (*jnienv)->ExceptionDescribe(jnienv); } } /** * Creates an exception or error with the fully qualified classname (ie java/lang/Error) * and message passed to its constructor */ jthrowable createThrowable( JNIEnv * jnienv, const char * className, jstring message) { jthrowable exception = NULL; jmethodID constructor = NULL; jclass exceptionClass = NULL; jboolean errorOutstanding = JNI_FALSE; jplis_assert(className != NULL); jplis_assert(isSafeForJNICalls(jnienv)); /* create new VMError with message from exception */ exceptionClass = (*jnienv)->FindClass(jnienv, className); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert(!errorOutstanding); if (!errorOutstanding) { constructor = (*jnienv)->GetMethodID( jnienv, exceptionClass, "", "(Ljava/lang/String;)V"); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert(!errorOutstanding); } if (!errorOutstanding) { exception = (*jnienv)->NewObject(jnienv, exceptionClass, constructor, message); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert(!errorOutstanding); } jplis_assert(isSafeForJNICalls(jnienv)); return exception; } jthrowable createInternalError(JNIEnv * jnienv, jstring message) { return createThrowable( jnienv, "java/lang/InternalError", message); } jthrowable createThrowableFromJVMTIErrorCode(JNIEnv * jnienv, jvmtiError errorCode) { const char * throwableClassName = NULL; const char * message = NULL; jstring messageString = NULL; switch ( errorCode ) { case JVMTI_ERROR_NULL_POINTER: throwableClassName = "java/lang/NullPointerException"; break; case JVMTI_ERROR_ILLEGAL_ARGUMENT: throwableClassName = "java/lang/IllegalArgumentException"; break; case JVMTI_ERROR_OUT_OF_MEMORY: throwableClassName = "java/lang/OutOfMemoryError"; break; case JVMTI_ERROR_CIRCULAR_CLASS_DEFINITION: throwableClassName = "java/lang/ClassCircularityError"; break; case JVMTI_ERROR_FAILS_VERIFICATION: throwableClassName = "java/lang/VerifyError"; break; case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_ADDED: throwableClassName = "java/lang/UnsupportedOperationException"; message = "class redefinition failed: attempted to add a method"; break; case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED: throwableClassName = "java/lang/UnsupportedOperationException"; message = "class redefinition failed: attempted to change the schema (add/remove fields)"; break; case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED: throwableClassName = "java/lang/UnsupportedOperationException"; message = "class redefinition failed: attempted to change superclass or interfaces"; break; case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_DELETED: throwableClassName = "java/lang/UnsupportedOperationException"; message = "class redefinition failed: attempted to delete a method"; break; case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED: throwableClassName = "java/lang/UnsupportedOperationException"; message = "class redefinition failed: attempted to change the class modifiers"; break; case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_CLASS_ATTRIBUTE_CHANGED: throwableClassName = "java/lang/UnsupportedOperationException"; message = "class redefinition failed: attempted to change the class NestHost or NestMembers attribute"; break; case JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED: throwableClassName = "java/lang/UnsupportedOperationException"; message = "class redefinition failed: attempted to change method modifiers"; break; case JVMTI_ERROR_UNSUPPORTED_VERSION: throwableClassName = "java/lang/UnsupportedClassVersionError"; break; case JVMTI_ERROR_NAMES_DONT_MATCH: throwableClassName = "java/lang/NoClassDefFoundError"; message = "class names don't match"; break; case JVMTI_ERROR_INVALID_CLASS_FORMAT: throwableClassName = "java/lang/ClassFormatError"; break; case JVMTI_ERROR_UNMODIFIABLE_CLASS: throwableClassName = "java/lang/instrument/UnmodifiableClassException"; break; case JVMTI_ERROR_INVALID_CLASS: throwableClassName = "java/lang/InternalError"; message = "class redefinition failed: invalid class"; break; case JVMTI_ERROR_CLASS_LOADER_UNSUPPORTED: throwableClassName = "java/lang/UnsupportedOperationException"; message = "unsupported operation"; break; case JVMTI_ERROR_INTERNAL: default: throwableClassName = "java/lang/InternalError"; break; } if ( message != NULL ) { jboolean errorOutstanding; messageString = (*jnienv)->NewStringUTF(jnienv, message); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert_msg(!errorOutstanding, "can't create exception java string"); } return createThrowable( jnienv, throwableClassName, messageString); } /** * Calls toString() on the given message which is the same call made by * Exception when passed a throwable to its constructor */ jstring getMessageFromThrowable( JNIEnv* jnienv, jthrowable exception) { jclass exceptionClass = NULL; jmethodID method = NULL; jstring message = NULL; jboolean errorOutstanding = JNI_FALSE; jplis_assert(isSafeForJNICalls(jnienv)); /* call getMessage on exception */ exceptionClass = (*jnienv)->GetObjectClass(jnienv, exception); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert(!errorOutstanding); if (!errorOutstanding) { method = (*jnienv)->GetMethodID(jnienv, exceptionClass, "toString", "()Ljava/lang/String;"); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert(!errorOutstanding); } if (!errorOutstanding) { message = (*jnienv)->CallObjectMethod(jnienv, exception, method); errorOutstanding = checkForAndClearThrowable(jnienv); jplis_assert(!errorOutstanding); } jplis_assert(isSafeForJNICalls(jnienv)); return message; } /** * Returns whether the exception given is an unchecked exception: * a subclass of Error or RuntimeException */ jboolean isUnchecked( JNIEnv* jnienv, jthrowable exception) { jboolean result = JNI_FALSE; jplis_assert(isSafeForJNICalls(jnienv)); result = (exception == NULL) || isInstanceofClassName(jnienv, exception, "java/lang/Error") || isInstanceofClassName(jnienv, exception, "java/lang/RuntimeException"); jplis_assert(isSafeForJNICalls(jnienv)); return result; } /* * Returns the current throwable, if any. Clears the throwable state. * Clients can use this to preserve the current throwable state on the stack. */ jthrowable preserveThrowable(JNIEnv * jnienv) { jthrowable result = (*jnienv)->ExceptionOccurred(jnienv); if ( result != NULL ) { (*jnienv)->ExceptionClear(jnienv); } return result; } /* * Installs the supplied throwable into the JNIEnv if the throwable is not null. * Clients can use this to preserve the current throwable state on the stack. */ void restoreThrowable( JNIEnv * jnienv, jthrowable preservedException) { throwThrowable( jnienv, preservedException); return; } void throwThrowable( JNIEnv * jnienv, jthrowable exception) { if ( exception != NULL ) { jint result = (*jnienv)->Throw(jnienv, exception); jplis_assert_msg(result == JNI_OK, "throwThrowable failed to re-throw"); } return; } /* * Always clears the JNIEnv throwable state. Returns true if an exception was present * before the clearing operation. */ jboolean checkForAndClearThrowable( JNIEnv * jnienv) { jboolean result = (*jnienv)->ExceptionCheck(jnienv); if ( result ) { (*jnienv)->ExceptionClear(jnienv); } return result; } /* creates a java.lang.InternalError and installs it into the JNIEnv */ void createAndThrowInternalError(JNIEnv * jnienv) { jthrowable internalError = createInternalError( jnienv, NULL); throwThrowable(jnienv, forceFallback(internalError)); } void createAndThrowThrowableFromJVMTIErrorCode(JNIEnv * jnienv, jvmtiError errorCode) { jthrowable throwable = createThrowableFromJVMTIErrorCode(jnienv, errorCode); throwThrowable(jnienv, forceFallback(throwable)); } void mapThrownThrowableIfNecessary( JNIEnv * jnienv, CheckedExceptionMapper mapper) { jthrowable originalThrowable = NULL; jthrowable resultThrowable = NULL; originalThrowable = preserveThrowable(jnienv); /* the throwable is now cleared, so JNI calls are safe */ if ( originalThrowable != NULL ) { /* if there is an exception: we can just throw it if it is unchecked. If checked, * we need to map it (mapper is conditional, will vary by usage, hence the callback) */ if ( isUnchecked(jnienv, originalThrowable) ) { resultThrowable = originalThrowable; } else { resultThrowable = (*mapper) (jnienv, originalThrowable); } } /* re-establish the correct throwable */ if ( resultThrowable != NULL ) { throwThrowable(jnienv, forceFallback(resultThrowable)); } }