Skip to content

Commit

Permalink
Verbose exceptions.
Browse files Browse the repository at this point in the history
  • Loading branch information
cpwright committed Sep 12, 2017
1 parent 2615f1f commit 69da2cd
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 13 deletions.
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
sources = [
os.path.join(src_main_c_dir, 'jpy_module.c'),
os.path.join(src_main_c_dir, 'jpy_diag.c'),
os.path.join(src_main_c_dir, 'jpy_verboseexcept.c'),
os.path.join(src_main_c_dir, 'jpy_conv.c'),
os.path.join(src_main_c_dir, 'jpy_compat.c'),
os.path.join(src_main_c_dir, 'jpy_jtype.c'),
Expand Down
159 changes: 148 additions & 11 deletions src/main/c/jpy_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include "jpy_module.h"
#include "jpy_diag.h"
#include "jpy_verboseexcept.h"
#include "jpy_jtype.h"
#include "jpy_jmethod.h"
#include "jpy_jfield.h"
Expand Down Expand Up @@ -120,6 +121,8 @@ JPy_JType* JPy_JClass = NULL;
JPy_JType* JPy_JString = NULL;
JPy_JType* JPy_JPyObject = NULL;
JPy_JType* JPy_JPyModule = NULL;
JPy_JType* JPy_JThrowable = NULL;
JPy_JType* JPy_JStackTraceElement = NULL;


// java.lang.Comparable
Expand Down Expand Up @@ -204,6 +207,15 @@ jmethodID JPy_PyObject_GetPointer_MID = NULL;
jmethodID JPy_PyObject_Init_MID = NULL;
jmethodID JPy_PyModule_Init_MID = NULL;

// java.lang.Throwable
jclass JPy_Throwable_JClass = NULL;
jmethodID JPy_Throwable_getMessage_MID = NULL;
jmethodID JPy_Throwable_getStackTrace_MID = NULL;
jmethodID JPy_Throwable_getCause_MID = NULL;

// stack trace element
jclass JPy_StackTraceElement_JClass = NULL;

// }}}


Expand Down Expand Up @@ -341,6 +353,15 @@ PyMODINIT_FUNC JPY_MODULE_INIT_FUNC(void)
PyModule_AddObject(JPy_Module, "diag", pyDiag);
}

if (PyType_Ready(&VerboseExceptions_Type) < 0) {
JPY_RETURN(NULL);
}
{
PyObject* pyVerboseExceptions = VerboseExceptions_New();
Py_INCREF(pyVerboseExceptions);
PyModule_AddObject(JPy_Module, "VerboseExceptions", pyVerboseExceptions);
}

/////////////////////////////////////////////////////////////////////////

if (JPy_JVM != NULL) {
Expand Down Expand Up @@ -795,6 +816,8 @@ int JPy_InitGlobalVars(JNIEnv* jenv)
DEFINE_CLASS(JPy_Void_JClass, "java/lang/Void");

DEFINE_CLASS(JPy_String_JClass, "java/lang/String");
DEFINE_CLASS(JPy_Throwable_JClass, "java/lang/Throwable");
DEFINE_CLASS(JPy_StackTraceElement_JClass, "java/lang/StackTraceElement");

// Non-Object types: Primitive types and void.
DEFINE_NON_OBJECT_TYPE(JPy_JBoolean, JPy_Boolean_JClass);
Expand All @@ -821,6 +844,10 @@ int JPy_InitGlobalVars(JNIEnv* jenv)
DEFINE_OBJECT_TYPE(JPy_JDoubleObj, JPy_Double_JClass);
// Other objects.
DEFINE_OBJECT_TYPE(JPy_JString, JPy_String_JClass);
DEFINE_OBJECT_TYPE(JPy_JThrowable, JPy_Throwable_JClass);
DEFINE_OBJECT_TYPE(JPy_JStackTraceElement, JPy_StackTraceElement_JClass);
DEFINE_METHOD(JPy_Throwable_getCause_MID, JPy_Throwable_JClass, "getCause", "()Ljava/lang/Throwable;");
DEFINE_METHOD(JPy_Throwable_getStackTrace_MID, JPy_Throwable_JClass, "getStackTrace", "()[Ljava/lang/StackTraceElement;");

// JType_AddClassAttribute is actually called from within JType_GetType(), but not for
// JPy_JObject and JPy_JClass for an obvious reason. So we do it now:
Expand Down Expand Up @@ -954,31 +981,141 @@ void JPy_ClearGlobalVars(JNIEnv* jenv)
JPy_JPyModule = NULL;
}

#define AT_STRING "\t at "
#define AT_STRLEN 5
#define CAUSED_BY_STRING "caused by "
#define CAUSED_BY_STRLEN 10


