Skip to content

Commit b2daf8b

Browse files
bcallerBen Caller
authored andcommitted
Recursive function calls shouldn't raise RecursionError
Store a stack of definitions. If you revisit a function, treat it as a blackbox. The non-recursive return values should still propagate.
1 parent c223e13 commit b2daf8b

File tree

2 files changed

+17
-0
lines changed

2 files changed

+17
-0
lines changed

pyt/cfg/expr_visitor.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ast
2+
import logging
23

34
from .alias_helper import handle_aliases_in_calls
45
from ..core.ast_helper import (
@@ -30,6 +31,8 @@
3031
from .stmt_visitor import StmtVisitor
3132
from .stmt_visitor_helper import CALL_IDENTIFIER
3233

34+
log = logging.getLogger(__name__)
35+
3336

3437
class ExprVisitor(StmtVisitor):
3538
def __init__(
@@ -52,6 +55,7 @@ def __init__(
5255
self.undecided = False
5356
self.function_names = list()
5457
self.function_return_stack = list()
58+
self.function_definition_stack = list() # used to avoid recursion
5559
self.module_definitions_stack = list()
5660
self.prev_nodes_to_avoid = list()
5761
self.last_control_flow_nodes = list()
@@ -543,6 +547,7 @@ def process_function(self, call_node, definition):
543547
first_node
544548
)
545549
self.function_return_stack.pop()
550+
self.function_definition_stack.pop()
546551

547552
return self.nodes[-1]
548553

@@ -560,11 +565,15 @@ def visit_Call(self, node):
560565
last_attribute = _id.rpartition('.')[-1]
561566

562567
if definition:
568+
if definition in self.function_definition_stack:
569+
log.debug("Recursion encountered in function %s", _id)
570+
return self.add_blackbox_or_builtin_call(node, blackbox=True)
563571
if isinstance(definition.node, ast.ClassDef):
564572
self.add_blackbox_or_builtin_call(node, blackbox=False)
565573
elif isinstance(definition.node, ast.FunctionDef):
566574
self.undecided = False
567575
self.function_return_stack.append(_id)
576+
self.function_definition_stack.append(definition)
568577
return self.process_function(node, definition)
569578
else:
570579
raise Exception('Definition was neither FunctionDef or ' +

tests/cfg/cfg_test.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from .cfg_base_test_case import CFGBaseTestCase
44

55
from pyt.core.node_types import (
6+
BBorBInode,
67
EntryOrExitNode,
78
Node
89
)
@@ -1389,6 +1390,13 @@ def test_call_on_call(self):
13891390
path = 'examples/example_inputs/call_on_call.py'
13901391
self.cfg_create_from_file(path)
13911392

1393+
def test_recursive_function(self):
1394+
path = 'examples/example_inputs/recursive.py'
1395+
self.cfg_create_from_file(path)
1396+
recursive_call = self.cfg.nodes[7]
1397+
assert recursive_call.label == '~call_3 = ret_rec(wat)'
1398+
assert isinstance(recursive_call, BBorBInode) # Not RestoreNode
1399+
13921400

13931401
class CFGCallWithAttributeTest(CFGBaseTestCase):
13941402
def setUp(self):

0 commit comments

Comments
 (0)