diff --git a/Include/errcode.h b/Include/errcode.h --- a/Include/errcode.h +++ b/Include/errcode.h @@ -30,6 +30,7 @@ extern "C" { #define E_EOLS 24 /* EOL in single-quoted string */ #define E_LINECONT 25 /* Unexpected characters after a line continuation */ #define E_IDENTIFIER 26 /* Invalid characters in identifier */ +#define E_BADSINGLE 27 /* Ill-formed single statement input */ #ifdef __cplusplus } diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py --- a/Lib/test/test_compile.py +++ b/Lib/test/test_compile.py @@ -6,6 +6,12 @@ from test import support class TestSpecifics(unittest.TestCase): + def compile_single(self, source): + compile(source, "", "single") + + def assertInvalidSingle(self, source): + self.assertRaises(SyntaxError, self.compile_single, source) + def test_no_ending_newline(self): compile("hi", "", "exec") compile("hi\r", "", "exec") @@ -442,6 +448,28 @@ if 1: if isinstance(obj, types.CodeType): self.assertIs(obj.co_filename, c.co_filename) + def test_single_statement(self): + self.compile_single("1 + 2") + self.compile_single("\n1 + 2") + self.compile_single("1 + 2\n") + self.compile_single("1 + 2\n\n") + self.compile_single("1 + 2\t\t\n") + self.compile_single("1 + 2\t\t\n ") + self.compile_single("1 + 2 # one plus two") + self.compile_single("1; 2") + self.compile_single("import sys; sys") + self.compile_single("def f():\n pass") + self.compile_single("while False:\n pass") + self.compile_single("if x:\n f(x)") + self.compile_single("if x:\n f(x)\nelse:\n g(x)") + self.compile_single("class T:\n pass") + + def test_bad_single_statement(self): + self.assertInvalidSingle('1\n2') + self.assertInvalidSingle('def f(): pass') + self.assertInvalidSingle('a = 13\nb = 187') + self.assertInvalidSingle('del x\ndel y') + self.assertInvalidSingle('f()\ng()') def test_main(): support.run_unittest(TestSpecifics) diff --git a/Parser/parsetok.c b/Parser/parsetok.c --- a/Parser/parsetok.c +++ b/Parser/parsetok.c @@ -224,6 +224,23 @@ parsetok(struct tok_state *tok, grammar if (err_ret->error == E_DONE) { n = ps->p_tree; ps->p_tree = NULL; + + /* Check that the source for a single input statement really + is a single statement by looking at what is left in the + buffer after parsing. Trailing whitespace and comments + are OK. */ + if (start == single_input) { + char *cur = tok->cur; + char c = *tok->cur; + + while (c == ' ' || c == '\t' || c == '\n' || c == '\014') + c = *++cur; + + if (c && c != '#') { + err_ret->error = E_BADSINGLE; + n = NULL; + } + } } else n = NULL; diff --git a/Python/pythonrun.c b/Python/pythonrun.c --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -2128,6 +2128,9 @@ err_input(perrdetail *err) case E_IDENTIFIER: msg = "invalid character in identifier"; break; + case E_BADSINGLE: + msg = "multiple statements found while compiling a single statement"; + break; default: fprintf(stderr, "error=%d\n", err->error); msg = "unknown parsing error";