[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