/*
* Copyright (c) 2003, 2006, 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 <jni.h>
#include <jvmti.h>
#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,
"<init>",
"(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_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));
}
}