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

Martin Milata mmilata at redhat.com
Wed May 22 14:08:01 UTC 2013


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           | 27 ++++++++++--------
 tests/python/python.py             | 34 ++++++++++++++++++++++-
 tests/python_stacktraces/python-03 |  9 ++++++
 5 files changed, 101 insertions(+), 29 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..24013a7 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 },
 };
 
@@ -167,16 +169,19 @@ sr_py_python_frame_str(PyObject *self)
 
 
     if (this->frame->file_name)
-      sr_strbuf_append_strf(buf, "File \"%s\"", this->frame->file_name);
+      sr_strbuf_append_strf(buf, "File \"%s%s%s\"",
+                            (this->frame->special_file ? "<" : ""),
+                            this->frame->file_name,
+                            (this->frame->special_file ? ">" : ""));
 
     if (this->frame->file_line)
       sr_strbuf_append_strf(buf, ", %d", this->frame->file_line);
 
     if (this->frame->function_name)
-      sr_strbuf_append_strf(buf, ", in %s", this->frame->function_name);
-
-    if (this->frame->is_module)
-      sr_strbuf_append_str(buf, ", in <module>");
+      sr_strbuf_append_strf(buf, ", in %s%s%s",
+                            (this->frame->special_function ? "<" : ""),
+                            this->frame->function_name,
+                            (this->frame->special_function ? ">" : ""));
 
     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..0f989dc 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, 'f')
+        self.assertFalse(f.special_file)
+        self.assertFalse(f.special_function)
+
+        f = trace.frames[2]
+        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[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
-- 
1.7.11.7



More information about the Crash-catcher mailing list