Repository: angr/angr

Issue:
fail to print debug representation of backward slice
**Describe the bug**
When playing with `BackwardSlice`, it crashes.

**To Reproduce**
~~~
import angr
import logging
logging.getLogger('angr.analyses.backward_slice').setLevel('DEBUG')


b = angr.Project('/home/kylebot/src/angr-dev/angr-doc/examples/fauxware/fauxware', auto_load_libs=False)
cfg = b.analyses.CFGEmulated(keep_state=True,
                                 state_add_options=angr.sim_options.refs,
                                 context_sensitivity_level=2)
cdg = b.analyses.CDG(cfg)
ddg = b.analyses.DDG(cfg)
target_func = cfg.kb.functions.function(name="exit")
target_node = cfg.get_any_node(target_func.addr)

bs = b.analyses.BackwardSlice(cfg, cdg=cdg, ddg=ddg, targets=[ (target_node, -1) ])

print(bs.dbg_repr())
~~~



Discussion:
The code is actually almost copy-paste from angr.io. The binary is the fauxware example from angr-doc.
What is the exception?
The error log is as follow:
~~~
Traceback (most recent call last):
  File "x.py", line 17, in <module>
    print(bs.dbg_repr())
  File "/home/kylebot/src/angr-dev/angr/angr/analyses/backward_slice.py", line 117, in dbg_repr
    run_addrs = sorted(self.chosen_statements.keys())[ : max_display]
TypeError: '<' not supported between instances of 'NoneType' and 'int'
~~~
The problem is there is a `None:[None]` in `bs.chosen_statements`
Yeah... it's a left-over from Python 2 to Python 3 transition I think. I'll fix this one (and your previous issue) tonight.
Thanks!

Resolving patch:
diff --git a/angr/analyses/backward_slice.py b/angr/analyses/backward_slice.py
--- a/angr/analyses/backward_slice.py
+++ b/angr/analyses/backward_slice.py
@@ -381,7 +381,9 @@ def _construct_default(self, targets):
             l.debug("Checking taint %s...", tainted_cl)
 
             # Mark it as picked
-            self._pick_statement(tainted_cl.block_addr, tainted_cl.stmt_idx)
+            if tainted_cl.block_addr is not None and tainted_cl.stmt_idx is not None:
+                # Skip SimProcedures
+                self._pick_statement(tainted_cl.block_addr, tainted_cl.stmt_idx)
 
             # Mark it as accessed
             accessed_taints.add(tainted_cl)
@@ -590,12 +592,18 @@ def _pick_statement(self, block_address, stmt_idx):
         """
         Include a statement in the final slice.
 
-        :param block_address:   Address of the basic block.
-        :param stmt_idx:        Statement ID.
+        :param int block_address:   Address of the basic block.
+        :param int stmt_idx:        Statement ID.
         """
 
         # TODO: Support context-sensitivity
 
+        # Sanity check
+        if not isinstance(block_address, int):
+            raise AngrBackwardSlicingError("Invalid block address %s." % block_address)
+        if not isinstance(stmt_idx, int):
+            raise AngrBackwardSlicingError("Invalid statement ID %s." % stmt_idx)
+
         self.chosen_statements[block_address].add(stmt_idx)
 
     def _pick_exit(self, block_address, stmt_idx, target_ips):