void JPy_HandleJavaException(JNIEnv* jenv)
{
jthrowable error = (*jenv)->ExceptionOccurred(jenv);
if (error != NULL) {
jstring message;
int allocError = 0;

if (JPy_DiagFlags != 0) {
(*jenv)->ExceptionDescribe(jenv);
}

message = (jstring) (*jenv)->CallObjectMethod(jenv, error, JPy_Object_ToString_MID);
if (message != NULL) {
const char* messageChars;

messageChars = (*jenv)->GetStringUTFChars(jenv, message, NULL);
if (messageChars != NULL) {
PyErr_Format(PyExc_RuntimeError, "%s", messageChars);
(*jenv)->ReleaseStringUTFChars(jenv, message, messageChars);
if (JPy_VerboseExceptions) {
char *stackTraceString;
int stackTraceLength = 0;

stackTraceString = strdup("");

jthrowable cause = error;
do {
/* We want the type and the detail string, which is actually what a Throwable toString() does by
* default, as does the default printStackTrace(). */

if (stackTraceLength > 0) {
char *newStackString;

newStackString = realloc(stackTraceString, CAUSED_BY_STRLEN + 1 + stackTraceLength);
if (newStackString == NULL) {
allocError = 1;
break;
}
stackTraceString = newStackString;
strcat(stackTraceString, CAUSED_BY_STRING);
stackTraceLength += CAUSED_BY_STRLEN;
}

message = (jstring) (*jenv)->CallObjectMethod(jenv, cause, JPy_Object_ToString_MID);
if (message != NULL) {
const char *messageChars = (*jenv)->GetStringUTFChars(jenv, message, NULL);
if (messageChars != NULL) {
char *newStackString;
size_t len = strlen(messageChars);

newStackString = realloc(stackTraceString, len + 2 + stackTraceLength);
if (newStackString == NULL) {
(*jenv)->ReleaseStringUTFChars(jenv, message, messageChars);
allocError = 1;
break;
}

stackTraceString = newStackString;
strcat(stackTraceString, messageChars);
stackTraceString[stackTraceLength + len] = '\n';
stackTraceString[stackTraceLength + len + 1] = '\0';
stackTraceLength += (len + 1);

(*jenv)->ReleaseStringUTFChars(jenv, message, messageChars);
} else {
allocError = 1;
break;
}
(*jenv)->DeleteLocalRef(jenv, message);
}

/* We should assemble a string based on the stack trace. */
jarray stackTrace = (*jenv)->CallObjectMethod(jenv, cause, JPy_Throwable_getStackTrace_MID);

jint stackTraceElements = (*jenv)->GetArrayLength(jenv, stackTrace);
for (jint ii = 0; ii < stackTraceElements; ++ii) {
jobject traceElement = (*jenv)->GetObjectArrayElement(jenv, stackTrace, ii);
if (traceElement != NULL) {
message = (jstring) (*jenv)->CallObjectMethod(jenv, traceElement, JPy_Object_ToString_MID);
if (message != NULL) {
const char *messageChars = (*jenv)->GetStringUTFChars(jenv, message, NULL);
if (messageChars == NULL) {
(*jenv)->ReleaseStringUTFChars(jenv, message, messageChars);
allocError = 1;
break;
}

size_t len = strlen(messageChars);

char *newStackString = realloc(stackTraceString, len + 2 + AT_STRLEN + stackTraceLength);
if (newStackString == NULL) {
allocError = 1;
break;
}

stackTraceString = newStackString;
strcat(stackTraceString, AT_STRING);
strcat(stackTraceString, messageChars);
stackTraceString[stackTraceLength + len + AT_STRLEN] = '\n';
stackTraceString[stackTraceLength + len + AT_STRLEN + 1] = '\0';
stackTraceLength += (len + 1 + AT_STRLEN);

(*jenv)->ReleaseStringUTFChars(jenv, message, messageChars);
}

}
}
(*jenv)->DeleteLocalRef(jenv, stackTrace);

/** Now the next cause. */
cause = (*jenv)->CallObjectMethod(jenv, cause, JPy_Throwable_getCause_MID);
} while (cause != NULL && !allocError);

if (allocError == 0 && stackTraceString != NULL) {
PyErr_Format(PyExc_RuntimeError, "%s", stackTraceString);
} else {
PyErr_SetString(PyExc_RuntimeError, "Java VM exception occurred, but failed to allocate message text");
PyErr_SetString(PyExc_RuntimeError,
"Java VM exception occurred, but failed to allocate message text");
}
(*jenv)->DeleteLocalRef(jenv, message);
free(stackTraceString);
} else {
PyErr_SetString(PyExc_RuntimeError, "Java VM exception occurred, no message");
message = (jstring) (*jenv)->CallObjectMethod(jenv, error, JPy_Object_ToString_MID);
if (message != NULL) {
const char *messageChars;

messageChars = (*jenv)->GetStringUTFChars(jenv, message, NULL);
if (messageChars != NULL) {
PyErr_Format(PyExc_RuntimeError, "%s", messageChars);
(*jenv)->ReleaseStringUTFChars(jenv, message, messageChars);
} else {
PyErr_SetString(PyExc_RuntimeError,
"Java VM exception occurred, but failed to allocate message text");
}
(*jenv)->DeleteLocalRef(jenv, message);
} else {
PyErr_SetString(PyExc_RuntimeError, "Java VM exception occurred, no message");
}
}

(*jenv)->DeleteLocalRef(jenv, error);
Expand Down
95 changes: 95 additions & 0 deletions src/main/c/jpy_verboseexcept.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright 2015 Brockmann Consult GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <Python.h>
#include "jpy_verboseexcept.h"

int JPy_VerboseExceptions = 0;

PyObject* VerboseExceptions_New(void)
{
return PyObject_New(PyObject, &VerboseExceptions_Type);
}


PyObject* VerboseExceptions_getattro(PyObject* self, PyObject *attr_name)
{
if (strcmp(JPy_AS_UTF8(attr_name), "enabled") == 0) {
return PyBool_FromLong(JPy_VerboseExceptions);
} else {
return PyObject_GenericGetAttr(self, attr_name);
}
}


int VerboseExceptions_setattro(PyObject* self, PyObject *attr_name, PyObject *v)
{
//printf("Diag_setattro: attr_name=%s\n", JPy_AS_UTF8(attr_name));
if (strcmp(JPy_AS_UTF8(attr_name), "enabled") == 0) {
if (PyBool_Check(v)) {
JPy_VerboseExceptions = v == Py_True;
} else {
PyErr_SetString(PyExc_ValueError, "value for 'flags' must be a boolean");
return -1;
}
return 0;
} else {
return PyObject_GenericSetAttr(self, attr_name, v);
}
}


PyTypeObject VerboseExceptions_Type =
{
PyVarObject_HEAD_INIT(NULL, 0)
"jpy.VerboseExceptions", /* tp_name */
sizeof (VerboseExceptions_Type), /* tp_basicsize */
0, /* tp_itemsize */
NULL, /* tp_dealloc */
NULL, /* tp_print */
NULL, /* tp_getattr */
NULL, /* tp_setattr */
NULL, /* tp_reserved */
NULL, /* tp_repr */
NULL, /* tp_as_number */
NULL, /* tp_as_sequence */
NULL, /* tp_as_mapping */
NULL, /* tp_hash */
NULL, /* tp_call */
NULL, /* tp_str */
(getattrofunc) VerboseExceptions_getattro, /* tp_getattro */
(setattrofunc) VerboseExceptions_setattro, /* tp_setattro */
NULL, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"Controls python exception verbosity", /* tp_doc */
NULL, /* tp_traverse */
NULL, /* tp_clear */
NULL, /* tp_richcompare */
0, /* tp_weaklistoffset */
NULL, /* tp_iter */
NULL, /* tp_iternext */
NULL, /* tp_methods */
NULL, /* tp_members */
NULL, /* tp_getset */
NULL, /* tp_base */
NULL, /* tp_dict */
NULL, /* tp_descr_get */
NULL, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc) NULL, /* tp_init */
NULL, /* tp_alloc */
NULL, /* tp_new */
};
34 changes: 34 additions & 0 deletions src/main/c/jpy_verboseexcept.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2015 Brockmann Consult GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef JPY_VERBOSEEXCEPT_H
#define JPY_VERBOSEEXCEPT_H

#ifdef __cplusplus
extern "C" {
#endif

#include "jpy_compat.h"

extern PyTypeObject VerboseExceptions_Type;
extern int JPy_VerboseExceptions;

PyObject* VerboseExceptions_New(void);

#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* !JPY_DIAG_H */
12 changes: 12 additions & 0 deletions src/test/java/org/jpy/fixtures/ExceptionTestFixture.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ public int throwNpeIfArgIsNull(String arg) {
return arg.length();
}

public int throwNpeIfArgIsNull2(String arg) {
return throwNpeIfArgIsNull(arg);
}

public int throwNpeIfArgIsNullNested(String arg) {
try {
return throwNpeIfArgIsNull(arg);
} catch (Exception e) {
throw new RuntimeException("Nested exception", e);
}
}

public int throwAioobeIfIndexIsNotZero(int index) {
int[] ints = new int[]{101};
return ints[index];
Expand Down
Loading

0 comments on commit 69da2cd

Please sign in to comment.