Commit 3e601053 authored by Stefan Behnel's avatar Stefan Behnel

propagate exceptions in call/line trace function calls

parent 408fc116
...@@ -1415,9 +1415,8 @@ class CCodeWriter(object): ...@@ -1415,9 +1415,8 @@ class CCodeWriter(object):
def __init__(self, create_from=None, buffer=None, copy_formatting=False, emit_linenums=None, c_line_in_traceback=True): def __init__(self, create_from=None, buffer=None, copy_formatting=False, emit_linenums=None, c_line_in_traceback=True):
if buffer is None: buffer = StringIOTree() if buffer is None: buffer = StringIOTree()
self.buffer = buffer self.buffer = buffer
self.marker = None self.last_pos = None
self.last_marker_line = 0 self.last_marked_pos = None
self.source_desc = ""
self.pyclass_stack = [] self.pyclass_stack = []
self.funcstate = None self.funcstate = None
...@@ -1434,6 +1433,8 @@ class CCodeWriter(object): ...@@ -1434,6 +1433,8 @@ class CCodeWriter(object):
self.level = create_from.level self.level = create_from.level
self.bol = create_from.bol self.bol = create_from.bol
self.call_level = create_from.call_level self.call_level = create_from.call_level
self.last_pos = create_from.last_pos
self.last_marked_pos = create_from.last_marked_pos
if emit_linenums is None and self.globalstate: if emit_linenums is None and self.globalstate:
self.emit_linenums = self.globalstate.emit_linenums self.emit_linenums = self.globalstate.emit_linenums
...@@ -1457,11 +1458,7 @@ class CCodeWriter(object): ...@@ -1457,11 +1458,7 @@ class CCodeWriter(object):
def write(self, s): def write(self, s):
# also put invalid markers (lineno 0), to indicate that those lines # also put invalid markers (lineno 0), to indicate that those lines
# have no Cython source code correspondence # have no Cython source code correspondence
if self.marker is None: cython_lineno = self.last_marked_pos[1] if self.last_marked_pos else 0
cython_lineno = self.last_marker_line
else:
cython_lineno = self.marker[0]
self.buffer.markers.extend([cython_lineno] * s.count('\n')) self.buffer.markers.extend([cython_lineno] * s.count('\n'))
self.buffer.write(s) self.buffer.write(s)
...@@ -1553,11 +1550,11 @@ class CCodeWriter(object): ...@@ -1553,11 +1550,11 @@ class CCodeWriter(object):
# code generation # code generation
def putln(self, code="", safe=False): def putln(self, code="", safe=False):
if self.marker and self.bol: if self.last_pos and self.bol:
self.emit_marker() self.emit_marker()
if self.emit_linenums and self.last_marker_line != 0: if self.emit_linenums and self.last_marked_pos:
self.write('\n#line %s "%s"\n' % (self.last_marker_line, self.source_desc)) source_desc, line, _ = self.last_marked_pos
self.write('\n#line %s "%s"\n' % (line, source_desc.get_escaped_description()))
if code: if code:
if safe: if safe:
self.put_safe(code) self.put_safe(code)
...@@ -1566,17 +1563,32 @@ class CCodeWriter(object): ...@@ -1566,17 +1563,32 @@ class CCodeWriter(object):
self.write("\n") self.write("\n")
self.bol = 1 self.bol = 1
def mark_pos(self, pos):
if pos is None:
return
if self.last_marked_pos and self.last_marked_pos[:2] == pos[:2]:
return
self.last_pos = pos
def emit_marker(self): def emit_marker(self):
pos = self.last_marked_pos = self.last_pos
self.last_pos = None
self.write("\n") self.write("\n")
self.indent() self.indent()
self.write("/* %s */\n" % self.marker[1]) self.write("/* %s */\n" % self._build_marker(pos))
if (self.funcstate and self.funcstate.can_trace if self.funcstate and self.funcstate.can_trace and self.globalstate.directives['linetrace']:
and self.globalstate.directives['linetrace']):
self.indent() self.indent()
self.write('__Pyx_TraceLine(%d,%d)\n' % ( self.write('__Pyx_TraceLine(%d,%d,%s)\n' % (
self.marker[0], not self.funcstate.gil_owned)) pos[1], not self.funcstate.gil_owned, self.error_goto(pos)))
self.last_marker_line = self.marker[0]
self.marker = None def _build_marker(self, pos):
source_desc, line, col = pos
assert isinstance(source_desc, SourceDescriptor)
contents = self.globalstate.commented_file_contents(source_desc)
lines = contents[max(0, line-3):line] # line numbers start at 1
lines[-1] += u' # <<<<<<<<<<<<<<'
lines += contents[line:line+2]
return u'"%s":%d\n%s\n' % (source_desc.get_escaped_description(), line, u'\n'.join(lines))
def put_safe(self, code): def put_safe(self, code):
# put code, but ignore {} # put code, but ignore {}
...@@ -1653,24 +1665,6 @@ class CCodeWriter(object): ...@@ -1653,24 +1665,6 @@ class CCodeWriter(object):
def get_py_version_hex(self, pyversion): def get_py_version_hex(self, pyversion):
return "0x%02X%02X%02X%02X" % (tuple(pyversion) + (0,0,0,0))[:4] return "0x%02X%02X%02X%02X" % (tuple(pyversion) + (0,0,0,0))[:4]
def mark_pos(self, pos):
if pos is None:
return
source_desc, line, col = pos
if self.last_marker_line == line:
return
assert isinstance(source_desc, SourceDescriptor)
contents = self.globalstate.commented_file_contents(source_desc)
lines = contents[max(0, line-3):line] # line numbers start at 1
lines[-1] += u' # <<<<<<<<<<<<<<'
lines += contents[line:line+2]
marker = u'"%s":%d\n%s\n' % (
source_desc.get_escaped_description(), line, u'\n'.join(lines))
self.marker = (line, marker)
if self.emit_linenums:
self.source_desc = source_desc.get_escaped_description()
def put_label(self, lbl): def put_label(self, lbl):
if lbl in self.funcstate.labels_used: if lbl in self.funcstate.labels_used:
self.putln("%s:;" % lbl) self.putln("%s:;" % lbl)
...@@ -2099,8 +2093,8 @@ class CCodeWriter(object): ...@@ -2099,8 +2093,8 @@ class CCodeWriter(object):
self.putln('__Pyx_TraceDeclarations(%s, %d)' % (codeobj or 'NULL', nogil)) self.putln('__Pyx_TraceDeclarations(%s, %d)' % (codeobj or 'NULL', nogil))
def put_trace_call(self, name, pos, nogil=False): def put_trace_call(self, name, pos, nogil=False):
self.putln('__Pyx_TraceCall("%s", %s[%s], %s, %d);' % ( self.putln('__Pyx_TraceCall("%s", %s[%s], %s, %d, %s);' % (
name, Naming.filetable_cname, self.lookup_filename(pos[0]), pos[1], nogil)) name, Naming.filetable_cname, self.lookup_filename(pos[0]), pos[1], nogil, self.error_goto(pos)))
def put_trace_exception(self): def put_trace_exception(self):
self.putln("__Pyx_TraceException();") self.putln("__Pyx_TraceException();")
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
if (codeobj) $frame_code_cname = (PyCodeObject*) codeobj; if (codeobj) $frame_code_cname = (PyCodeObject*) codeobj;
#ifdef WITH_THREAD #ifdef WITH_THREAD
#define __Pyx_TraceCall(funcname, srcfile, firstlineno, nogil) \ #define __Pyx_TraceCall(funcname, srcfile, firstlineno, nogil, goto_error) \
if (nogil) { \ if (nogil) { \
if (CYTHON_TRACE_NOGIL) { \ if (CYTHON_TRACE_NOGIL) { \
PyThreadState *tstate; \ PyThreadState *tstate; \
...@@ -60,20 +60,23 @@ ...@@ -60,20 +60,23 @@
__Pyx_use_tracing = __Pyx_TraceSetupAndCall(&$frame_code_cname, &$frame_cname, funcname, srcfile, firstlineno); \ __Pyx_use_tracing = __Pyx_TraceSetupAndCall(&$frame_code_cname, &$frame_cname, funcname, srcfile, firstlineno); \
} \ } \
PyGILState_Release(state); \ PyGILState_Release(state); \
if (unlikely(__Pyx_use_tracing < 0)) goto_error; \
} \ } \
} else { \ } else { \
PyThreadState* tstate = PyThreadState_GET(); \ PyThreadState* tstate = PyThreadState_GET(); \
if (unlikely(tstate->use_tracing) && !tstate->tracing && \ if (unlikely(tstate->use_tracing) && !tstate->tracing && \
(tstate->c_profilefunc || (CYTHON_TRACE && tstate->c_tracefunc))) { \ (tstate->c_profilefunc || (CYTHON_TRACE && tstate->c_tracefunc))) { \
__Pyx_use_tracing = __Pyx_TraceSetupAndCall(&$frame_code_cname, &$frame_cname, funcname, srcfile, firstlineno); \ __Pyx_use_tracing = __Pyx_TraceSetupAndCall(&$frame_code_cname, &$frame_cname, funcname, srcfile, firstlineno); \
if (unlikely(__Pyx_use_tracing < 0)) goto_error; \
} \ } \
} }
#else #else
#define __Pyx_TraceCall(funcname, srcfile, firstlineno, nogil) \ #define __Pyx_TraceCall(funcname, srcfile, firstlineno, nogil, goto_error) \
{ PyThreadState* tstate = PyThreadState_GET(); \ { PyThreadState* tstate = PyThreadState_GET(); \
if (unlikely(tstate->use_tracing) && !tstate->tracing && \ if (unlikely(tstate->use_tracing) && !tstate->tracing && \
(tstate->c_profilefunc || (CYTHON_TRACE && tstate->c_tracefunc))) { \ (tstate->c_profilefunc || (CYTHON_TRACE && tstate->c_tracefunc))) { \
__Pyx_use_tracing = __Pyx_TraceSetupAndCall(&$frame_code_cname, &$frame_cname, funcname, srcfile, firstlineno); \ __Pyx_use_tracing = __Pyx_TraceSetupAndCall(&$frame_code_cname, &$frame_cname, funcname, srcfile, firstlineno); \
if (unlikely(__Pyx_use_tracing < 0)) goto_error; \
} \ } \
} }
#endif #endif
...@@ -150,58 +153,71 @@ ...@@ -150,58 +153,71 @@
#else #else
#define __Pyx_TraceDeclarations(codeobj, nogil) #define __Pyx_TraceDeclarations(codeobj, nogil)
#define __Pyx_TraceCall(funcname, srcfile, firstlineno, nogil) // mark error label as used to avoid compiler warnings
#define __Pyx_TraceCall(funcname, srcfile, firstlineno, nogil, goto_error) if (1); else goto_error;
#define __Pyx_TraceException() #define __Pyx_TraceException()
#define __Pyx_TraceReturn(result, nogil) #define __Pyx_TraceReturn(result, nogil)
#endif /* CYTHON_PROFILE */ #endif /* CYTHON_PROFILE */
#if CYTHON_TRACE #if CYTHON_TRACE
// FIXME: we should eventually propagate trace errors instead of just swallowing them
// see call_trace_protected() in CPython's ceval.c // see call_trace_protected() in CPython's ceval.c
static void __Pyx_call_line_trace_func(PyThreadState *tstate, PyFrameObject *frame, int lineno) { static int __Pyx_call_line_trace_func(PyThreadState *tstate, PyFrameObject *frame, int lineno) {
int ret;
PyObject *type, *value, *traceback; PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback); PyErr_Fetch(&type, &value, &traceback);
frame->f_lineno = lineno; frame->f_lineno = lineno;
tstate->tracing++; tstate->tracing++;
tstate->use_tracing = 0; tstate->use_tracing = 0;
tstate->c_tracefunc(tstate->c_traceobj, frame, PyTrace_LINE, NULL); ret = tstate->c_tracefunc(tstate->c_traceobj, frame, PyTrace_LINE, NULL);
tstate->use_tracing = 1; tstate->use_tracing = 1;
tstate->tracing--; tstate->tracing--;
if (likely(!ret)) {
PyErr_Restore(type, value, traceback); PyErr_Restore(type, value, traceback);
} else {
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
}
return ret;
} }
#ifdef WITH_THREAD #ifdef WITH_THREAD
#define __Pyx_TraceLine(lineno, nogil) \ #define __Pyx_TraceLine(lineno, nogil, goto_error) \
if (likely(!__Pyx_use_tracing)); else { \ if (likely(!__Pyx_use_tracing)); else { \
if (nogil) { \ if (nogil) { \
if (CYTHON_TRACE_NOGIL) { \ if (CYTHON_TRACE_NOGIL) { \
int ret = 0; \
PyThreadState *tstate; \ PyThreadState *tstate; \
PyGILState_STATE state = PyGILState_Ensure(); \ PyGILState_STATE state = PyGILState_Ensure(); \
tstate = PyThreadState_GET(); \ tstate = PyThreadState_GET(); \
if (unlikely(tstate->use_tracing && tstate->c_tracefunc)) { \ if (unlikely(tstate->use_tracing && tstate->c_tracefunc)) { \
__Pyx_call_line_trace_func(tstate, $frame_cname, lineno); \ ret = __Pyx_call_line_trace_func(tstate, $frame_cname, lineno); \
} \ } \
PyGILState_Release(state); \ PyGILState_Release(state); \
if (unlikely(ret)) goto_error; \
} \ } \
} else { \ } else { \
PyThreadState* tstate = PyThreadState_GET(); \ PyThreadState* tstate = PyThreadState_GET(); \
if (unlikely(tstate->use_tracing && tstate->c_tracefunc)) { \ if (unlikely(tstate->use_tracing && tstate->c_tracefunc)) { \
__Pyx_call_line_trace_func(tstate, $frame_cname, lineno); \ int ret = __Pyx_call_line_trace_func(tstate, $frame_cname, lineno); \
if (unlikely(ret)) goto_error; \
} \ } \
} \ } \
} }
#else #else
#define __Pyx_TraceLine(lineno, nogil) \ #define __Pyx_TraceLine(lineno, nogil, goto_error) \
if (likely(!__Pyx_use_tracing)); else { \ if (likely(!__Pyx_use_tracing)); else { \
PyThreadState* tstate = PyThreadState_GET(); \ PyThreadState* tstate = PyThreadState_GET(); \
if (unlikely(tstate->use_tracing && tstate->c_tracefunc)) { \ if (unlikely(tstate->use_tracing && tstate->c_tracefunc)) { \
__Pyx_call_line_trace_func(tstate, $frame_cname, lineno); \ int ret = __Pyx_call_line_trace_func(tstate, $frame_cname, lineno); \
if (unlikely(ret)) goto_error; \
} \ } \
} }
#endif #endif
#else #else
#define __Pyx_TraceLine(lineno, nogil) // mark error label as used to avoid compiler warnings
#define __Pyx_TraceLine(lineno, nogil, goto_error) if (1); else goto_error;
#endif #endif
/////////////// Profile /////////////// /////////////// Profile ///////////////
...@@ -255,12 +271,13 @@ static int __Pyx_TraceSetupAndCall(PyCodeObject** code, ...@@ -255,12 +271,13 @@ static int __Pyx_TraceSetupAndCall(PyCodeObject** code,
tstate->tracing--; tstate->tracing--;
if (retval) { if (retval) {
PyErr_Restore(type, value, traceback); PyErr_Restore(type, value, traceback);
return tstate->use_tracing && retval;
} else { } else {
Py_XDECREF(type); Py_XDECREF(type);
Py_XDECREF(value); Py_XDECREF(value);
Py_XDECREF(traceback); Py_XDECREF(traceback);
return -1;
} }
return tstate->use_tracing && retval;
} }
static PyCodeObject *__Pyx_createFrameCodeObject(const char *funcname, const char *srcfile, int firstlineno) { static PyCodeObject *__Pyx_createFrameCodeObject(const char *funcname, const char *srcfile, int firstlineno) {
......
...@@ -31,6 +31,21 @@ cdef int _trace_func(PyObject* _traceobj, PyFrameObject* _frame, int what, PyObj ...@@ -31,6 +31,21 @@ cdef int _trace_func(PyObject* _traceobj, PyFrameObject* _frame, int what, PyObj
return 0 return 0
cdef int _failing_call_trace_func(PyObject* _traceobj, PyFrameObject* _frame, int what, PyObject* arg) except -1:
if what == PyTrace_CALL:
raise ValueError("failing call trace!")
return _trace_func(_traceobj, _frame, what, arg)
cdef int _failing_line_trace_func(PyObject* _traceobj, PyFrameObject* _frame, int what, PyObject* arg) except -1:
if what == PyTrace_LINE and _traceobj:
trace = <object>_traceobj
if len(trace) == 1:
# right after call with empty trace list => fail!
raise ValueError("failing line trace!")
return _trace_func(_traceobj, _frame, what, arg)
def cy_add(a,b): def cy_add(a,b):
x = a + b x = a + b
return x return x
...@@ -63,3 +78,47 @@ def run_trace(func, *args): ...@@ -63,3 +78,47 @@ def run_trace(func, *args):
finally: finally:
PyEval_SetTrace(NULL, None) PyEval_SetTrace(NULL, None)
return trace return trace
def fail_on_call_trace(func, *args):
"""
>>> def py_add(a,b):
... x = a+b
... return x
>>> fail_on_call_trace(py_add, 1, 2)
Traceback (most recent call last):
ValueError: failing call trace!
"""
trace = []
PyEval_SetTrace(<Py_tracefunc>_failing_call_trace_func, trace)
try:
func(*args)
finally:
PyEval_SetTrace(NULL, None)
assert not trace
def fail_on_line_trace(bint fail):
"""
>>> fail_on_line_trace(False)
['STARTING', ('call', 0), ('line', 1), ('line', 2), ('return', 2), ('call', 0), ('line', 1), ('line', 2), ('return', 2)]
>>> fail_on_line_trace(True)
Traceback (most recent call last):
ValueError: failing line trace!
"""
cdef int x = 1
trace = ['STARTING']
PyEval_SetTrace(<Py_tracefunc>_failing_line_trace_func, trace)
try:
x += 1
cy_add(1, 2)
x += 1
if fail:
del trace[:] # trigger error
x += 1
cy_add(3, 4)
x += 1
finally:
PyEval_SetTrace(NULL, None)
assert x == 5
return trace
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment