diff --git a/python/extractor/semmle/python/passes/flow.py b/python/extractor/semmle/python/passes/flow.py index a9148aefd0f7..9f434fe0c4c8 100755 --- a/python/extractor/semmle/python/passes/flow.py +++ b/python/extractor/semmle/python/passes/flow.py @@ -909,14 +909,22 @@ def _walk_slice(self, node, predecessors): def _walk_break(self, node, predecessors): #A break statement counts as an exit to the enclosing loop statement predecessors = self.add_successor(predecessors, node) - self.scope.breaking_stack.add(predecessors) + # In well formed code, there should always be an element in the breaking stack, but because + # our parser accepts code where `break` appears outside of a loop, we must check for this + # case. + if self.scope.breaking_stack: + self.scope.breaking_stack.add(predecessors) #Provide no predecessors to following statement return EMPTY def _walk_continue(self, node, predecessors): #A continue statement counts as an exit to the following orelse predecessors = self.add_successor(predecessors, node) - self.scope.continuing_stack.add(predecessors) + # In well formed code, there should always be an element in the continuing stack, but + # because our parser accepts code where `continue` appears outside of a loop, we must check + # for this case. + if self.scope.continuing_stack: + self.scope.continuing_stack.add(predecessors) #Provide no predecessors to following statement return EMPTY diff --git a/python/ql/lib/change-notes/2025-02-06-robustly-handle-loop-constructs.md b/python/ql/lib/change-notes/2025-02-06-robustly-handle-loop-constructs.md new file mode 100644 index 000000000000..45bbb2e7cc4f --- /dev/null +++ b/python/ql/lib/change-notes/2025-02-06-robustly-handle-loop-constructs.md @@ -0,0 +1,5 @@ +--- +category: fix +--- + +- Using the `break` and `continue` keywords outside of a loop, which is a syntax error but is accepted by our parser, would cause the control-flow construction to fail. This is now no longer the case. diff --git a/python/ql/test/extractor-tests/syntax_error/without_loop.py b/python/ql/test/extractor-tests/syntax_error/without_loop.py new file mode 100644 index 000000000000..84a0ea15932c --- /dev/null +++ b/python/ql/test/extractor-tests/syntax_error/without_loop.py @@ -0,0 +1,10 @@ +# The following constructs are syntax errors in Python, as they are not inside a loop. +# However, our parser does not consider this code to be syntactically incorrect. +# Thus, this test is really observing that allowing these constructs does not break any other parts +# of the extractor. + +if True: + break + +if True: + continue