From 7fa846eea1f9f1a541ec061f272d4f82d4aee428 Mon Sep 17 00:00:00 2001 From: Jakub Filak Date: Wed, 25 Jun 2014 17:28:00 +0200 Subject: [SATYR PATCH 3/6] Fix parsing of invalid syntax Python exceptions Closes #168 Signed-off-by: Jakub Filak mmilata: commit msg typo --- lib/python_frame.c | 56 +++++++++++++++++++------------ lib/python_stacktrace.c | 69 ++++++++++++++++++++++++++++++++++---- tests/python/python.py | 38 +++++++++++++++++++++ tests/python_stacktraces/python-04 | 6 ++++ tests/python_stacktraces/python-05 | 16 +++++++++ 5 files changed, 157 insertions(+), 28 deletions(-) create mode 100644 tests/python_stacktraces/python-04 create mode 100644 tests/python_stacktraces/python-05 diff --git a/lib/python_frame.c b/lib/python_frame.c index b62628f..b0540fe 100644 --- a/lib/python_frame.c +++ b/lib/python_frame.c @@ -260,33 +260,45 @@ sr_python_frame_parse(const char **input, if (0 == sr_skip_string(&local_input, ", in ")) { - location->message = sr_asprintf("Function name separator not found."); - return NULL; - } - - location->column += strlen(", in "); + if (local_input[0] != '\n') + { + location->message = sr_asprintf("Function name separator not found."); + return NULL; + } - /* Parse function name */ - if (!sr_parse_char_cspan(&local_input, "\n", &frame->function_name)) + /* The last frame of SyntaxError stack trace does not have + * function name on its line. For the sake of simplicity, we will + * believe that we are dealing with such a frame now. + */ + frame->function_name = sr_strdup("syntax"); + frame->special_function = true; + } + else { - sr_python_frame_free(frame); - location->message = sr_asprintf("Unable to find the newline character " - "identifying the end of function name."); + location->column += strlen(", in "); - return NULL; - } + /* Parse function name */ + if (!sr_parse_char_cspan(&local_input, "\n", &frame->function_name)) + { + sr_python_frame_free(frame); + location->message = sr_asprintf("Unable to find the newline character " + "identifying the end of function name."); - location->column += strlen(frame->function_name); + return NULL; + } - if (strlen(frame->function_name) > 0 && - frame->function_name[0] == '<' && - frame->function_name[strlen(frame->function_name)-1] == '>') - { - 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 = inside; + location->column += strlen(frame->function_name); + + if (strlen(frame->function_name) > 0 && + frame->function_name[0] == '<' && + frame->function_name[strlen(frame->function_name)-1] == '>') + { + 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 = inside; + } } sr_skip_char(&local_input, '\n'); diff --git a/lib/python_stacktrace.c b/lib/python_stacktrace.c index 3dc0a75..99aa52c 100644 --- a/lib/python_stacktrace.c +++ b/lib/python_stacktrace.c @@ -29,6 +29,7 @@ #include "generic_thread.h" #include "internal_utils.h" #include +#include #include #include @@ -137,13 +138,48 @@ sr_python_stacktrace_parse(const char **input, if (!local_input) { - location->message = "Traceback header not found."; - return NULL; - } + /* SyntaxError stack trace of an exception thrown in the executed file + * conforms to the following template: + * invalid syntax ($file, line $number) + * + * File "$file", line $number + * $code + * ^ + * SyntaxError: invalid syntax + * + * for exceptions thrown from imported files, the stack trace has the + * regular form, except the last frame has no function name and is + * followed by the pointer line (^). + */ + HEADER = "invalid syntax (", + local_input = sr_strstr_location(*input, + HEADER, + &location->line, + &location->column); - local_input += strlen(HEADER); - location->line += 2; - location->column = 0; + if (!local_input) + { + location->message = "Traceback header not found."; + return NULL; + } + + local_input = sr_strstr_location(local_input, + " File \"", + &location->line, + &location->column); + + if (!local_input) + { + location->message = "Frame with invalid line not found."; + return NULL; + } + } + else + { + local_input += strlen(HEADER); + location->line += 2; + location->column = 0; + } struct sr_python_stacktrace *stacktrace = sr_python_stacktrace_new(); @@ -173,6 +209,27 @@ sr_python_stacktrace_parse(const char **input, return NULL; } + bool invalid_syntax_pointer = true; + const char *tmp_input = local_input; + while (*tmp_input != '\n' && *tmp_input != '\0') + { + if (*tmp_input != ' ' && *tmp_input != '^') + { + invalid_syntax_pointer = false; + break; + } + ++tmp_input; + } + + if (invalid_syntax_pointer) + { + /* Skip line " ^" pointing to the invalid code */ + sr_skip_char_cspan(&local_input, "\n"); + ++local_input; + ++location->line; + location->column = 1; + } + /* Parse exception name. */ sr_parse_char_cspan(&local_input, ":\n", diff --git a/tests/python/python.py b/tests/python/python.py index 146ca01..9044200 100755 --- a/tests/python/python.py +++ b/tests/python/python.py @@ -143,6 +143,44 @@ class TestPythonStacktrace(BindingsTestCase): def test_hash(self): self.assertHashable(self.trace) + def test_invalid_syntax_current_file(self): + trace = load_input_contents('../python_stacktraces/python-04') + trace = satyr.PythonStacktrace(trace) + + self.assertEqual(len(trace.frames), 1) + self.assertEqual(trace.exception_name, 'SyntaxError') + + f = trace.frames[0] + self.assertEqual(f.file_name, '/usr/bin/python3-mako-render') + self.assertEqual(f.function_name, "syntax") + self.assertEqual(f.file_line, 43) + self.assertEqual(f.line_contents, 'print render(data, kw)') + self.assertTrue(f.special_function) + self.assertFalse(f.special_file) + + def test_invalid_syntax_imported_file(self): + trace = load_input_contents('../python_stacktraces/python-05') + trace = satyr.PythonStacktrace(trace) + + self.assertEqual(len(trace.frames), 2) + self.assertEqual(trace.exception_name, 'SyntaxError') + + f = trace.frames[1] + self.assertEqual(f.file_name, '/usr/bin/will_python_raise') + self.assertEqual(f.function_name, "module") + self.assertEqual(f.file_line, 2) + self.assertEqual(f.line_contents, 'import report') + self.assertTrue(f.special_function) + self.assertFalse(f.special_file) + + f = trace.frames[0] + self.assertEqual(f.file_name, '/usr/lib64/python2.7/site-packages/report/__init__.py') + self.assertEqual(f.function_name, "syntax") + self.assertEqual(f.file_line, 15) + self.assertEqual(f.line_contents, 'def foo(:') + self.assertTrue(f.special_function) + self.assertFalse(f.special_file) + class TestPythonFrame(BindingsTestCase): def setUp(self): self.frame = satyr.PythonStacktrace(contents).frames[-1] diff --git a/tests/python_stacktraces/python-04 b/tests/python_stacktraces/python-04 new file mode 100644 index 0000000..91c3a3d --- /dev/null +++ b/tests/python_stacktraces/python-04 @@ -0,0 +1,6 @@ +invalid syntax (python3-mako-render, line 43) + + File "/usr/bin/python3-mako-render", line 43 + print render(data, kw) + ^ +SyntaxError: invalid syntax diff --git a/tests/python_stacktraces/python-05 b/tests/python_stacktraces/python-05 new file mode 100644 index 0000000..79d1b8d --- /dev/null +++ b/tests/python_stacktraces/python-05 @@ -0,0 +1,16 @@ +will_python_raise:2:: File "/usr/lib64/python2.7/site-packages/report/__init__.py", line 15 + +Traceback (most recent call last): + File "/usr/bin/will_python_raise", line 2, in + import report + File "/usr/lib64/python2.7/site-packages/report/__init__.py", line 15 + def foo(: + ^ +SyntaxError: invalid syntax + +Local variables in innermost frame: +__builtins__: +__name__: '__main__' +__file__: '/usr/bin/will_python_raise' +__doc__: None +__package__: None -- 1.9.3