From fa8db66368a9527c8df441a0bbfee714eb795930 Mon Sep 17 00:00:00 2001 From: da-woods Date: Mon, 27 Sep 2021 10:11:12 +0100 Subject: Avoid AddTraceback() if stringtab isn't set up (GH-4378) This can happen (rarely) with exceptions that occur very early in the module init process. Fixes https://github.com/cython/cython/issues/4377 Uses a fake Numpy module for testing to make a version of "import_array" that always fails. --- Cython/Compiler/Code.py | 51 +++++++++++++++++------------- Cython/Compiler/ModuleNode.py | 11 +++++-- tests/run/numpy_import_array_error.srctree | 40 +++++++++++++++++++++++ 3 files changed, 77 insertions(+), 25 deletions(-) create mode 100644 tests/run/numpy_import_array_error.srctree diff --git a/Cython/Compiler/Code.py b/Cython/Compiler/Code.py index a2affbe89..8f0af0ce2 100644 --- a/Cython/Compiler/Code.py +++ b/Cython/Compiler/Code.py @@ -1138,7 +1138,8 @@ class GlobalState(object): 'pystring_table', 'cached_builtins', 'cached_constants', - 'init_globals', + 'init_constants', + 'init_globals', # (utility code called at init-time) 'init_module', 'cleanup_globals', 'cleanup_module', @@ -1209,6 +1210,11 @@ class GlobalState(object): w.putln("") w.putln("static CYTHON_SMALL_CODE int __Pyx_InitGlobals(void) {") + w = self.parts['init_constants'] + w.enter_cfunc_scope() + w.putln("") + w.putln("static CYTHON_SMALL_CODE int __Pyx_InitConstants(void) {") + if not Options.generate_cleanup_code: del self.parts['cleanup_globals'] else: @@ -1284,13 +1290,14 @@ class GlobalState(object): w.putln("}") w.exit_cfunc_scope() - w = self.parts['init_globals'] - w.putln("return 0;") - if w.label_used(w.error_label): - w.put_label(w.error_label) - w.putln("return -1;") - w.putln("}") - w.exit_cfunc_scope() + for part in ['init_globals', 'init_constants']: + w = self.parts[part] + w.putln("return 0;") + if w.label_used(w.error_label): + w.put_label(w.error_label) + w.putln("return -1;") + w.putln("}") + w.exit_cfunc_scope() if Options.generate_cleanup_code: w = self.parts['cleanup_globals'] @@ -1510,7 +1517,7 @@ class GlobalState(object): return decl = self.parts['decls'] - init = self.parts['init_globals'] + init = self.parts['init_constants'] cnames = [] for (type_cname, method_name), cname in sorted(self.cached_cmethods.items()): cnames.append(cname) @@ -1560,7 +1567,7 @@ class GlobalState(object): decls_writer.putln("static Py_UNICODE %s[] = { %s };" % (cname, utf16_array)) decls_writer.putln("#endif") - init_globals = self.parts['init_globals'] + init_constants = self.parts['init_constants'] if py_strings: self.use_utility_code(UtilityCode.load_cached("InitStrings", "StringTools.c")) py_strings.sort() @@ -1575,9 +1582,9 @@ class GlobalState(object): decls_writer.putln("#if !CYTHON_USE_MODULE_STATE") not_limited_api_decls_writer = decls_writer.insertion_point() decls_writer.putln("#endif") - init_globals.putln("#if CYTHON_USE_MODULE_STATE") - init_globals_in_module_state = init_globals.insertion_point() - init_globals.putln("#endif") + init_constants.putln("#if CYTHON_USE_MODULE_STATE") + init_constants_in_module_state = init_constants.insertion_point() + init_constants.putln("#endif") for idx, py_string_args in enumerate(py_strings): c_cname, _, py_string = py_string_args if not py_string.is_str or not py_string.encoding or \ @@ -1627,20 +1634,20 @@ class GlobalState(object): py_string.is_str, py_string.intern )) - init_globals_in_module_state.putln("if (__Pyx_InitString(%s[%d], &%s) < 0) %s;" % ( + init_constants_in_module_state.putln("if (__Pyx_InitString(%s[%d], &%s) < 0) %s;" % ( Naming.stringtab_cname, idx, py_string.cname, - init_globals.error_goto(self.module_pos))) + init_constants.error_goto(self.module_pos))) w.putln("{0, 0, 0, 0, 0, 0, 0}") w.putln("};") - init_globals.putln("#if !CYTHON_USE_MODULE_STATE") - init_globals.putln( + init_constants.putln("#if !CYTHON_USE_MODULE_STATE") + init_constants.putln( "if (__Pyx_InitStrings(%s) < 0) %s;" % ( Naming.stringtab_cname, - init_globals.error_goto(self.module_pos))) - init_globals.putln("#endif") + init_constants.error_goto(self.module_pos))) + init_constants.putln("#endif") def generate_num_constants(self): consts = [(c.py_type, c.value[0] == '-', len(c.value), c.value, c.value_code, c) @@ -1648,7 +1655,7 @@ class GlobalState(object): consts.sort() decls_writer = self.parts['decls'] decls_writer.putln("#if !CYTHON_USE_MODULE_STATE") - init_globals = self.parts['init_globals'] + init_constants = self.parts['init_constants'] for py_type, _, _, value, value_code, c in consts: cname = c.cname self.parts['module_state'].putln("PyObject *%s;" % cname) @@ -1669,9 +1676,9 @@ class GlobalState(object): function = "PyInt_FromLong(%sL)" else: function = "PyInt_FromLong(%s)" - init_globals.putln('%s = %s; %s' % ( + init_constants.putln('%s = %s; %s' % ( cname, function % value_code, - init_globals.error_goto_if_null(cname, self.module_pos))) + init_constants.error_goto_if_null(cname, self.module_pos))) decls_writer.putln("#endif") # The functions below are there in a transition phase only diff --git a/Cython/Compiler/ModuleNode.py b/Cython/Compiler/ModuleNode.py index d0687624f..99b2f0028 100644 --- a/Cython/Compiler/ModuleNode.py +++ b/Cython/Compiler/ModuleNode.py @@ -2939,7 +2939,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): # start of module init/exec function (pre/post PEP 489) code.putln("{") - + code.putln('int stringtab_initialized = 0;') tempdecl_code = code.insertion_point() profile = code.globalstate.directives['profile'] @@ -3012,7 +3012,10 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): code.putln("#endif") code.putln("/*--- Initialize various global constants etc. ---*/") - code.put_error_if_neg(self.pos, "__Pyx_InitGlobals()") + code.put_error_if_neg(self.pos, "__Pyx_InitConstants()") + code.putln("stringtab_initialized = 1;") + code.put_error_if_neg(self.pos, "__Pyx_InitGlobals()") # calls any utility code + code.putln("#if PY_MAJOR_VERSION < 3 && (__PYX_DEFAULT_STRING_ENCODING_IS_ASCII || " "__PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT)") @@ -3095,7 +3098,9 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): for cname, type in code.funcstate.all_managed_temps(): code.put_xdecref(cname, type) code.putln('if (%s) {' % env.module_cname) - code.putln('if (%s) {' % env.module_dict_cname) + code.putln('if (%s && stringtab_initialized) {' % env.module_dict_cname) + # We can run into errors before the module or stringtab are initialized. + # In this case it is not safe to add a traceback (because it uses the stringtab) code.put_add_traceback(EncodedString("init %s" % env.qualified_name)) code.globalstate.use_utility_code(Nodes.traceback_utility_code) # Module reference and module dict are in global variables which might still be needed diff --git a/tests/run/numpy_import_array_error.srctree b/tests/run/numpy_import_array_error.srctree new file mode 100644 index 000000000..9f2c286f1 --- /dev/null +++ b/tests/run/numpy_import_array_error.srctree @@ -0,0 +1,40 @@ +PYTHON setup.py build_ext -i +PYTHON main.py + +############# setup.py ############ + +from distutils.core import setup +from Cython.Build import cythonize + +setup(ext_modules = cythonize('cimport_numpy.pyx')) + +############# numpy.pxd ############ + +# A fake Numpy module. This defines a version of _import_array +# that always fails. The Cython-generated call to _import_array +# happens quite early (before the stringtab is initialized) +# and thus the error itself handling could cause a segmentation fault +# https://github.com/cython/cython/issues/4377 + +cdef extern from *: + """ + #define NPY_NDARRAYOBJECT_H + static int _import_array(void) { + PyErr_SetString(PyExc_ValueError, "Oh no!"); + return -1; + } + """ + int _import_array() except -1 + +############# cimport_numpy.pyx ########### + +cimport numpy + +############# main.py #################### + +try: + import cimport_numpy +except ImportError as e: + print(e) +else: + assert(False) -- cgit v1.2.1