[Python-checkins] gh-93351: Ensure the position information in AST nodes created by the parser is always consistent (GH-93352)

miss-islington webhook-mailer at python.org
Mon May 30 14:52:41 EDT 2022


https://github.com/python/cpython/commit/7f6e6abdc43d9b61d81eb26b9fb2073dccf6a3cb
commit: 7f6e6abdc43d9b61d81eb26b9fb2073dccf6a3cb
branch: 3.11
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: miss-islington <31488909+miss-islington at users.noreply.github.com>
date: 2022-05-30T11:52:36-07:00
summary:

gh-93351: Ensure the position information in AST nodes created by the parser is always consistent (GH-93352)

(cherry picked from commit 5893b5db98b38b17750c0572c7209774a5034898)

Co-authored-by: Pablo Galindo Salgado <Pablogsal at gmail.com>

files:
M Lib/test/test_ast.py
M Python/ast.c

diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py
index 03d9b310f161b..896eb5bedd7f3 100644
--- a/Lib/test/test_ast.py
+++ b/Lib/test/test_ast.py
@@ -335,6 +335,33 @@ def test_ast_validation(self):
         for snippet in snippets_to_validate:
             tree = ast.parse(snippet)
             compile(tree, '<string>', 'exec')
+    
+    def test_invalid_position_information(self):
+        invalid_linenos = [
+            (10, 1), (-10, -11), (10, -11), (-5, -2), (-5, 1)
+        ]
+
+        for lineno, end_lineno in invalid_linenos:
+            with self.subTest(f"Check invalid linenos {lineno}:{end_lineno}"):
+                snippet = "a = 1"
+                tree = ast.parse(snippet)
+                tree.body[0].lineno = lineno
+                tree.body[0].end_lineno = end_lineno
+                with self.assertRaises(ValueError):
+                    compile(tree, '<string>', 'exec')
+
+        invalid_col_offsets = [
+            (10, 1), (-10, -11), (10, -11), (-5, -2), (-5, 1)
+        ]
+        for col_offset, end_col_offset in invalid_col_offsets:
+            with self.subTest(f"Check invalid col_offset {col_offset}:{end_col_offset}"):
+                snippet = "a = 1"
+                tree = ast.parse(snippet)
+                tree.body[0].col_offset = col_offset
+                tree.body[0].end_col_offset = end_col_offset
+                with self.assertRaises(ValueError):
+                    compile(tree, '<string>', 'exec')
+
 
     def test_slice(self):
         slc = ast.parse("x[::]").body[0].value.slice
diff --git a/Python/ast.c b/Python/ast.c
index 607281e268553..0885fe7798fa5 100644
--- a/Python/ast.c
+++ b/Python/ast.c
@@ -22,6 +22,27 @@ static int validate_stmt(struct validator *, stmt_ty);
 static int validate_expr(struct validator *, expr_ty, expr_context_ty);
 static int validate_pattern(struct validator *, pattern_ty, int);
 
+#define VALIDATE_POSITIONS(node) \
+    if (node->lineno > node->end_lineno) { \
+        PyErr_Format(PyExc_ValueError, \
+                     "line %d-%d is not a valid range", \
+                     node->lineno, node->end_lineno); \
+        return 0; \
+    } \
+    if ((node->lineno < 0 && node->end_lineno != node->lineno) || \
+        (node->col_offset < 0 && node->col_offset != node->end_col_offset)) { \
+        PyErr_Format(PyExc_ValueError, \
+                     "line %d-%d, column %d-%d is not a valid range", \
+                     node->lineno, node->end_lineno, node->col_offset, node->end_col_offset); \
+        return 0; \
+    } \
+    if (node->lineno == node->end_lineno && node->col_offset > node->end_col_offset) { \
+        PyErr_Format(PyExc_ValueError, \
+                     "line %d, column %d-%d is not a valid range", \
+                     node->lineno, node->col_offset, node->end_col_offset); \
+        return 0; \
+    }
+
 static int
 validate_name(PyObject *name)
 {
@@ -75,6 +96,7 @@ validate_args(struct validator *state, asdl_arg_seq *args)
     Py_ssize_t i;
     for (i = 0; i < asdl_seq_LEN(args); i++) {
         arg_ty arg = asdl_seq_GET(args, i);
+        VALIDATE_POSITIONS(arg);
         if (arg->annotation && !validate_expr(state, arg->annotation, Load))
             return 0;
     }
@@ -183,6 +205,7 @@ validate_constant(struct validator *state, PyObject *value)
 static int
 validate_expr(struct validator *state, expr_ty exp, expr_context_ty ctx)
 {
+    VALIDATE_POSITIONS(exp);
     int ret = -1;
     if (++state->recursion_depth > state->recursion_limit) {
         PyErr_SetString(PyExc_RecursionError,
@@ -505,6 +528,7 @@ validate_capture(PyObject *name)
 static int
 validate_pattern(struct validator *state, pattern_ty p, int star_ok)
 {
+    VALIDATE_POSITIONS(p);
     int ret = -1;
     if (++state->recursion_depth > state->recursion_limit) {
         PyErr_SetString(PyExc_RecursionError,
@@ -674,6 +698,7 @@ validate_body(struct validator *state, asdl_stmt_seq *body, const char *owner)
 static int
 validate_stmt(struct validator *state, stmt_ty stmt)
 {
+    VALIDATE_POSITIONS(stmt);
     int ret = -1;
     Py_ssize_t i;
     if (++state->recursion_depth > state->recursion_limit) {
@@ -807,6 +832,7 @@ validate_stmt(struct validator *state, stmt_ty stmt)
         }
         for (i = 0; i < asdl_seq_LEN(stmt->v.Try.handlers); i++) {
             excepthandler_ty handler = asdl_seq_GET(stmt->v.Try.handlers, i);
+            VALIDATE_POSITIONS(handler);
             if ((handler->v.ExceptHandler.type &&
                  !validate_expr(state, handler->v.ExceptHandler.type, Load)) ||
                 !validate_body(state, handler->v.ExceptHandler.body, "ExceptHandler"))



More information about the Python-checkins mailing list