fe008ae27a
Reviewed-by: darcy, weijun
415 lines
13 KiB
C
415 lines
13 KiB
C
/*
|
|
* 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));
|
|
}
|
|
|
|
}
|