From 415e7ea3c142ae14fef4aebdf0b73b5a92d79e2a Mon Sep 17 00:00:00 2001 From: Mark Florisson Date: Tue, 16 Nov 2010 12:58:49 +0100 Subject: [PATCH] Ensure 'cy locals' and 'cy exec' don't use uninitialized local variables (which fixes a lot of 'cy exec' core dumps! :) --- Cython/Compiler/ParseTreeTransforms.py | 15 ++++++-- Cython/Debugger/libcython.py | 48 +++++++++++--------------- Cython/Debugger/libpython.py | 10 ++++-- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/Cython/Compiler/ParseTreeTransforms.py b/Cython/Compiler/ParseTreeTransforms.py index bba734a0..eecf56d5 100644 --- a/Cython/Compiler/ParseTreeTransforms.py +++ b/Cython/Compiler/ParseTreeTransforms.py @@ -1606,13 +1606,22 @@ class DebugTransform(CythonTransform): cname = entry.cname # if entry.type.is_extension_type: # cname = entry.type.typeptr_cname - + + if not entry.pos: + # this happens for variables that are not in the user's code, + # e.g. for the global __builtins__, __doc__, etc. We can just + # set the lineno to 0 for those. + lineno = '0' + else: + lineno = str(entry.pos[1]) + attrs = dict( name=entry.name, cname=cname, qualified_name=entry.qualified_name, - type=vartype) - + type=vartype, + lineno=lineno) + self.tb.start('LocalVar', attrs) self.tb.end('LocalVar') diff --git a/Cython/Debugger/libcython.py b/Cython/Debugger/libcython.py index f02817bd..a781bcf0 100644 --- a/Cython/Debugger/libcython.py +++ b/Cython/Debugger/libcython.py @@ -159,11 +159,12 @@ class CythonModule(object): class CythonVariable(object): - def __init__(self, name, cname, qualified_name, type): + def __init__(self, name, cname, qualified_name, type, lineno): self.name = name self.cname = cname self.qualified_name = qualified_name self.type = type + self.lineno = int(lineno) class CythonFunction(CythonVariable): def __init__(self, @@ -177,10 +178,10 @@ class CythonFunction(CythonVariable): super(CythonFunction, self).__init__(name, cname, qualified_name, - type) + type, + lineno) self.module = module self.pf_cname = pf_cname - self.lineno = int(lineno) self.locals = {} self.arguments = [] self.step_into_functions = set() @@ -387,6 +388,10 @@ class CythonBase(object): print '%s%-*s = %s%s' % (prefix, max_name_length, name, typename, value) + def is_initialized(self, cython_func, local_name): + cur_lineno = self.get_cython_lineno() + return (local_name in cython_func.arguments or + cur_lineno > cython_func.locals[local_name].lineno) class SourceFileDescriptor(object): def __init__(self, filename, lexer, formatter=None): @@ -1073,12 +1078,8 @@ class CyLocals(CythonCommand): completer_class = gdb.COMPLETE_NONE def _print_if_initialized(self, cyvar, max_name_length, prefix=''): - try: + if self.is_initialized(self.get_cython_function(), cyvar.name): value = gdb.parse_and_eval(cyvar.cname) - except RuntimeError: - # variable not initialized yet - pass - else: self.print_gdb_value(cyvar.name, value, max_name_length, prefix) @dispatch_on_frame(c_command='info locals', python_command='py-locals') @@ -1134,20 +1135,12 @@ class CyExec(CythonCommand, libpython.PyExec): def _fill_locals_dict(self, executor, local_dict_pointer): "Fill a remotely allocated dict with values from the Cython C stack" cython_func = self.get_cython_function() + current_lineno = self.get_cython_lineno() for name, cyvar in cython_func.locals.iteritems(): - if cyvar.type == PythonObject: - # skip unitialized Cython variables - try: - val = gdb.parse_and_eval(cyvar.cname) - except RuntimeError: - continue - else: - # Fortunately, Cython initializes all local (automatic) - # variables to NULL - if libpython.pointervalue(val) == 0: - continue - + if (cyvar.type == PythonObject and + self.is_initialized(cython_func, name)): + pystringp = executor.alloc_pystring(name) code = ''' PyDict_SetItem( @@ -1155,13 +1148,14 @@ class CyExec(CythonCommand, libpython.PyExec): (PyObject *) %d, (PyObject *) %s) ''' % (local_dict_pointer, pystringp, cyvar.cname) - - # PyDict_SetItem doesn't steal our reference - executor.decref(pystringp) - - if gdb.parse_and_eval(code) < 0: - gdb.parse_and_eval('PyErr_Print()') - raise gdb.GdbError("Unable to execute Python code.") + + try: + if gdb.parse_and_eval(code) < 0: + gdb.parse_and_eval('PyErr_Print()') + raise gdb.GdbError("Unable to execute Python code.") + finally: + # PyDict_SetItem doesn't steal our reference + executor.decref(pystringp) def _find_first_cython_or_python_frame(self): frame = gdb.selected_frame() diff --git a/Cython/Debugger/libpython.py b/Cython/Debugger/libpython.py index 45e1fde2..64f2eb2f 100644 --- a/Cython/Debugger/libpython.py +++ b/Cython/Debugger/libpython.py @@ -1929,7 +1929,7 @@ class PythonCodeExecutor(object): pointer = pointervalue(chunk) if pointer == 0: - err("No memory could be allocated in the inferior.") + raise gdb.GdbError("No memory could be allocated in the inferior.") return pointer @@ -1950,7 +1950,7 @@ class PythonCodeExecutor(object): pointer = pointervalue(result) if pointer == 0: - err("Unable to allocate Python string in " + raise gdb.GdbError("Unable to allocate Python string in " "the inferior.") return pointer @@ -1958,6 +1958,10 @@ class PythonCodeExecutor(object): def free(self, pointer): gdb.parse_and_eval("free((void *) %d)" % pointer) + def incref(self, pointer): + "Increment the reference count of a Python object in the inferior." + gdb.parse_and_eval('Py_IncRef((PyObject *) %d)' % pointer) + def decref(self, pointer): "Decrement the reference count of a Python object in the inferior." # Py_DecRef is like Py_XDECREF, but a function. So we don't have @@ -1975,7 +1979,7 @@ class PythonCodeExecutor(object): leave the debuggee in an unsafe state or terminate it alltogether. """ if '\0' in code: - err("String contains NUL byte.") + raise gdb.GdbError("String contains NUL byte.") code += '\0' -- 2.26.2