Repository: angr/angr

Issue:
DefUseGraph.top_predecessors() may trigger an infinite recursion
<!--
*Disclaimer:
The angr suite is maintained by a small team of volunteers.
While we cannot guarantee any timeliness for fixes and enhancements, we will do our best.
For more real-time help with angr, from us and the community, join our [Slack.](http://angr.io/invite/)*
-->

**Describe the bug.**
<!--
Please include a clear and concise description of what the bug is.
-->

Infinite recursion triggered by one of the test cases. Raw log is [here](https://dev.azure.com/angr/64bc1357-22a8-47d9-a89e-588cd888be53/_apis/build/builds/1354/logs/104).

```
======================================================================
ERROR: test_def_use_graph.transplant_class.<locals>.C (test_top_predecessors_should_not_contain_duplicates)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/__w/1/s/build/src/angr/tests/test_def_use_graph.py", line 100, in test_top_predecessors_should_not_contain_duplicates
    result = def_use_graph.top_predecessors(D)
  File "/__w/1/s/build/src/angr/angr/analyses/reaching_definitions/def_use_graph.py", line 121, in top_predecessors
    return _top_predecessors(definition, self._graph, [])
  File "/__w/1/s/build/src/angr/angr/analyses/reaching_definitions/def_use_graph.py", line 118, in _top_predecessors
    result
  File "/__w/1/s/build/src/angr/angr/analyses/reaching_definitions/def_use_graph.py", line 116, in <lambda>
    lambda acc, definition: _top_predecessors(definition, graph, acc),
  File "/__w/1/s/build/src/angr/angr/analyses/reaching_definitions/def_use_graph.py", line 118, in _top_predecessors
    result
  File "/__w/1/s/build/src/angr/angr/analyses/reaching_definitions/def_use_graph.py", line 116, in <lambda>
    lambda acc, definition: _top_predecessors(definition, graph, acc),
  File "/__w/1/s/build/src/angr/angr/analyses/reaching_definitions/def_use_graph.py", line 118, in _top_predecessors
    result
  File "/__w/1/s/build/src/angr/angr/analyses/reaching_definitions/def_use_graph.py", line 116, in <lambda>
    lambda acc, definition: _top_predecessors(definition, graph, acc),
...snip...
  File "/__w/1/s/build/src/angr/angr/analyses/reaching_definitions/def_use_graph.py", line 116, in <lambda>
    lambda acc, definition: _top_predecessors(definition, graph, acc),
  File "/__w/1/s/build/src/angr/angr/analyses/reaching_definitions/def_use_graph.py", line 118, in _top_predecessors
    result
  File "/__w/1/s/build/src/angr/angr/analyses/reaching_definitions/def_use_graph.py", line 116, in <lambda>
    lambda acc, definition: _top_predecessors(definition, graph, acc),
  File "/__w/1/s/build/src/angr/angr/analyses/reaching_definitions/def_use_graph.py", line 110, in _top_predecessors
    predecessors = list(graph.predecessors(definition))
  File "/__w/1/s/build/virtualenv/lib/python3.6/site-packages/networkx/classes/digraph.py", line 837, in predecessors
    return iter(self._pred[n])
  File "/__w/1/s/build/src/angr/angr/analyses/reaching_definitions/definition.py", line 40, in __hash__
    return hash((self.atom, self.codeloc, self.data))
RecursionError: maximum recursion depth exceeded while calling a Python object
```


**Environment Information.**
<!--
Many common issues are caused by problems with the local Python environment.
Before submitting, double-check that your versions of all modules in the angr suite (angr, cle, pyvex, ...) are up to date.
Please include the output of `python -m angr.misc.bug_report` here.
-->

Latest master branches of all projects (except for CLE) by 1/5/2020 and `cle:feat/pe_fast_load`.

**To Reproduce.**
<!--
Please include *both a script to reproduce the crash, and attach the binary used, if possible*
-->

Cannot be reproduced reliably. But running the test case multiple times should trigger it (if lucky).

**Additional context.**
<!--
Add any other context about the problem here.
-->



Discussion:

Resolving patch:
diff --git a/angr/analyses/reaching_definitions/dep_graph.py b/angr/analyses/reaching_definitions/dep_graph.py
--- a/angr/analyses/reaching_definitions/dep_graph.py
+++ b/angr/analyses/reaching_definitions/dep_graph.py
@@ -1,4 +1,4 @@
-from typing import Optional, Dict, List
+from typing import Optional, Dict
 from functools import reduce
 
 import networkx
@@ -91,27 +91,3 @@ def _transitive_closure(def_: Definition, graph: networkx.DiGraph, result: netwo
             return closure
 
         return _transitive_closure(definition, self._graph, networkx.DiGraph())
-
-    def top_predecessors(self, definition: Definition) -> List[Definition]:
-        """
-        Recover the "entrypoint definitions" flowing into a given definition.
-        Obtained by transitively computing the top-level ancestors (nodes without predecessors) of this definition in
-        the graph.
-
-        :param definition: The <Definition> to return the top-level ancestors for.
-        :return: The list of top-level definitions flowing into the <node>.
-        """
-
-        def _top_predecessors(def_: Definition, graph: networkx.DiGraph, result: List[Definition]):
-            predecessors = list(graph.predecessors(def_))
-
-            if len(predecessors) == 0 and def_ not in result:
-                return result + [ def_ ]
-
-            return reduce(
-                lambda acc, definition: _top_predecessors(definition, graph, acc),
-                predecessors,
-                result
-            )
-
-        return _top_predecessors(definition, self._graph, [])


Test patch:
diff --git a/tests/test_dep_graph.py b/tests/test_dep_graph.py
--- a/tests/test_dep_graph.py
+++ b/tests/test_dep_graph.py
@@ -67,51 +67,6 @@ def test_delegate_add_edge_to_the_underlying_graph_object(digraph_add_edge_mock)
     digraph_add_edge_mock.assert_called_once_with(*use, **labels)
 
 
-def test_top_predecessors():
-    dep_graph = DepGraph()
-
-    # A -> B, B -> D, C -> D
-    A = _a_mock_definition()
-    B = _a_mock_definition()
-    C = _a_mock_definition()
-    D = _a_mock_definition()
-    uses = [
-        (A, B),
-        (B, D),
-        (C, D),
-    ]
-
-    for use in uses:
-        dep_graph.add_edge(*use)
-
-    result = dep_graph.top_predecessors(D)
-
-    nose.tools.assert_list_equal(result, [A, C])
-
-
-def test_top_predecessors_should_not_contain_duplicates():
-    dep_graph = DepGraph()
-
-    # A -> B, A -> C, B -> D, C -> D
-    A = _a_mock_definition()
-    B = _a_mock_definition()
-    C = _a_mock_definition()
-    D = _a_mock_definition()
-    uses = [
-        (A, B),
-        (A, C),
-        (B, D),
-        (C, D),
-    ]
-
-    for use in uses:
-        dep_graph.add_edge(*use)
-
-    result = dep_graph.top_predecessors(D)
-
-    nose.tools.assert_list_equal(result, [A])
-
-
 def test_transitive_closure_of_a_node():
     dep_graph = DepGraph()