@@ -661,27 +669,16 @@ def _last_branching_statement(statements):
         """
         cmp_stmt_id = None
         cmp_tmp_id = None
-        all_statements = len(statements)
+        total_stmts = len(statements)
         statements = reversed(statements)
         for stmt_rev_idx, stmt in enumerate(statements):
-            stmt_idx = all_statements - stmt_rev_idx - 1
-            actions = stmt.actions
-            # Ugly implementation here
-            has_code_action = False
-            for a in actions:
-                if isinstance(a, SimActionExit):
-                    has_code_action = True
-                    break
-            if has_code_action:
-                readtmp_action = next(filter(lambda r: r.type == 'tmp' and r.action == 'read', actions), None)
-                if readtmp_action is not None:
-                    cmp_tmp_id = readtmp_action.tmp
-                    cmp_stmt_id = stmt_idx
-                    break
-                else:
-                    raise AngrBackwardSlicingError("ReadTempAction is not found. Please report to Fish.")
+            if isinstance(stmt, pyvex.IRStmt.Exit):
+                stmt_idx = total_stmts - stmt_rev_idx - 1
+                cmp_stmt_id = stmt_idx
+                cmp_tmp_id = stmt.guard.tmp
 
         return cmp_stmt_id, cmp_tmp_id
 
+
 from angr.analyses import AnalysesHub
 AnalysesHub.register_default('BackwardSlice', BackwardSlice)


Test patch:
diff --git a/tests/broken_slicing.py b/tests/test_slicing.py
similarity index 78%
rename from tests/broken_slicing.py
rename to tests/test_slicing.py
--- a/tests/broken_slicing.py
+++ b/tests/test_slicing.py
@@ -11,6 +11,7 @@
 import os
 test_location = str(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../binaries/tests"))
 
+
 def test_find_exits():
     slicing_test = angr.Project(test_location + "/x86_64/cfg_1",
                                 use_sim_procedures=True,
@@ -43,6 +44,7 @@ def test_find_exits():
         'default': [ 0x4005a4 ]
     })
 
+
 def test_control_flow_slicing():
     slicing_test = angr.Project(test_location + "/x86_64/cfg_1",
                                 use_sim_procedures=True,
@@ -61,16 +63,20 @@ def test_control_flow_slicing():
     nose.tools.assert_equal(anno_cfg.get_whitelisted_statements(0x400594), None)
     nose.tools.assert_equal(anno_cfg.get_whitelisted_statements(0x4005a4), [ ])
 
+
 def broken_backward_slice():
-    #TODO: Fix this test case. There seems to be a bug with CDG itself.
 
-    slicing_test = angr.Project(test_location + "/x86_64/cfg_1",
+    # TODO: Fix this test case
+
+    slicing_test = angr.Project(os.path.join(test_location, "x86_64", "cfg_1"),
                                 use_sim_procedures=True,
                                 default_analysis_mode='symbolic')
 
     l.info("Control Flow Slicing")
 
-    cfg = slicing_test.analyses.CFGEmulated(context_sensitivity_level=2, keep_state=True)
+    cfg = slicing_test.analyses.CFGEmulated(context_sensitivity_level=2,
+                                            keep_state=True,
+                                            state_add_options=angr.sim_options.refs)
     cdg = slicing_test.analyses.CDG(cfg=cfg)
     ddg = slicing_test.analyses.DDG(cfg=cfg)
 
@@ -94,6 +100,7 @@ def broken_backward_slice():
         [ 1, 2, 3, 5, 6, 11, 12, 13, 14, 15, 16, 17, 18, 19 ]
     )
 
+
 def test_last_branching_statement():
     slicing_test = angr.Project(test_location + '/armel/fauxware',
                                 use_sim_procedures=True)
@@ -125,22 +132,41 @@ def test_last_branching_statement():
     # t27 = 32to1(t25)
     # if (t27) { PUT(68) = 0x86f8; Ijk_Boring }
 
-    target_path = slicing_test.factory.path(slicing_test.factory.blank_state(addr=0x86dc))
-    target_path.step()
-    target = target_path.next_run
+    target_state = slicing_test.factory.blank_state(addr=0x86dc)
+    simgr = slicing_test.factory.simgr(target_state)
+    simgr.step()
+    target = simgr.active[0]
     l.debug("IRSB:")
-    for line in target.artifacts['irsb']._pp_str().split('\n'):
+    for line in target.scratch.irsb._pp_str().split('\n'):
         l.debug(line)
 
     bs = slicing_test.analyses.BackwardSlice(None, None, None, targets=[ (target, -1) ], no_construct=True)
 
-    stmt_idx, tmp = bs._last_branching_statement(target.statements)
+    stmt_idx, tmp = bs._last_branching_statement(target.scratch.irsb.statements)
 
     nose.tools.assert_equal(stmt_idx, 22)
     nose.tools.assert_equal(tmp, 27)
 
+
+def test_fauxware():
+    b = angr.Project(os.path.join(test_location, 'x86_64', 'fauxware'), auto_load_libs=False)
+    cfg = b.analyses.CFGEmulated(keep_state=True,
+                                 state_add_options=angr.sim_options.refs,
+                                 context_sensitivity_level=2)
+    cdg = b.analyses.CDG(cfg)
+    ddg = b.analyses.DDG(cfg)
+    target_func = cfg.kb.functions.function(name="exit")
+    target_node = cfg.get_any_node(target_func.addr)
+
+    bs = b.analyses.BackwardSlice(cfg, cdg=cdg, ddg=ddg, targets=[(target_node, -1)])
+
+    # Make sure dbg_repr() works
+    l.debug(bs.dbg_repr())
+
+
 if __name__ == "__main__":
+    test_fauxware()
     test_find_exits()
     test_last_branching_statement()
     test_control_flow_slicing()
-    #test_backward_slice()
+    # test_backward_slice()