Merge branch 'coleb-python35_dtor_exception_fix'
* coleb-python35_dtor_exception_fix: Add test case for Python 3.5 assertion with a pending StopIteration Amend python_destructor_exception runtime test Call PyErr_WriteUnraisable if a destructor sets a Python exception (-builtin) Extended zjturner's changes to encompass all function dispatch and use PyErr_WriteUnraisable to handle exceptions during __del__. Python - Save and restore exception state before calling destroy.
This commit is contained in:
commit
b15ad9349e
5 changed files with 118 additions and 10 deletions
|
|
@ -58,6 +58,7 @@ CPP_TEST_CASES += \
|
||||||
primitive_types \
|
primitive_types \
|
||||||
python_abstractbase \
|
python_abstractbase \
|
||||||
python_append \
|
python_append \
|
||||||
|
python_destructor_exception \
|
||||||
python_director \
|
python_director \
|
||||||
python_docstring \
|
python_docstring \
|
||||||
python_nondynamic \
|
python_nondynamic \
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import python_destructor_exception
|
||||||
|
from StringIO import StringIO
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def error_function():
|
||||||
|
python_destructor_exception.ClassWithThrowingDestructor().GetBlah()
|
||||||
|
|
||||||
|
def runtest():
|
||||||
|
attributeErrorOccurred = False
|
||||||
|
try:
|
||||||
|
error_function()
|
||||||
|
except AttributeError, e:
|
||||||
|
attributeErrorOccurred = True
|
||||||
|
return attributeErrorOccurred
|
||||||
|
|
||||||
|
def test1():
|
||||||
|
stderr_saved = sys.stderr
|
||||||
|
buffer = StringIO()
|
||||||
|
attributeErrorOccurred = False
|
||||||
|
try:
|
||||||
|
# Suppress stderr while making this call to suppress the output shown by PyErr_WriteUnraisable
|
||||||
|
sys.stderr = buffer
|
||||||
|
|
||||||
|
attributeErrorOccurred = runtest()
|
||||||
|
finally:
|
||||||
|
sys.stderr.flush()
|
||||||
|
sys.stderr = stderr_saved
|
||||||
|
|
||||||
|
assert attributeErrorOccurred
|
||||||
|
assert buffer.getvalue().count("I am the ClassWithThrowingDestructor dtor doing bad things") >= 1
|
||||||
|
|
||||||
|
class VectorHolder(object):
|
||||||
|
def __init__(self, v):
|
||||||
|
self.v = v
|
||||||
|
def gen(self):
|
||||||
|
for e in self.v:
|
||||||
|
yield e
|
||||||
|
|
||||||
|
# See issue #559, #560, #573 - In Python 3.5, test2() call to the generator 'gen' was
|
||||||
|
# resulting in the following (not for -builtin where there is no call to SWIG_Python_CallFunctor
|
||||||
|
# as SwigPyObject_dealloc is not used):
|
||||||
|
#
|
||||||
|
# StopIteration
|
||||||
|
#
|
||||||
|
# During handling of the above exception, another exception occurred:
|
||||||
|
# ...
|
||||||
|
# SystemError: <built-in function delete_VectorInt> returned a result with an error set
|
||||||
|
|
||||||
|
def addup():
|
||||||
|
sum = 0
|
||||||
|
for i in VectorHolder(python_destructor_exception.VectorInt([1, 2, 3])).gen():
|
||||||
|
sum = sum + i
|
||||||
|
return sum
|
||||||
|
|
||||||
|
def test2():
|
||||||
|
sum = addup()
|
||||||
|
|
||||||
|
if sum != 6:
|
||||||
|
raise RuntimeError("Sum is incorrect")
|
||||||
|
|
||||||
|
# These two tests are different are two different ways to recreate essentially the same problem
|
||||||
|
# reported by Python 3.5 that an exception was already set when destroying a wrapped object
|
||||||
|
test1()
|
||||||
|
test2()
|
||||||
19
Examples/test-suite/python_destructor_exception.i
Normal file
19
Examples/test-suite/python_destructor_exception.i
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
/* File : example.i */
|
||||||
|
%module python_destructor_exception
|
||||||
|
%include exception.i
|
||||||
|
|
||||||
|
%exception ClassWithThrowingDestructor::~ClassWithThrowingDestructor()
|
||||||
|
{
|
||||||
|
$action
|
||||||
|
SWIG_exception(SWIG_RuntimeError, "I am the ClassWithThrowingDestructor dtor doing bad things");
|
||||||
|
}
|
||||||
|
|
||||||
|
%inline %{
|
||||||
|
class ClassWithThrowingDestructor
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
%}
|
||||||
|
|
||||||
|
%include <std_vector.i>
|
||||||
|
%template(VectorInt) std::vector<int>;
|
||||||
|
|
@ -9,9 +9,17 @@ SWIGINTERN void \
|
||||||
wrapper##_closure(PyObject *a) { \
|
wrapper##_closure(PyObject *a) { \
|
||||||
SwigPyObject *sobj; \
|
SwigPyObject *sobj; \
|
||||||
sobj = (SwigPyObject *)a; \
|
sobj = (SwigPyObject *)a; \
|
||||||
Py_XDECREF(sobj->dict); \
|
Py_XDECREF(sobj->dict); \
|
||||||
if (sobj->own) { \
|
if (sobj->own) { \
|
||||||
|
PyObject *val = 0, *type = 0, *tb = 0; \
|
||||||
|
PyErr_Fetch(&val, &type, &tb); \
|
||||||
PyObject *o = wrapper(a, NULL); \
|
PyObject *o = wrapper(a, NULL); \
|
||||||
|
if (!o) { \
|
||||||
|
PyObject *deallocname = PyString_FromString(#wrapper); \
|
||||||
|
PyErr_WriteUnraisable(deallocname); \
|
||||||
|
Py_DECREF(deallocname); \
|
||||||
|
} \
|
||||||
|
PyErr_Restore(val, type, tb); \
|
||||||
Py_XDECREF(o); \
|
Py_XDECREF(o); \
|
||||||
} \
|
} \
|
||||||
if (PyType_IS_GC(a->ob_type)) { \
|
if (PyType_IS_GC(a->ob_type)) { \
|
||||||
|
|
@ -64,7 +72,7 @@ wrapper##_closure(PyObject *a, PyObject *b, PyObject *c) { \
|
||||||
|
|
||||||
#define SWIGPY_TERNARYCALLFUNC_CLOSURE(wrapper) \
|
#define SWIGPY_TERNARYCALLFUNC_CLOSURE(wrapper) \
|
||||||
SWIGINTERN PyObject * \
|
SWIGINTERN PyObject * \
|
||||||
wrapper##_closure(PyObject *callable_object, PyObject *args, PyObject *) { \
|
wrapper##_closure(PyObject *callable_object, PyObject *args, PyObject *) { \
|
||||||
return wrapper(callable_object, args); \
|
return wrapper(callable_object, args); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -124,7 +132,7 @@ wrapper##_closure(PyObject *a, Py_ssize_t b) { \
|
||||||
return result; \
|
return result; \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define SWIGPY_FUNPACK_SSIZEARGFUNC_CLOSURE(wrapper) \
|
#define SWIGPY_FUNPACK_SSIZEARGFUNC_CLOSURE(wrapper) \
|
||||||
SWIGINTERN PyObject * \
|
SWIGINTERN PyObject * \
|
||||||
wrapper##_closure(PyObject *a, Py_ssize_t b) { \
|
wrapper##_closure(PyObject *a, Py_ssize_t b) { \
|
||||||
PyObject *arg, *result; \
|
PyObject *arg, *result; \
|
||||||
|
|
|
||||||
|
|
@ -536,16 +536,32 @@ SwigPyObject_dealloc(PyObject *v)
|
||||||
if (destroy) {
|
if (destroy) {
|
||||||
/* destroy is always a VARARGS method */
|
/* destroy is always a VARARGS method */
|
||||||
PyObject *res;
|
PyObject *res;
|
||||||
|
|
||||||
|
/* PyObject_CallFunction() has the potential to silently drop
|
||||||
|
the active active exception. In cases of unnamed temporary
|
||||||
|
variable or where we just finished iterating over a generator
|
||||||
|
StopIteration will be active right now, and this needs to
|
||||||
|
remain true upon return from SwigPyObject_dealloc. So save
|
||||||
|
and restore. */
|
||||||
|
|
||||||
|
PyObject *val = NULL, *type = NULL, *tb = NULL;
|
||||||
|
PyErr_Fetch(&val, &type, &tb);
|
||||||
|
|
||||||
if (data->delargs) {
|
if (data->delargs) {
|
||||||
/* we need to create a temporary object to carry the destroy operation */
|
/* we need to create a temporary object to carry the destroy operation */
|
||||||
PyObject *tmp = SwigPyObject_New(sobj->ptr, ty, 0);
|
PyObject *tmp = SwigPyObject_New(sobj->ptr, ty, 0);
|
||||||
res = SWIG_Python_CallFunctor(destroy, tmp);
|
res = SWIG_Python_CallFunctor(destroy, tmp);
|
||||||
Py_DECREF(tmp);
|
Py_DECREF(tmp);
|
||||||
} else {
|
} else {
|
||||||
PyCFunction meth = PyCFunction_GET_FUNCTION(destroy);
|
PyCFunction meth = PyCFunction_GET_FUNCTION(destroy);
|
||||||
PyObject *mself = PyCFunction_GET_SELF(destroy);
|
PyObject *mself = PyCFunction_GET_SELF(destroy);
|
||||||
res = ((*meth)(mself, v));
|
res = ((*meth)(mself, v));
|
||||||
}
|
}
|
||||||
|
if (!res)
|
||||||
|
PyErr_WriteUnraisable(destroy);
|
||||||
|
|
||||||
|
PyErr_Restore(val, type, tb);
|
||||||
|
|
||||||
Py_XDECREF(res);
|
Py_XDECREF(res);
|
||||||
}
|
}
|
||||||
#if !defined(SWIG_PYTHON_SILENT_MEMLEAK)
|
#if !defined(SWIG_PYTHON_SILENT_MEMLEAK)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue