[SATYR PATCH] Handle special files and frames in python stacktraces

Richard Marko rmarko at redhat.com
Wed May 22 14:22:59 UTC 2013


Awk!

On 05/21/2013 06:37 PM, Martin Milata wrote:
> Note: special_function and special_file sound quite weird but I wasn't able to
> come up with better alternative (unnamed_function?). Let me know if you can
> think of something better.
>
> --->8--->8--->8--->8--->8--->8--->8--->8--->8--->8--->8--->8---
>
> Python code can be executed from stdin or from string (in case of eval
> and similar functions), and python stack frames need not come from named
> functions but can also be contained in lambdas and module-level code.
>
> This patch:
> - Adds special_file and special_function booleans to the python frame
>   structure and its python object counterpart. These are true when the
>   above mentioned conditions occur.
> - Changes the JSON format so that the python frame contains e.g.
>   special_function: "module" key-value pair for module level functions
>   instead of function_name: "xyz" which is used for ordinary functions.
>   Special files are treated in similar way.
>
> Closes #74.
>
> Signed-off-by: Martin Milata <mmilata at redhat.com>
> ---
>  lib/python_frame.c                 | 56 +++++++++++++++++++++++++++-----------
>  lib/python_frame.h                 |  4 ++-
>  python/py_python_frame.c           | 16 +++++++----
>  tests/python/python.py             | 34 ++++++++++++++++++++++-
>  tests/python_stacktraces/python-03 |  9 ++++++
>  5 files changed, 95 insertions(+), 24 deletions(-)
>  create mode 100644 tests/python_stacktraces/python-03
>
> diff --git a/lib/python_frame.c b/lib/python_frame.c
> index 11f8ddb..24a07c2 100644
> --- a/lib/python_frame.c
> +++ b/lib/python_frame.c
> @@ -102,11 +102,17 @@ sr_python_frame_cmp(struct sr_python_frame *frame1,
>      if (file_line != 0)
>          return file_line;
>  
> -    /* is_module */
> -    int is_module = frame1->is_module - frame2->is_module;
> +    /* special_function */
> +    int special_function = frame1->special_function - frame2->special_function;
>  
> -    if (is_module != 0)
> -        return is_module;
> +    if (special_function != 0)
> +        return special_function;
> +
> +    /* special_file */
> +    int special_file = frame1->special_file - frame2->special_file;
> +
> +    if (special_file != 0)
> +        return special_file;
>  
>      /* line_contents */
>      int line_contents = sr_strcmp0(frame1->line_contents,
> @@ -170,6 +176,17 @@ sr_python_frame_parse(const char **input,
>          return NULL;
>      }
>  
> +    if (strlen(frame->file_name) > 0 &&
> +        frame->file_name[0] == '<' &&
> +        frame->file_name[strlen(frame->file_name)-1] == '>')
> +    {
> +        frame->special_file = true;
> +        frame->file_name[strlen(frame->file_name)-1] = '\0';
> +        char *inside = sr_strdup(frame->file_name + 1);
> +        free(frame->file_name);
> +        frame->file_name = inside;
> +    }
> +
>      location->column += strlen(frame->file_name);
>  
>      if (0 == sr_skip_string(&local_input, "\", line "))
> @@ -210,11 +227,15 @@ sr_python_frame_parse(const char **input,
>  
>      location->column += strlen(frame->function_name);
>  
> -    if (0 == strcmp(frame->function_name, "<module>"))
> +    if (strlen(frame->function_name) > 0 &&
> +        frame->function_name[0] == '<' &&
> +        frame->function_name[strlen(frame->function_name)-1] == '>')
>      {
> -        frame->is_module = true;
> +        frame->special_function = true;
> +        frame->function_name[strlen(frame->function_name)-1] = '\0';
> +        char *inside = sr_strdup(frame->function_name + 1);
>          free(frame->function_name);
> -        frame->function_name = NULL;
> +        frame->function_name = inside;
>      }
>  
>      sr_skip_char(&local_input, '\n');
> @@ -237,10 +258,14 @@ sr_python_frame_to_json(struct sr_python_frame *frame)
>  {
>      struct sr_strbuf *strbuf = sr_strbuf_new();
>  
> -    /* Source file name. */
> +    /* Source file name / special file. */
>      if (frame->file_name)
>      {
> -        sr_strbuf_append_str(strbuf, ",   \"file_name\": ");
> +        if (frame->special_file)
> +            sr_strbuf_append_str(strbuf, ",   \"special_file\": ");
> +        else
> +            sr_strbuf_append_str(strbuf, ",   \"file_name\": ");
> +
>          sr_json_append_escaped(strbuf, frame->file_name);
>          sr_strbuf_append_str(strbuf, "\n");
>      }
> @@ -253,15 +278,14 @@ sr_python_frame_to_json(struct sr_python_frame *frame)
>                                frame->file_line);
>      }
>  
> -    /* Is module. */
> -    sr_strbuf_append_strf(strbuf,
> -                          ",   \"is_module\": %s\n",
> -                          frame->is_module ? "true" : "false");
> -
> -    /* Function name. */
> +    /* Function name / special function. */
>      if (frame->function_name)
>      {
> -        sr_strbuf_append_str(strbuf, ",   \"function_name\": ");
> +        if (frame->special_function)
> +            sr_strbuf_append_str(strbuf, ",   \"special_function\": ");
> +        else
> +            sr_strbuf_append_str(strbuf, ",   \"function_name\": ");
> +
>          sr_json_append_escaped(strbuf, frame->function_name);
>          sr_strbuf_append_str(strbuf, "\n");
>      }
> diff --git a/lib/python_frame.h b/lib/python_frame.h
> index 7ca7166..0466f85 100644
> --- a/lib/python_frame.h
> +++ b/lib/python_frame.h
> @@ -37,11 +37,13 @@ struct sr_location;
>  
>  struct sr_python_frame
>  {
> +    bool special_file;
> +
>      char *file_name;
>  
>      uint32_t file_line;
>  
> -    bool is_module;
> +    bool special_function;
>  
>      char *function_name;
>  
> diff --git a/python/py_python_frame.c b/python/py_python_frame.c
> index e654b64..7da7eb9 100644
> --- a/python/py_python_frame.c
> +++ b/python/py_python_frame.c
> @@ -50,7 +50,8 @@ frame_methods[] =
>  GSOFF_START
>  GSOFF_MEMBER(file_name),
>  GSOFF_MEMBER(file_line),
> -GSOFF_MEMBER(is_module),
> +GSOFF_MEMBER(special_file),
> +GSOFF_MEMBER(special_function),
>  GSOFF_MEMBER(function_name),
>  GSOFF_MEMBER(line_contents)
>  GSOFF_END
> @@ -58,11 +59,12 @@ GSOFF_END
>  static PyGetSetDef
>  frame_getset[] =
>  {
> -    SR_ATTRIBUTE_STRING(file_name,     "Source file name (string)"                       ),
> -    SR_ATTRIBUTE_UINT32(file_line,     "Source line number (positive integer)"           ),
> -    SR_ATTRIBUTE_BOOL  (is_module,     "True if the frame is from the main module (bool)"),
> -    SR_ATTRIBUTE_STRING(function_name, "Function name (string)"                          ),
> -    SR_ATTRIBUTE_STRING(line_contents, "Remaining line contents (string)"                ),
> +    SR_ATTRIBUTE_STRING(file_name,        "Source file name (string)"                                                    ),
> +    SR_ATTRIBUTE_BOOL  (special_file,     "True if the frame is not a real file, like stdin or eval'd string (bool)"     ),
> +    SR_ATTRIBUTE_UINT32(file_line,        "Source line number (positive integer)"                                        ),
> +    SR_ATTRIBUTE_STRING(function_name,    "Function name (string)"                                                       ),
> +    SR_ATTRIBUTE_BOOL  (special_function, "True if the frame doesn't belong to a named function, e.g. lambda or a module"),
> +    SR_ATTRIBUTE_STRING(line_contents,    "Remaining line contents (string)"                                             ),
>      { NULL },
>  };
>  
> @@ -175,8 +177,10 @@ sr_py_python_frame_str(PyObject *self)
>      if (this->frame->function_name)
>        sr_strbuf_append_strf(buf, ", in %s", this->frame->function_name);
>  
> +#if 0
>      if (this->frame->is_module)
>        sr_strbuf_append_str(buf, ", in <module>");
> +#endif
>  
>      if (this->frame->line_contents)
>        sr_strbuf_append_strf(buf, "\n    %s", this->frame->line_contents);
> diff --git a/tests/python/python.py b/tests/python/python.py
> index 1018b63..065cdb9 100755
> --- a/tests/python/python.py
> +++ b/tests/python/python.py
> @@ -52,6 +52,37 @@ class TestPythonStacktrace(BindingsTestCase):
>      def test_getset(self):
>          self.assertGetSetCorrect(self.trace, 'exception_name', 'AttributeError', 'WhateverException')
>  
> +    def test_special_files_and_frames(self):
> +        path2 = str(path)
> +        path2 = path2[0:-1] + '3'
> +        with open(path2, 'r') as f:
> +            trace = f.read()
> +        trace = satyr.PythonStacktrace(trace)
> +
> +        f = trace.frames[0]
> +        self.assertEqual(f.file_name, 'string')
> +        self.assertEqual(f.function_name, 'module')
> +        self.assertTrue(f.special_file)
> +        self.assertTrue(f.special_function)
> +
> +        f = trace.frames[1]
> +        self.assertEqual(f.file_name, './test.py')
> +        self.assertEqual(f.function_name, 'lambda')
> +        self.assertFalse(f.special_file)
> +        self.assertTrue(f.special_function)
> +
> +        f = trace.frames[2]
> +        self.assertEqual(f.file_name, './test.py')
> +        self.assertEqual(f.function_name, 'f')
> +        self.assertFalse(f.special_file)
> +        self.assertFalse(f.special_function)
> +
> +        f = trace.frames[3]
> +        self.assertEqual(f.file_name, 'stdin')
> +        self.assertEqual(f.function_name, 'module')
> +        self.assertTrue(f.special_file)
> +        self.assertTrue(f.special_function)
> +
>  class TestPythonFrame(BindingsTestCase):
>      def setUp(self):
>          self.frame = satyr.PythonStacktrace(contents).frames[-1]
> @@ -84,7 +115,8 @@ class TestPythonFrame(BindingsTestCase):
>      def test_getset(self):
>          self.assertGetSetCorrect(self.frame, 'file_name', '/usr/share/PackageKit/helpers/yum/yumBackend.py', 'java.py')
>          self.assertGetSetCorrect(self.frame, 'file_line', 1830, 6667)
> -        self.assertGetSetCorrect(self.frame, 'is_module', False, True)
> +        self.assertGetSetCorrect(self.frame, 'special_file', False, True)
> +        self.assertGetSetCorrect(self.frame, 'special_function', False, True)
>          self.assertGetSetCorrect(self.frame, 'function_name', '_runYumTransaction', 'iiiiii')
>          self.assertGetSetCorrect(self.frame, 'line_contents', 'rpmDisplay=rpmDisplay)', 'abcde')
>  
> diff --git a/tests/python_stacktraces/python-03 b/tests/python_stacktraces/python-03
> new file mode 100644
> index 0000000..8879c6b
> --- /dev/null
> +++ b/tests/python_stacktraces/python-03
> @@ -0,0 +1,9 @@
> +Traceback (most recent call last):
> +  File "<stdin>", line 1, in <module>
> +    l(42)
> +  File "./test.py", line 6, in <lambda>
> +    l = lambda x: [ f(x) for i in range(10) ]
> +  File "./test.py", line 4, in f
> +    eval("0/0")
> +  File "<string>", line 1, in <module>
> +ZeroDivisionError: integer division or modulo by zero


-- 
Richard Marko



More information about the Crash-catcher mailing list