Repository: cython/cython

Issue:
variable '__pyx_tstate' is used uninitialized whenever 'if' condition is true
Hi! I'm facing problem with generating valid c code with enabled linetracing for code like this:
```
try:
    return LoadJsonFromString(s, len(s))
except Exception as e:
   raise ValueError(str(e))
```
cython generates:
```
  /* "library/python/json/loads.pyx":9
 *         s = s.encode('utf-8')
 * 
 *     try:             # <<<<<<<<<<<<<<
 *         return LoadJsonFromString(s, len(s))
 *     except Exception as e:
 */
  __Pyx_TraceLine(9,0,__PYX_ERR(0, 9, __pyx_L4_error))
  {
    __Pyx_PyThreadState_declare
    __Pyx_PyThreadState_assign
    __Pyx_ExceptionSave(&__pyx_t_5, &__pyx_t_6, &__pyx_t_7);
    __Pyx_XGOTREF(__pyx_t_5);
    __Pyx_XGOTREF(__pyx_t_6);
    __Pyx_XGOTREF(__pyx_t_7);
    /*try:*/ {
[...skipped code for generated LoadJsonFromString call...]
      /* "library/python/json/loads.pyx":9
 *         s = s.encode('utf-8')
 * 
 *     try:             # <<<<<<<<<<<<<<
 *         return LoadJsonFromString(s, len(s))
 *     except Exception as e:
 */
    __pyx_L4_error:;
    __Pyx_XDECREF(__pyx_t_3); __pyx_t_3 = 0;
    __Pyx_XDECREF(__pyx_t_4); __pyx_t_4 = 0;
[...skipped code for exception handling...]
    /* "library/python/json/loads.pyx":9
 *         s = s.encode('utf-8')
 * 
 *     try:             # <<<<<<<<<<<<<<
 *         return LoadJsonFromString(s, len(s))
 *     except Exception as e:
 */
    __Pyx_XGIVEREF(__pyx_t_5);
    __Pyx_XGIVEREF(__pyx_t_6);
    __Pyx_XGIVEREF(__pyx_t_7);
    __Pyx_ExceptionReset(__pyx_t_5, __pyx_t_6, __pyx_t_7);
    goto __pyx_L1_error;
    __pyx_L8_try_return:;
    __Pyx_XGIVEREF(__pyx_t_5);
    __Pyx_XGIVEREF(__pyx_t_6);
    __Pyx_XGIVEREF(__pyx_t_7);
    __Pyx_ExceptionReset(__pyx_t_5, __pyx_t_6, __pyx_t_7);
    goto __pyx_L0;
  }
```
compiler error:
```
library/python/json/loads.pyx.cpp:1393:3: error: variable '__pyx_tstate' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized]
  __Pyx_TraceLine(9,0,__PYX_ERR(0, 9, __pyx_L4_error))
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library/python/json/loads.pyx.cpp:1090:19: note: expanded from macro '__Pyx_TraceLine'
              if (unlikely(ret)) goto_error;\
                  ^~~~~~~~~~~~~
library/python/json/loads.pyx.cpp:770:23: note: expanded from macro 'unlikely'
  #define unlikely(x) __builtin_expect(!!(x), 0)
                      ^~~~~~~~~~~~~~~~~~~~~~~~~~
library/python/json/loads.pyx.cpp:1482:5: note: uninitialized use occurs here
    __Pyx_ExceptionReset(__pyx_t_5, __pyx_t_6, __pyx_t_7);
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library/python/json/loads.pyx.cpp:1119:70: note: expanded from macro '__Pyx_ExceptionReset'
#define __Pyx_ExceptionReset(type, value, tb)  __Pyx__ExceptionReset(__pyx_tstate, type, value, tb)
                                                                     ^~~~~~~~~~~~
library/python/json/loads.pyx.cpp:1393:3: note: remove the 'if' if its condition is always false
  __Pyx_TraceLine(9,0,__PYX_ERR(0, 9, __pyx_L4_error))
  ^
library/python/json/loads.pyx.cpp:1090:15: note: expanded from macro '__Pyx_TraceLine'
              if (unlikely(ret)) goto_error;\
              ^
library/python/json/loads.pyx.cpp:1395:5: note: variable '__pyx_tstate' is declared here
    __Pyx_PyThreadState_declare
    ^
library/python/json/loads.pyx.cpp:872:38: note: expanded from macro '__Pyx_PyThreadState_declare'
#define __Pyx_PyThreadState_declare  PyThreadState *__pyx_tstate;
                                     ^
```
which says that macro __Pyx_TraceLine(9,0,__PYX_ERR(0, 9, __pyx_L4_error)) expands into code that will jumps to __pyx_L4_error label if __Pyx_call_line_trace_func returns nonzero value. Jumping to __pyx_L4_error label will skip __pyx_tstate declaration:
```
    __Pyx_PyThreadState_declare
    __Pyx_PyThreadState_assign
```
which will be used later in the code expanded from __Pyx_ExceptionReset macro (___Pyx__ExceptionReset(__pyx_tstate, type, value, tb)_)


Discussion:
Hi, can you please provide a short standalone script to reproduce the bug? I cannot reproduce it. 

Thanks.
Thanks for your quick response! Will something like this be enough?
`cat lib.pyx`
```
cdef num(x):
    try:
        return x
    except AttributeError as e:
        raise ValueError(str(e))
```
`git clone https://github.com/cython/cython`
`python cython/cython.py -X linetrace=True --cplus lib.pyx -o lib.pyx.cpp`
`clang++-5.0 -Wall -Werror -I/usr/include/python2.7/ -DCYTHON_TRACE=1 -DCYTHON_TRACE_NOGIL=1 -c lib.pyx.cpp`
```
lib.pyx.cpp:1293:3: error: variable '__pyx_tstate' is used uninitialized whenever 'if' condition is true [-Werror,-Wsometimes-uninitialized]
  __Pyx_TraceLine(2,0,__PYX_ERR(0, 2, __pyx_L3_error))
  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib.pyx.cpp:1108:19: note: expanded from macro '__Pyx_TraceLine'
              if (unlikely(ret)) goto_error;\
                  ^~~~~~~~~~~~~
lib.pyx.cpp:788:23: note: expanded from macro 'unlikely'
  #define unlikely(x) __builtin_expect(!!(x), 0)
                      ^~~~~~~~~~~~~~~~~~~~~~~~~~
lib.pyx.cpp:1371:5: note: uninitialized use occurs here
    __Pyx_ExceptionReset(__pyx_t_1, __pyx_t_2, __pyx_t_3);
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
lib.pyx.cpp:1130:70: note: expanded from macro '__Pyx_ExceptionReset'
#define __Pyx_ExceptionReset(type, value, tb)  __Pyx__ExceptionReset(__pyx_tstate, type, value, tb)
                                                                     ^~~~~~~~~~~~
lib.pyx.cpp:1293:3: note: remove the 'if' if its condition is always false
  __Pyx_TraceLine(2,0,__PYX_ERR(0, 2, __pyx_L3_error))
  ^
lib.pyx.cpp:1108:15: note: expanded from macro '__Pyx_TraceLine'
              if (unlikely(ret)) goto_error;\
              ^
lib.pyx.cpp:1295:5: note: variable '__pyx_tstate' is declared here
    __Pyx_PyThreadState_declare
    ^
lib.pyx.cpp:890:38: note: expanded from macro '__Pyx_PyThreadState_declare'
#define __Pyx_PyThreadState_declare  PyThreadState *__pyx_tstate;
                                     ^
1 error generated.
```


I tried to make a `setup.py` rather than manually run the cython/cython.py and  clang.

This works.

lib.pyx
```python
cdef num(x):
    try:
        return x
    except AttributeError as e:
        raise ValueError(str(e))
```

setup.py
```python
from distutils.core import setup
from Cython.Build import cythonize
from distutils.extension import Extension

extensions = [
    Extension("lib", ["lib.pyx"], define_macros=[('CYTHON_TRACE', '1'),
                                                 ('DCYTHON_TRACE_NOGIL', '1')])
]

setup(
    name = "hello",
    ext_modules = cythonize(extensions, compiler_directives={'linetrace': True}),
)
```

Command to run:
```shell
python setup.py build_ext --inplace
```

Would this workaround suits you?

I'm in no way an expert in Cython, I'm only trying to help the maintainers. So maybe I'm missing something obvious.
Yes, your example works for me too, but it's obviously hiding real problem. It uses gcc as compiler by default and due 14 years old gcc bug (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=18501) it doesn't detect uninitialized variable use.
To reproduce problem you can run command:
```
CC=clang++-5.0 python setup.py build_ext --inplace
```

I have found similar issue - https://github.com/cython/cython/issues/2269. Pitrou didn't provide enough information, but it seems to me that gcc (at least 4.9.4) is capable to detect some cases of uninitialized variable use. It does't look like my try-except-raise case, but i believe that it's different face of the same problem and cython generates code like
```
  __Pyx_TraceLine(9,0,__PYX_ERR(0, 9, __pyx_L4_error))
  {
    __Pyx_PyThreadState_declare
    __Pyx_PyThreadState_assign
```
which may lead to jump over __Pyx_PyThreadState_declare and __Pyx_PyThreadState_assign if__Pyx_call_line_trace_func() returns nonzero value.

My workaround:
```diff
+++ b/Cython/Utility/Profile.c
@@ -193,6 +193,7 @@
   #ifdef WITH_THREAD
   #define __Pyx_TraceLine(lineno, nogil, goto_error)                                       \
   if (likely(!__Pyx_use_tracing)); else {                                                  \
+      if ((1)); else goto_error;                                                           \
       if (nogil) {                                                                         \
           if (CYTHON_TRACE_NOGIL) {                                                        \
               int ret = 0;                                                                 \
@@ -203,23 +204,27 @@
                   ret = __Pyx_call_line_trace_func(tstate, $frame_cname, lineno);          \
               }                                                                            \
               PyGILState_Release(state);                                                   \
-              if (unlikely(ret)) goto_error;                                               \
+              // It's a bug - see https://github.com/cython/cython/issues/2274             \
+              if (unlikely(ret)) { fprintf(stderr, "cython: line_trace_func returned %d\n", ret); }  \
           }                                                                                \
       } else {                                                                             \
           PyThreadState* tstate = __Pyx_PyThreadState_Current;                             \
           if (unlikely(tstate->use_tracing && tstate->c_tracefunc && $frame_cname->f_trace)) { \
               int ret = __Pyx_call_line_trace_func(tstate, $frame_cname, lineno);          \
-              if (unlikely(ret)) goto_error;                                               \
+              // It's a bug - see https://github.com/cython/cython/issues/2274             \
+              if (unlikely(ret)) { fprintf(stderr, "cython: line_trace_func returned %d\n", ret); }  \
           }                                                                                \
       }                                                                                    \
   }
   #else
   #define __Pyx_TraceLine(lineno, nogil, goto_error)                                       \
   if (likely(!__Pyx_use_tracing)); else {                                                  \
+      if ((1)); else goto_error;                                                           \
       PyThreadState* tstate = __Pyx_PyThreadState_Current;                                 \
       if (unlikely(tstate->use_tracing && tstate->c_tracefunc && $frame_cname->f_trace)) { \
           int ret = __Pyx_call_line_trace_func(tstate, $frame_cname, lineno);              \
-          if (unlikely(ret)) goto_error;                                                   \
+          // It's a bug - see https://github.com/cython/cython/issues/2274                 \
+          if (unlikely(ret)) { fprintf(stderr, "cython: line_trace_func returned %d\n", ret); } \
       }                                                                                    \
   }
   #endif
```
It looks valid for me, because i don't understand why tracing errors should affect executing code. So, if error occurs in tracing code, we just accept this fact and should move on.
Cython generated code:
```c++
  /* "lib.pyx":2
 * cdef num(x):
 *     try:             # <<<<<<<<<<<<<<
 *         return x
 *     except AttributeError as e:
 */
  __Pyx_TraceLine(2,0,__PYX_ERR(0, 2, __pyx_L3_error))
  {
    __Pyx_PyThreadState_declare
    __Pyx_PyThreadState_assign
    __Pyx_ExceptionSave(&__pyx_t_1, &__pyx_t_2, &__pyx_t_3);
    __Pyx_XGOTREF(__pyx_t_1);
    __Pyx_XGOTREF(__pyx_t_2);
    __Pyx_XGOTREF(__pyx_t_3);
    /*try:*/ {

      /* "lib.pyx":3
 * cdef num(x):
 *     try:
 *         return x             # <<<<<<<<<<<<<<
 *     except AttributeError as e:
 *         raise ValueError(str(e))
 */
      __Pyx_TraceLine(3,0,__PYX_ERR(0, 3, __pyx_L3_error))
      __Pyx_XDECREF(__pyx_r);
      __Pyx_INCREF(__pyx_v_x);
      __pyx_r = __pyx_v_x;
      goto __pyx_L7_try_return;

      /* "lib.pyx":2
 * cdef num(x):
 *     try:             # <<<<<<<<<<<<<<
 *         return x
 *     except AttributeError as e:
 */
    }
    __pyx_L3_error:;

    /* "lib.pyx":4
 *     try:
 *         return x
 *     except AttributeError as e:             # <<<<<<<<<<<<<<
 *         raise ValueError(str(e))
 *
 */
    __Pyx_TraceLine(4,0,__PYX_ERR(0, 4, __pyx_L5_except_error))
    __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(__pyx_builtin_AttributeError);

```
Cython generated code with expanded macro (with my 'expanded' comments):
```c++
// expanded __Pyx_TraceLine(2,0,__PYX_ERR(0, 2, __pyx_L3_error))
  if (__builtin_expect(!!(!__Pyx_use_tracing), 1)); else { if (0) { if (0) { int ret = 0; PyThreadState *tstate; PyGILState_STATE state = PyGILState_Ensure(); tstate = _PyThreadState_Current; if (__builtin_expect(!!(tstate->use_tracing &&
 tstate->c_tracefunc && __pyx_frame->f_trace), 0)) { ret = __Pyx_call_line_trace_func(tstate, __pyx_frame, 2); } PyGILState_Release(state); if (__builtin_expect(!!(ret), 0)) { __pyx_filename = __pyx_f[0]; __pyx_lineno = 2; __pyx_clineno =
 1301; goto __pyx_L3_error; }; } } else { PyThreadState* tstate = _PyThreadState_Current; if (__builtin_expect(!!(tstate->use_tracing && tstate->c_tracefunc && __pyx_frame->f_trace), 0)) { int ret = __Pyx_call_line_trace_func(tstate, __py
x_frame, 2); if (__builtin_expect(!!(ret), 0)) { __pyx_filename = __pyx_f[0]; __pyx_lineno = 2; __pyx_clineno = 1301; goto __pyx_L3_error; }; } } }
  {
// expanded  __Pyx_PyThreadState_declare
    PyThreadState *__pyx_tstate;
// expanded __Pyx_PyThreadState_assign
    __pyx_tstate = _PyThreadState_Current;
// expanded  __Pyx_ExceptionSave(&__pyx_t_1, &__pyx_t_2, &__pyx_t_3);
    __Pyx__ExceptionSave(__pyx_tstate, &__pyx_t_1, &__pyx_t_2, &__pyx_t_3);
                            ;
                            ;
                            ;
             {
# 1318 "lib.c"
// expanded  __Pyx_TraceLine(3,0,__PYX_ERR(0, 3, __pyx_L3_error))
      if (__builtin_expect(!!(!__Pyx_use_tracing), 1)); else { if (0) { if (0) { int ret = 0; PyThreadState *tstate; PyGILState_STATE state = PyGILState_Ensure(); tstate = _PyThreadState_Current; if (__builtin_expect(!!(tstate->use_tracing && tstate->c_tracefunc && __pyx_frame->f_trace), 0)) { ret = __Pyx_call_line_trace_func(tstate, __pyx_frame, 3); } PyGILState_Release(state); if (__builtin_expect(!!(ret), 0)) { __pyx_filename = __pyx_f[0]; __pyx_lineno = 3; __pyx_clineno = 1318; goto __pyx_L3_error; }; } } else { PyThreadState* tstate = _PyThreadState_Current; if (__builtin_expect(!!(tstate->use_tracing && tstate->c_tracefunc && __pyx_frame->f_trace), 0)) { int ret = __Pyx_call_line_trace_func(tstate, __pyx_frame, 3); if (__builtin_expect(!!(ret), 0)) { __pyx_filename = __pyx_f[0]; __pyx_lineno = 3; __pyx_clineno = 1318; goto __pyx_L3_error; }; } } }
      do { if ((__pyx_r) == ((void*)0)) ; else do { if ( --((PyObject*)(__pyx_r))->ob_refcnt != 0) ; else ( (*(((PyObject*)((PyObject *)(__pyx_r)))->ob_type)->tp_dealloc)((PyObject *)((PyObject *)(__pyx_r)))); } while (0); } while (0);
      ( ((PyObject*)(__pyx_v_x))->ob_refcnt++);
      __pyx_r = __pyx_v_x;
      goto __pyx_L7_try_return;
    }
    __pyx_L3_error:;
# 1340 "lib.c"
// expanded __Pyx_TraceLine(4,0,__PYX_ERR(0, 4, __pyx_L5_except_error))
    if (__builtin_expect(!!(!__Pyx_use_tracing), 1)); else { if (0) { if (0) { int ret = 0; PyThreadState *tstate; PyGILState_STATE state = PyGILState_Ensure(); tstate = _PyThreadState_Current; if (__builtin_expect(!!(tstate->use_tracing && tstate->c_tracefunc && __pyx_frame->f_trace), 0)) { ret = __Pyx_call_line_trace_func(tstate, __pyx_frame, 4); } PyGILState_Release(state); if (__builtin_expect(!!(ret), 0)) { __pyx_filename = __pyx_f[0]; __pyx_lineno = 4; __pyx_clineno = 1340; goto __pyx_L5_except_error; }; } } else { PyThreadState* tstate = _PyThreadState_Current; if (__builtin_expect(!!(tstate->use_tracing && tstate->c_tracefunc && __pyx_frame->f_trace), 0)) { int ret = __Pyx_call_line_trace_func(tstate, __pyx_frame, 4); if (__builtin_expect(!!(ret), 0)) { __pyx_filename = __pyx_f[0]; __pyx_lineno = 4; __pyx_clineno = 1340; goto __pyx_L5_except_error; }; } } }
// expanded __pyx_t_4 = __Pyx_PyErr_ExceptionMatches(__pyx_builtin_AttributeError);
    __pyx_t_4 = __Pyx_PyErr_ExceptionMatchesInState(__pyx_tstate, __pyx_builtin_AttributeError);
    if (__pyx_t_4) {
      __Pyx_AddTraceback("lib.num", __pyx_clineno, __pyx_lineno, __pyx_filename);
      if (__Pyx__GetException(__pyx_tstate, &__pyx_t_5, &__pyx_t_6, &__pyx_t_7) < 0) { __pyx_filename = __pyx_f[0]; __pyx_lineno = 4; __pyx_clineno = 1344; goto __pyx_L5_except_error; }
                             ;
                             ;
                             ;
```
As you can see at the first line we can perform ```goto __pyx_L3_error``` and skip ```PyThreadState *__pyx_tstate; __pyx_tstate = _PyThreadState_Current;```, which is the cause of problem, because we will use __pyx_tstate later
Thanks for all those details. 

I don't have enough knowledge to do a PR, but hopefully someone will figure out the error and fix it with all the information you provided.
Some additional info, which might be useful: it looks like pure UB and behaviour may vary from compiler to compiler. A minimalist example:
```c++
#include <stdio.h>

int main(int c, char** v)
{
  if (c > 1) {
      fprintf(stderr, "goto __pyx_L3_error\n");
      goto __pyx_L3_error;
  }
  {
      fprintf(stderr, "declare *p\n");
      int *__pyx_tstate;
      __pyx_tstate = &c;
__pyx_L3_error:;
      fprintf(stderr, "val: %d\n", *__pyx_tstate);
  }
  return 0;
}
```
```bash
~> g++-7 -O2 1.cpp; ./a.out 2;
goto __pyx_L3_error
val: 2

~> g++-7 1.cpp; ./a.out 2;    
goto __pyx_L3_error
Segmentation fault (core dumped)

~> clang++-5.0 -O2 1.cpp; ./a.out 2; 
goto __pyx_L3_error
val: 2

~> clang++-5.0 1.cpp; ./a.out 2;
goto __pyx_L3_error
val: -1991643855
```
I see this with clang 6.0.1 as well.

Resolving patch:
diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py
--- a/Cython/Compiler/Nodes.py
+++ b/Cython/Compiler/Nodes.py
@@ -7171,6 +7171,9 @@ def analyse_expressions(self, env):
     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 @@ def generate_execution_code(self, code):
 
         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 @@ def analyse_expressions(self, env):
     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 @@ def generate_execution_code(self, code):
             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
 


Test patch:
diff --git a/tests/run/line_trace.pyx b/tests/run/line_trace.pyx
--- 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):