summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Behnel <stefan_ml@behnel.de>2018-10-19 21:22:09 +0200
committerStefan Behnel <stefan_ml@behnel.de>2018-10-19 21:22:09 +0200
commit7ab11ec473a604792bae454305adece55cd8ab37 (patch)
treed7370fff13af14d09b705f978a60963597e687fb
parent5dfefdadf6c20bf631ea3b6e567eede2f1ddd3d0 (diff)
downloadcython-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.py10
-rw-r--r--tests/run/line_trace.pyx75
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):