Commit bba69054 authored by da-woods's avatar da-woods Committed by GitHub

Fix "except+*" exception handling (GH-4013)

Fixes issue https://github.com/cython/cython/issues/3065
Fixes issue https://github.com/cython/cython/issues/3066

* delete cpp_exception_declaration_compatibility test
  Because I'm emitting an error at an earlier stage I don't think there's a way to get this test to work.
parent 395b89c1
......@@ -266,6 +266,20 @@ def translate_cpp_exception(code, pos, inside, py_result, exception_value, nogil
code.putln(code.error_goto(pos))
code.putln("}")
def needs_cpp_exception_conversion(node):
assert node.exception_check == "+"
if node.exception_value is None:
return True
# exception_value can be a NameNode
# (in which case it's used as a handler function and no conversion is needed)
if node.exception_value.is_name:
return False
# or a CharNode with a value of "*"
if isinstance(node.exception_value, CharNode) and node.exception_value.value == "*":
return True
# Most other const-nodes are disallowed after "+" by the parser
return False
# Used to handle the case where an lvalue expression and an overloaded assignment
# both have an exception declaration.
......@@ -3874,7 +3888,7 @@ class IndexNode(_IndexingBaseNode):
if self.exception_check:
if not setting:
self.is_temp = True
if self.exception_value is None:
if needs_cpp_exception_conversion(self):
env.use_utility_code(UtilityCode.load_cached("CppExceptionConversion", "CppSupport.cpp"))
self.index = self.index.coerce_to(func_type.args[0].type, env)
self.type = func_type.return_type
......@@ -5958,7 +5972,7 @@ class SimpleCallNode(CallNode):
env.use_utility_code(pyerr_occurred_withgil_utility_code)
# C++ exception handler
if func_type.exception_check == '+':
if func_type.exception_value is None:
if needs_cpp_exception_conversion(func_type):
env.use_utility_code(UtilityCode.load_cached("CppExceptionConversion", "CppSupport.cpp"))
self.overflowcheck = env.directives['overflowcheck']
......@@ -10279,7 +10293,7 @@ class UnopNode(ExprNode):
self.exception_value = entry.type.exception_value
if self.exception_check == '+':
self.is_temp = True
if self.exception_value is None:
if needs_cpp_exception_conversion(self):
env.use_utility_code(UtilityCode.load_cached("CppExceptionConversion", "CppSupport.cpp"))
else:
self.exception_check = ''
......@@ -11232,7 +11246,7 @@ class BinopNode(ExprNode):
# Used by NumBinopNodes to break up expressions involving multiple
# operators so that exceptions can be handled properly.
self.is_temp = 1
if self.exception_value is None:
if needs_cpp_exception_conversion(self):
env.use_utility_code(UtilityCode.load_cached("CppExceptionConversion", "CppSupport.cpp"))
if func_type.is_ptr:
func_type = func_type.base_type
......@@ -12849,7 +12863,7 @@ class PrimaryCmpNode(ExprNode, CmpNode):
self.exception_value = func_type.exception_value
if self.exception_check == '+':
self.is_temp = True
if self.exception_value is None:
if needs_cpp_exception_conversion(self):
env.use_utility_code(UtilityCode.load_cached("CppExceptionConversion", "CppSupport.cpp"))
if len(func_type.args) == 1:
self.operand2 = self.operand2.coerce_to(func_type.args[0].type, env)
......
......@@ -627,8 +627,8 @@ class CFuncDeclaratorNode(CDeclaratorNode):
# args [CArgDeclNode]
# templates [TemplatePlaceholderType]
# has_varargs boolean
# exception_value ConstNode
# exception_check boolean True if PyErr_Occurred check needed
# exception_value ConstNode or NameNode NameNode when the name of a c++ exception conversion function
# exception_check boolean or "+" True if PyErr_Occurred check needed, "+" for a c++ check
# nogil boolean Can be called without gil
# with_gil boolean Acquire gil around function body
# is_const_method boolean Whether this is a const method
......@@ -2512,8 +2512,15 @@ class CFuncDefNode(FuncDefNode):
"Function with optional arguments may not be declared public or api")
if typ.exception_check == '+' and self.visibility != 'extern':
warning(self.cfunc_declarator.pos,
if typ.exception_value and typ.exception_value.is_name:
# it really is impossible to reason about what the user wants to happens
# if they've specified a C++ exception translation function. Therefore,
# raise an error.
error(self.cfunc_declarator.pos,
"Only extern functions can throw C++ exceptions.")
else:
warning(self.cfunc_declarator.pos,
"Only extern functions can throw C++ exceptions.", 2)
for formal_arg, type_arg in zip(self.args, typ.args):
self.align_argument_type(env, type_arg)
......@@ -2804,7 +2811,6 @@ class CFuncDefNode(FuncDefNode):
if self.return_type.is_pyobject:
return "0"
else:
#return None
return self.entry.type.exception_value
def caller_will_check_exceptions(self):
......
# mode: error
# tag: warnings
cdef inline void handle_exception():
pass
# GH 3064 - cppfunc caused invalid code to be generated with +handle_exception
# error to prevent this
cdef test_func1(self) except +handle_exception:
pass
# warning
cdef test_func2(self) except +:
pass
_ERRORS = """
9:16: Only extern functions can throw C++ exceptions.
"""
_WARNINGS = """
13:16: Only extern functions can throw C++ exceptions.
"""
# tag: cpp
"""
PYTHON setup.py build_ext -i
PYTHON test.py
"""
############### setup.py ###################
from distutils.core import setup
from Cython.Build import cythonize
setup(
name="cython_test",
ext_modules=cythonize('*.pyx', language="c++")
)
############### test.py ###################
from cpp_exc import TestClass
TestClass().test_func()
############### cpp_exc.pxd ###################
cdef inline void handle_exception():
pass
cdef class TestClass:
cpdef test_func(self) except +handle_exception
############### cpp_exc.pyx ###################
cdef class TestClass:
cpdef test_func(self) except +handle_exception:
print('test')
# mode: run
# tag: cpp, werror
# ticket: 3065
# This is intentionally in a file on its own. The issue was that it failed to generate utility-code
# and so putting it with the other c++ exception checks wouldn't be a useful test
cdef extern from *:
"""
#include <stdexcept>
void cppf(int raiseCpp) {
if (raiseCpp) {
throw std::runtime_error("cpp");
} else {
PyErr_SetString(PyExc_RuntimeError, "py");
}
}
"""
void cppf(int) except+*
def callcppf(int raiseCpp):
"""
>>> callcppf(0)
py
>>> callcppf(1)
cpp
"""
try:
cppf(raiseCpp)
except RuntimeError as e:
print(e.args[0])
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment