Skip to content

Commit fa8db66

Browse files
authored
Avoid AddTraceback() if stringtab isn't set up (cythonGH-4378)
This can happen (rarely) with exceptions that occur very early in the module init process. Fixes cython#4377 Uses a fake Numpy module for testing to make a version of "import_array" that always fails.
1 parent f94f26a commit fa8db66

File tree

3 files changed

+77
-25
lines changed

3 files changed

+77
-25
lines changed

Cython/Compiler/Code.py

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1138,7 +1138,8 @@ class GlobalState(object):
11381138
'pystring_table',
11391139
'cached_builtins',
11401140
'cached_constants',
1141-
'init_globals',
1141+
'init_constants',
1142+
'init_globals', # (utility code called at init-time)
11421143
'init_module',
11431144
'cleanup_globals',
11441145
'cleanup_module',
@@ -1209,6 +1210,11 @@ def initialize_main_c_code(self):
12091210
w.putln("")
12101211
w.putln("static CYTHON_SMALL_CODE int __Pyx_InitGlobals(void) {")
12111212

1213+
w = self.parts['init_constants']
1214+
w.enter_cfunc_scope()
1215+
w.putln("")
1216+
w.putln("static CYTHON_SMALL_CODE int __Pyx_InitConstants(void) {")
1217+
12121218
if not Options.generate_cleanup_code:
12131219
del self.parts['cleanup_globals']
12141220
else:
@@ -1284,13 +1290,14 @@ def close_global_decls(self):
12841290
w.putln("}")
12851291
w.exit_cfunc_scope()
12861292

1287-
w = self.parts['init_globals']
1288-
w.putln("return 0;")
1289-
if w.label_used(w.error_label):
1290-
w.put_label(w.error_label)
1291-
w.putln("return -1;")
1292-
w.putln("}")
1293-
w.exit_cfunc_scope()
1293+
for part in ['init_globals', 'init_constants']:
1294+
w = self.parts[part]
1295+
w.putln("return 0;")
1296+
if w.label_used(w.error_label):
1297+
w.put_label(w.error_label)
1298+
w.putln("return -1;")
1299+
w.putln("}")
1300+
w.exit_cfunc_scope()
12941301

12951302
if Options.generate_cleanup_code:
12961303
w = self.parts['cleanup_globals']
@@ -1510,7 +1517,7 @@ def generate_cached_methods_decls(self):
15101517
return
15111518

15121519
decl = self.parts['decls']
1513-
init = self.parts['init_globals']
1520+
init = self.parts['init_constants']
15141521
cnames = []
15151522
for (type_cname, method_name), cname in sorted(self.cached_cmethods.items()):
15161523
cnames.append(cname)
@@ -1560,7 +1567,7 @@ def generate_string_constants(self):
15601567
decls_writer.putln("static Py_UNICODE %s[] = { %s };" % (cname, utf16_array))
15611568
decls_writer.putln("#endif")
15621569

1563-
init_globals = self.parts['init_globals']
1570+
init_constants = self.parts['init_constants']
15641571
if py_strings:
15651572
self.use_utility_code(UtilityCode.load_cached("InitStrings", "StringTools.c"))
15661573
py_strings.sort()
@@ -1575,9 +1582,9 @@ def generate_string_constants(self):
15751582
decls_writer.putln("#if !CYTHON_USE_MODULE_STATE")
15761583
not_limited_api_decls_writer = decls_writer.insertion_point()
15771584
decls_writer.putln("#endif")
1578-
init_globals.putln("#if CYTHON_USE_MODULE_STATE")
1579-
init_globals_in_module_state = init_globals.insertion_point()
1580-
init_globals.putln("#endif")
1585+
init_constants.putln("#if CYTHON_USE_MODULE_STATE")
1586+
init_constants_in_module_state = init_constants.insertion_point()
1587+
init_constants.putln("#endif")
15811588
for idx, py_string_args in enumerate(py_strings):
15821589
c_cname, _, py_string = py_string_args
15831590
if not py_string.is_str or not py_string.encoding or \
@@ -1627,28 +1634,28 @@ def generate_string_constants(self):
16271634
py_string.is_str,
16281635
py_string.intern
16291636
))
1630-
init_globals_in_module_state.putln("if (__Pyx_InitString(%s[%d], &%s) < 0) %s;" % (
1637+
init_constants_in_module_state.putln("if (__Pyx_InitString(%s[%d], &%s) < 0) %s;" % (
16311638
Naming.stringtab_cname,
16321639
idx,
16331640
py_string.cname,
1634-
init_globals.error_goto(self.module_pos)))
1641+
init_constants.error_goto(self.module_pos)))
16351642
w.putln("{0, 0, 0, 0, 0, 0, 0}")
16361643
w.putln("};")
16371644

1638-
init_globals.putln("#if !CYTHON_USE_MODULE_STATE")
1639-
init_globals.putln(
1645+
init_constants.putln("#if !CYTHON_USE_MODULE_STATE")
1646+
init_constants.putln(
16401647
"if (__Pyx_InitStrings(%s) < 0) %s;" % (
16411648
Naming.stringtab_cname,
1642-
init_globals.error_goto(self.module_pos)))
1643-
init_globals.putln("#endif")
1649+
init_constants.error_goto(self.module_pos)))
1650+
init_constants.putln("#endif")
16441651

16451652
def generate_num_constants(self):
16461653
consts = [(c.py_type, c.value[0] == '-', len(c.value), c.value, c.value_code, c)
16471654
for c in self.num_const_index.values()]
16481655
consts.sort()
16491656
decls_writer = self.parts['decls']
16501657
decls_writer.putln("#if !CYTHON_USE_MODULE_STATE")
1651-
init_globals = self.parts['init_globals']
1658+
init_constants = self.parts['init_constants']
16521659
for py_type, _, _, value, value_code, c in consts:
16531660
cname = c.cname
16541661
self.parts['module_state'].putln("PyObject *%s;" % cname)
@@ -1669,9 +1676,9 @@ def generate_num_constants(self):
16691676
function = "PyInt_FromLong(%sL)"
16701677
else:
16711678
function = "PyInt_FromLong(%s)"
1672-
init_globals.putln('%s = %s; %s' % (
1679+
init_constants.putln('%s = %s; %s' % (
16731680
cname, function % value_code,
1674-
init_globals.error_goto_if_null(cname, self.module_pos)))
1681+
init_constants.error_goto_if_null(cname, self.module_pos)))
16751682
decls_writer.putln("#endif")
16761683

16771684
# The functions below are there in a transition phase only

Cython/Compiler/ModuleNode.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2939,7 +2939,7 @@ def generate_module_init_func(self, imported_modules, env, code):
29392939

29402940
# start of module init/exec function (pre/post PEP 489)
29412941
code.putln("{")
2942-
2942+
code.putln('int stringtab_initialized = 0;')
29432943
tempdecl_code = code.insertion_point()
29442944

29452945
profile = code.globalstate.directives['profile']
@@ -3012,7 +3012,10 @@ def generate_module_init_func(self, imported_modules, env, code):
30123012
code.putln("#endif")
30133013

30143014
code.putln("/*--- Initialize various global constants etc. ---*/")
3015-
code.put_error_if_neg(self.pos, "__Pyx_InitGlobals()")
3015+
code.put_error_if_neg(self.pos, "__Pyx_InitConstants()")
3016+
code.putln("stringtab_initialized = 1;")
3017+
code.put_error_if_neg(self.pos, "__Pyx_InitGlobals()") # calls any utility code
3018+
30163019

30173020
code.putln("#if PY_MAJOR_VERSION < 3 && (__PYX_DEFAULT_STRING_ENCODING_IS_ASCII || "
30183021
"__PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT)")
@@ -3095,7 +3098,9 @@ def generate_module_init_func(self, imported_modules, env, code):
30953098
for cname, type in code.funcstate.all_managed_temps():
30963099
code.put_xdecref(cname, type)
30973100
code.putln('if (%s) {' % env.module_cname)
3098-
code.putln('if (%s) {' % env.module_dict_cname)
3101+
code.putln('if (%s && stringtab_initialized) {' % env.module_dict_cname)
3102+
# We can run into errors before the module or stringtab are initialized.
3103+
# In this case it is not safe to add a traceback (because it uses the stringtab)
30993104
code.put_add_traceback(EncodedString("init %s" % env.qualified_name))
31003105
code.globalstate.use_utility_code(Nodes.traceback_utility_code)
31013106
# Module reference and module dict are in global variables which might still be needed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
PYTHON setup.py build_ext -i
2+
PYTHON main.py
3+
4+
############# setup.py ############
5+
6+
from distutils.core import setup
7+
from Cython.Build import cythonize
8+
9+
setup(ext_modules = cythonize('cimport_numpy.pyx'))
10+
11+
############# numpy.pxd ############
12+
13+
# A fake Numpy module. This defines a version of _import_array
14+
# that always fails. The Cython-generated call to _import_array
15+
# happens quite early (before the stringtab is initialized)
16+
# and thus the error itself handling could cause a segmentation fault
17+
# https://github.com/cython/cython/issues/4377
18+
19+
cdef extern from *:
20+
"""
21+
#define NPY_NDARRAYOBJECT_H
22+
static int _import_array(void) {
23+
PyErr_SetString(PyExc_ValueError, "Oh no!");
24+
return -1;
25+
}
26+
"""
27+
int _import_array() except -1
28+
29+
############# cimport_numpy.pyx ###########
30+
31+
cimport numpy
32+
33+
############# main.py ####################
34+
35+
try:
36+
import cimport_numpy
37+
except ImportError as e:
38+
print(e)
39+
else:
40+
assert(False)

0 commit comments

Comments
 (0)