diff options
author | Stefan Behnel <stefan_ml@behnel.de> | 2018-10-19 21:22:09 +0200 |
---|---|---|
committer | Stefan Behnel <stefan_ml@behnel.de> | 2018-10-19 21:22:09 +0200 |
commit | 7ab11ec473a604792bae454305adece55cd8ab37 (patch) | |
tree | d7370fff13af14d09b705f978a60963597e687fb | |
parent | 5dfefdadf6c20bf631ea3b6e567eede2f1ddd3d0 (diff) | |
download | cython-7ab11ec473a604792bae454305adece55cd8ab37.tar.gz |
Correct the handling of line tracing errors on try-statements. Previously, this jumped to the error label of the try-statement, whereas it must target the outer error label instead.
Closes #2274.
-rw-r--r-- | Cython/Compiler/Nodes.py | 10 | ||||
-rw-r--r-- | tests/run/line_trace.pyx | 75 |
2 files changed, 77 insertions, 8 deletions
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 439a1fde6..38183481d 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -7171,6 +7171,9 @@ class TryExceptStatNode(StatNode): gil_message = "Try-except statement" def generate_execution_code(self, code): + code.mark_pos(self.pos) # before changing the error label, in case of tracing errors + code.putln("{") + old_return_label = code.return_label old_break_label = code.break_label old_continue_label = code.continue_label @@ -7186,8 +7189,6 @@ class TryExceptStatNode(StatNode): exc_save_vars = [code.funcstate.allocate_temp(py_object_type, False) for _ in range(3)] - code.mark_pos(self.pos) - code.putln("{") save_exc = code.insertion_point() code.putln( "/*try:*/ {") @@ -7520,7 +7521,9 @@ class TryFinallyStatNode(StatNode): gil_message = "Try-finally statement" def generate_execution_code(self, code): - code.mark_pos(self.pos) + code.mark_pos(self.pos) # before changing the error label, in case of tracing errors + code.putln("/*try:*/ {") + old_error_label = code.error_label old_labels = code.all_new_labels() new_labels = code.get_all_labels() @@ -7529,7 +7532,6 @@ class TryFinallyStatNode(StatNode): code.error_label = old_error_label catch_label = code.new_label() - code.putln("/*try:*/ {") was_in_try_finally = code.funcstate.in_try_finally code.funcstate.in_try_finally = 1 diff --git a/tests/run/line_trace.pyx b/tests/run/line_trace.pyx index ef5015b62..d6f9c3d0e 100644 --- a/tests/run/line_trace.pyx +++ b/tests/run/line_trace.pyx @@ -5,7 +5,7 @@ import sys -from cpython.ref cimport PyObject, Py_INCREF, Py_XINCREF, Py_XDECREF +from cpython.ref cimport PyObject, Py_INCREF, Py_XDECREF cdef extern from "frameobject.h": ctypedef struct PyFrameObject: @@ -23,7 +23,7 @@ cdef extern from *: map_trace_types = { PyTrace_CALL: 'call', - PyTrace_EXCEPTION: 'exc', + PyTrace_EXCEPTION: 'exception', PyTrace_LINE: 'line', PyTrace_RETURN: 'return', PyTrace_C_CALL: 'ccall', @@ -74,6 +74,10 @@ def _create_trace_func(trace): local_names = {} def _trace_func(frame, event, arg): + if sys.version_info < (3,) and 'line_trace' not in frame.f_code.co_filename: + # Prevent tracing into Py2 doctest functions. + return None + trace.append((map_trace_types(event, event), frame.f_lineno - frame.f_code.co_firstlineno)) lnames = frame.f_code.co_varnames @@ -85,9 +89,9 @@ def _create_trace_func(trace): # Currently, the locals dict is empty for Cython code, but not for Python code. if frame.f_code.co_name.startswith('py_'): # Change this when we start providing proper access to locals. - assert frame.f_locals + assert frame.f_locals, frame.f_code.co_name else: - assert not frame.f_locals + assert not frame.f_locals, frame.f_code.co_name return _trace_func return _trace_func @@ -154,6 +158,13 @@ cdef int cy_add_nogil(int a, int b) nogil except -1: return x # 2 +def cy_try_except(func): + try: + return func() + except KeyError as exc: + raise AttributeError(exc.args[0]) + + def run_trace(func, *args, bint with_sys=False): """ >>> def py_add(a,b): @@ -226,6 +237,62 @@ def run_trace(func, *args, bint with_sys=False): return trace +def run_trace_with_exception(func, bint with_sys=False, bint fail=False): + """ + >>> def py_return(retval=123): return retval + >>> run_trace_with_exception(py_return) + OK: 123 + [('call', 0), ('line', 1), ('line', 2), ('call', 0), ('line', 0), ('return', 0), ('return', 2)] + >>> run_trace_with_exception(py_return, with_sys=True) + OK: 123 + [('call', 0), ('line', 1), ('line', 2), ('call', 0), ('line', 0), ('return', 0), ('return', 2)] + + >>> run_trace_with_exception(py_return, fail=True) + ValueError('failing line trace!') + [('call', 0)] + + #>>> run_trace_with_exception(lambda: 123, with_sys=True, fail=True) + #ValueError('huhu') + #[('call', 0), ('line', 1), ('line', 2), ('call', 0), ('line', 0), ('return', 0), ('return', 2)] + + >>> def py_raise_exc(exc=KeyError('huhu')): raise exc + >>> run_trace_with_exception(py_raise_exc) + AttributeError('huhu') + [('call', 0), ('line', 1), ('line', 2), ('call', 0), ('line', 0), ('exception', 0), ('return', 0), ('line', 3), ('line', 4), ('return', 4)] + >>> run_trace_with_exception(py_raise_exc, with_sys=True) + AttributeError('huhu') + [('call', 0), ('line', 1), ('line', 2), ('call', 0), ('line', 0), ('exception', 0), ('return', 0), ('line', 3), ('line', 4), ('return', 4)] + >>> run_trace_with_exception(py_raise_exc, fail=True) + ValueError('failing line trace!') + [('call', 0)] + + #>>> run_trace_with_exception(raise_exc, with_sys=True, fail=True) + #ValueError('huhu') + #[('call', 0), ('line', 1), ('line', 2), ('call', 0), ('line', 0), ('exception', 0), ('return', 0), ('line', 3), ('line', 4), ('return', 4)] + """ + trace = ['cy_try_except' if fail else 'NO ERROR'] + trace_func = _create__failing_line_trace_func(trace) if fail else _create_trace_func(trace) + if with_sys: + sys.settrace(trace_func) + else: + PyEval_SetTrace(<Py_tracefunc>trace_trampoline, <PyObject*>trace_func) + try: + try: + retval = cy_try_except(func) + except ValueError as exc: + print("%s(%r)" % (type(exc).__name__, str(exc))) + except AttributeError as exc: + print("%s(%r)" % (type(exc).__name__, str(exc))) + else: + print('OK: %r' % retval) + finally: + if with_sys: + sys.settrace(None) + else: + PyEval_SetTrace(NULL, NULL) + return trace[1:] + + def fail_on_call_trace(func, *args): """ >>> def py_add(a,b): |