From e704c21116bd9248312a54a5aeed77634fb22345 Mon Sep 17 00:00:00 2001 From: Adrian Bravo Date: Mon, 19 Nov 2018 16:05:47 -0800 Subject: [PATCH 1/5] 133: Visit functions in while test --- .../example_inputs/while_func_comparator.py | 6 ++++ pyt/cfg/stmt_visitor.py | 9 ++++-- tests/cfg/cfg_test.py | 32 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 examples/example_inputs/while_func_comparator.py diff --git a/examples/example_inputs/while_func_comparator.py b/examples/example_inputs/while_func_comparator.py new file mode 100644 index 00000000..6aafc2b6 --- /dev/null +++ b/examples/example_inputs/while_func_comparator.py @@ -0,0 +1,6 @@ +def foo(): + return 6 + +while x < foo(): + print(x) + x += 1 diff --git a/pyt/cfg/stmt_visitor.py b/pyt/cfg/stmt_visitor.py index 95913211..ab45707a 100644 --- a/pyt/cfg/stmt_visitor.py +++ b/pyt/cfg/stmt_visitor.py @@ -565,13 +565,18 @@ def visit_While(self, node): label_visitor = LabelVisitor() label_visitor.visit(node.test) - test = self.append_node(Node( + while_node = self.append_node(Node( 'while ' + label_visitor.result + ':', node, path=self.filenames[-1] )) - return self.loop_node_skeleton(test, node) + for comp in node.test.comparators: + if isinstance(comp, ast.Call) and get_call_names_as_string(comp.func) in self.function_names: + last_node = self.visit(comp) + last_node.connect(while_node) + + return self.loop_node_skeleton(while_node, node) def add_blackbox_or_builtin_call(self, node, blackbox): # noqa: C901 """Processes a blackbox or builtin function when it is called. diff --git a/tests/cfg/cfg_test.py b/tests/cfg/cfg_test.py index a4c24ba5..cfdc056a 100644 --- a/tests/cfg/cfg_test.py +++ b/tests/cfg/cfg_test.py @@ -684,6 +684,38 @@ def test_while_line_numbers(self): self.assertLineNumber(else_body_2, 6) self.assertLineNumber(next_stmt, 7) + def test_while_func_iterator(self): + self.cfg_create_from_file('examples/example_inputs/while_func_comparator.py') + + self.assert_length(self.cfg.nodes, expected_length=9) + + entry = 0 + test = 1 + entry_foo = 2 + ret_foo = 3 + exit_foo = 4 + call_foo = 5 + _print = 6 + body_1 = 7 + _exit = 8 + + self.assertEqual(self.cfg.nodes[test].label, 'while x < foo():') + + self.assertInCfg([ + (test, entry), + (entry_foo, test), + (_print, test), + (_exit, test), + (body_1, _print), + + (test, body_1), + (test, call_foo), + (ret_foo, entry_foo), + (exit_foo, ret_foo), + (call_foo, exit_foo), + + ]) + class CFGAssignmentMultiTest(CFGBaseTestCase): def test_assignment_multi_target(self): From effd87248cfba31318d61c72b3a0862e5313345a Mon Sep 17 00:00:00 2001 From: Adrian Bravo Date: Tue, 20 Nov 2018 10:03:54 -0800 Subject: [PATCH 2/5] 133: Support for LHS functions and no comparison while tests --- .../example_inputs/while_func_comparator.py | 4 +- .../while_func_comparator_lhs.py | 6 +++ .../while_func_comparator_rhs.py | 6 +++ pyt/cfg/stmt_visitor.py | 18 +++++-- tests/cfg/cfg_test.py | 51 +++++++++++++++++-- 5 files changed, 76 insertions(+), 9 deletions(-) create mode 100644 examples/example_inputs/while_func_comparator_lhs.py create mode 100644 examples/example_inputs/while_func_comparator_rhs.py diff --git a/examples/example_inputs/while_func_comparator.py b/examples/example_inputs/while_func_comparator.py index 6aafc2b6..8c775f72 100644 --- a/examples/example_inputs/while_func_comparator.py +++ b/examples/example_inputs/while_func_comparator.py @@ -1,6 +1,6 @@ def foo(): - return 6 + return True -while x < foo(): +while foo(): print(x) x += 1 diff --git a/examples/example_inputs/while_func_comparator_lhs.py b/examples/example_inputs/while_func_comparator_lhs.py new file mode 100644 index 00000000..1904e8e7 --- /dev/null +++ b/examples/example_inputs/while_func_comparator_lhs.py @@ -0,0 +1,6 @@ +def foo(): + return 6 + +while foo() > x: + print(x) + x += 1 diff --git a/examples/example_inputs/while_func_comparator_rhs.py b/examples/example_inputs/while_func_comparator_rhs.py new file mode 100644 index 00000000..6aafc2b6 --- /dev/null +++ b/examples/example_inputs/while_func_comparator_rhs.py @@ -0,0 +1,6 @@ +def foo(): + return 6 + +while x < foo(): + print(x) + x += 1 diff --git a/pyt/cfg/stmt_visitor.py b/pyt/cfg/stmt_visitor.py index ab45707a..965b24eb 100644 --- a/pyt/cfg/stmt_visitor.py +++ b/pyt/cfg/stmt_visitor.py @@ -563,7 +563,8 @@ def visit_For(self, node): def visit_While(self, node): label_visitor = LabelVisitor() - label_visitor.visit(node.test) + test = node.test # the test condition of the while loop + label_visitor.visit(test) while_node = self.append_node(Node( 'while ' + label_visitor.result + ':', @@ -571,11 +572,20 @@ def visit_While(self, node): path=self.filenames[-1] )) - for comp in node.test.comparators: - if isinstance(comp, ast.Call) and get_call_names_as_string(comp.func) in self.function_names: - last_node = self.visit(comp) + def process_comparator(comp_n): + if isinstance(comp_n, ast.Call) and get_call_names_as_string(comp_n.func) in self.function_names: + last_node = self.visit(comp_n) last_node.connect(while_node) + if isinstance(test, ast.Compare): + comparators = test.comparators + comparators.append(test.left) # quirk. See https://greentreesnakes.readthedocs.io/en/latest/nodes.html#Compare + + for comp in comparators: + process_comparator(comp) + else: # while foo(): + process_comparator(test) + return self.loop_node_skeleton(while_node, node) def add_blackbox_or_builtin_call(self, node, blackbox): # noqa: C901 diff --git a/tests/cfg/cfg_test.py b/tests/cfg/cfg_test.py index cfdc056a..bf47275e 100644 --- a/tests/cfg/cfg_test.py +++ b/tests/cfg/cfg_test.py @@ -684,7 +684,7 @@ def test_while_line_numbers(self): self.assertLineNumber(else_body_2, 6) self.assertLineNumber(next_stmt, 7) - def test_while_func_iterator(self): + def test_while_func_comparator(self): self.cfg_create_from_file('examples/example_inputs/while_func_comparator.py') self.assert_length(self.cfg.nodes, expected_length=9) @@ -699,6 +699,23 @@ def test_while_func_iterator(self): body_1 = 7 _exit = 8 + self.assertEqual(self.cfg.nodes[test].label, 'while foo():') + + def test_while_func_comparator_rhs(self): + self.cfg_create_from_file('examples/example_inputs/while_func_comparator_rhs.py') + + self.assert_length(self.cfg.nodes, expected_length=9) + + entry = 0 + test = 1 + entry_foo = 2 + ret_foo = 3 + exit_foo = 4 + call_foo = 5 + _print = 6 + body_1 = 7 + _exit = 8 + self.assertEqual(self.cfg.nodes[test].label, 'while x < foo():') self.assertInCfg([ @@ -707,13 +724,41 @@ def test_while_func_iterator(self): (_print, test), (_exit, test), (body_1, _print), - (test, body_1), (test, call_foo), (ret_foo, entry_foo), (exit_foo, ret_foo), - (call_foo, exit_foo), + (call_foo, exit_foo) + ]) + + def test_while_func_comparator_lhs(self): + self.cfg_create_from_file('examples/example_inputs/while_func_comparator_lhs.py') + self.assert_length(self.cfg.nodes, expected_length=9) + + entry = 0 + test = 1 + entry_foo = 2 + ret_foo = 3 + exit_foo = 4 + call_foo = 5 + _print = 6 + body_1 = 7 + _exit = 8 + + self.assertEqual(self.cfg.nodes[test].label, 'while foo() > x:') + + self.assertInCfg([ + (test, entry), + (entry_foo, test), + (_print, test), + (_exit, test), + (body_1, _print), + (test, body_1), + (test, call_foo), + (ret_foo, entry_foo), + (exit_foo, ret_foo), + (call_foo, exit_foo) ]) From b52a8707b7fc598d1c7f59c785be0269d1b4a1d5 Mon Sep 17 00:00:00 2001 From: Adrian Bravo Date: Tue, 20 Nov 2018 10:17:20 -0800 Subject: [PATCH 3/5] 133: Fix style and complexity issues for Travis.ci --- pyt/cfg/stmt_visitor.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/pyt/cfg/stmt_visitor.py b/pyt/cfg/stmt_visitor.py index 965b24eb..d3f8e2c8 100644 --- a/pyt/cfg/stmt_visitor.py +++ b/pyt/cfg/stmt_visitor.py @@ -555,15 +555,26 @@ def visit_For(self, node): path=self.filenames[-1] )) - if isinstance(node.iter, ast.Call) and get_call_names_as_string(node.iter.func) in self.function_names: - last_node = self.visit(node.iter) - last_node.connect(for_node) + self.process_loop_funcs(node.iter, for_node) return self.loop_node_skeleton(for_node, node) + def process_loop_funcs(self, comp_n, loop_node): + """ + If the loop test node contains function calls, it connects the loop node to the nodes of + those function calls. + + :param comp_n: The test node of a loop that may contain functions. + :param loop_node: The loop node itself to connect to the new function nodes if any + :return: None + """ + if isinstance(comp_n, ast.Call) and get_call_names_as_string(comp_n.func) in self.function_names: + last_node = self.visit(comp_n) + last_node.connect(loop_node) + def visit_While(self, node): label_visitor = LabelVisitor() - test = node.test # the test condition of the while loop + test = node.test # the test condition of the while loop label_visitor.visit(test) while_node = self.append_node(Node( @@ -572,19 +583,14 @@ def visit_While(self, node): path=self.filenames[-1] )) - def process_comparator(comp_n): - if isinstance(comp_n, ast.Call) and get_call_names_as_string(comp_n.func) in self.function_names: - last_node = self.visit(comp_n) - last_node.connect(while_node) - if isinstance(test, ast.Compare): comparators = test.comparators - comparators.append(test.left) # quirk. See https://greentreesnakes.readthedocs.io/en/latest/nodes.html#Compare + comparators.append(test.left) # quirk. See https://greentreesnakes.readthedocs.io/en/latest/nodes.html#Compare for comp in comparators: - process_comparator(comp) - else: # while foo(): - process_comparator(test) + self.process_loop_funcs(comp, while_node) + else: # while foo(): + self.process_loop_funcs(test, while_node) return self.loop_node_skeleton(while_node, node) From 6d25eae6ce969e1930846e6714445ee5ca870a47 Mon Sep 17 00:00:00 2001 From: Adrian Bravo Date: Tue, 20 Nov 2018 10:21:56 -0800 Subject: [PATCH 4/5] 133: Finished tests --- tests/cfg/cfg_test.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/cfg/cfg_test.py b/tests/cfg/cfg_test.py index bf47275e..3af37942 100644 --- a/tests/cfg/cfg_test.py +++ b/tests/cfg/cfg_test.py @@ -701,6 +701,19 @@ def test_while_func_comparator(self): self.assertEqual(self.cfg.nodes[test].label, 'while foo():') + self.assertInCfg([ + (test, entry), + (entry_foo, test), + (_print, test), + (_exit, test), + (body_1, _print), + (test, body_1), + (test, call_foo), + (ret_foo, entry_foo), + (exit_foo, ret_foo), + (call_foo, exit_foo) + ]) + def test_while_func_comparator_rhs(self): self.cfg_create_from_file('examples/example_inputs/while_func_comparator_rhs.py') From 9cb0b567c8cd0c66bcf9ac87ad1425d47a75b908 Mon Sep 17 00:00:00 2001 From: Adrian Bravo Date: Wed, 21 Nov 2018 10:12:19 -0800 Subject: [PATCH 5/5] 133: Avoid mutating node.test.comparators --- pyt/cfg/stmt_visitor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyt/cfg/stmt_visitor.py b/pyt/cfg/stmt_visitor.py index d3f8e2c8..3b9d5f48 100644 --- a/pyt/cfg/stmt_visitor.py +++ b/pyt/cfg/stmt_visitor.py @@ -584,10 +584,10 @@ def visit_While(self, node): )) if isinstance(test, ast.Compare): - comparators = test.comparators - comparators.append(test.left) # quirk. See https://greentreesnakes.readthedocs.io/en/latest/nodes.html#Compare + # quirk. See https://greentreesnakes.readthedocs.io/en/latest/nodes.html#Compare + self.process_loop_funcs(test.left, while_node) - for comp in comparators: + for comp in test.comparators: self.process_loop_funcs(comp, while_node) else: # while foo(): self.process_loop_funcs(test, while_node)