From 81c6d8ef8aea440932c51519ee2844a64da0cd90 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Mon, 15 Jan 2024 14:14:35 -0800 Subject: [PATCH] fix: unreachable code analysis inside for loops (#3731) unreachable code analysis did not analyze for loop bodies. fix: in `find_terminating_node()`, recurse into the bodies of for loops. --- .../syntax/test_unbalanced_return.py | 40 +++++++++++++++++++ vyper/ast/nodes.pyi | 6 ++- vyper/semantics/analysis/local.py | 5 +++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/tests/functional/syntax/test_unbalanced_return.py b/tests/functional/syntax/test_unbalanced_return.py index d5754f0053..04835bb0f0 100644 --- a/tests/functional/syntax/test_unbalanced_return.py +++ b/tests/functional/syntax/test_unbalanced_return.py @@ -118,6 +118,39 @@ def foo() -> uint256: """, StructureException, ), + ( + """ +@internal +def foo() -> uint256: + for i: uint256 in range(10): + if i == 11: + return 1 + """, + FunctionDeclarationException, + ), + ( + """ +@internal +def foo() -> uint256: + for i: uint256 in range(9): + if i == 11: + return 1 + if block.number % 2 == 0: + return 1 + """, + FunctionDeclarationException, + ), + ( + """ +@internal +def foo() -> uint256: + for i: uint256 in range(10): + return 1 + pass # unreachable + return 5 + """, + StructureException, + ), ] @@ -187,6 +220,13 @@ def foo() -> int128: else: raw_revert(b"vyper") """, + """ +@external +def foo() -> int128: + for i: uint256 in range(1): + return 1 + return 0 + """, ] diff --git a/vyper/ast/nodes.pyi b/vyper/ast/nodes.pyi index 7f8c902d45..896329c702 100644 --- a/vyper/ast/nodes.pyi +++ b/vyper/ast/nodes.pyi @@ -257,6 +257,10 @@ class IfExp(ExprNode): body: ExprNode = ... orelse: ExprNode = ... -class For(VyperNode): ... +class For(VyperNode): + target: ExprNode + iter: ExprNode + body: list[VyperNode] + class Break(VyperNode): ... class Continue(VyperNode): ... diff --git a/vyper/semantics/analysis/local.py b/vyper/semantics/analysis/local.py index c4af5b1e3a..29a93a9eaf 100644 --- a/vyper/semantics/analysis/local.py +++ b/vyper/semantics/analysis/local.py @@ -74,6 +74,7 @@ def find_terminating_node(node_list: list) -> Optional[vy_ast.VyperNode]: for node in node_list: if ret is not None: raise StructureException("Unreachable code!", node) + if node.is_terminus: ret = node @@ -87,6 +88,10 @@ def find_terminating_node(node_list: list) -> Optional[vy_ast.VyperNode]: if body_terminates is not None and else_terminates is not None: ret = else_terminates + if isinstance(node, vy_ast.For): + # call find_terminating_node for its side effects + find_terminating_node(node.body) + return ret