Repository: angr/angr

Issue:
Errored state getting pruned
Hi,
I went through something a bit tricky, so I wanted to check if this is a mistake or the intended behaviour.
I had a program running in a simulation manager, and a breakpoint was fired. Inside the breakpoint function, I was using the `solver.eval()` method without checking the expression's satisfiability (shame on me), so an unsat error was raised. The error got silently caught inside `SimulationManager.step_state()` which pruned the state.
Shouldn't this state go to the "errored" stash instead?


Discussion:
Any states raising exceptions inside breakpoint functions that are invoked by `step_state()` should generally go to the `errored` stash. What you described might be a result of another exception handler in the call stack catching the unsat error raised in your breakpoint function.

I can only guess what might be going on. If you post a script and your binary that reproduce this issue, I can take a look at what went wrong.
Here is how to trigger the bug :

``` python
import angr
import claripy


def sim_proc_bp(state):
    bvs = claripy.BVS("test", 32)
    state.solver.eval(bvs, extra_constraints=[1 == 0])  # unsat error


def trigger_bug():

    proj = angr.Project("hello_world")

    entry = proj.factory.entry_state()
    # any bp would do
    entry.inspect.b("simprocedure", angr.BP_BEFORE, action=sim_proc_bp)

    sim = proj.factory.simulation_manager(entry)
    sim.run()

    print("errored :", len(sim.errored))
    print("pruned :", len(sim.pruned))


if __name__ == "__main__":
    trigger_bug()
```

For the binary, any would do (here, I used a simple "Hello World").
I believe the culprit is [this stanza in sim_manager.py](https://github.com/angr/angr/blob/b4f439781d250bb020b108c2c4a434b74ba264c4/angr/sim_manager.py#L407). It seems to have the exact behavior which you're describing, which suggests that someone in the past thought this was desirable. I do agree that this is at the very least unintuivive. I'm going to try removing this and seeing if any tests fail.

Resolving patch:
diff --git a/angr/sim_manager.py b/angr/sim_manager.py
--- a/angr/sim_manager.py
+++ b/angr/sim_manager.py
@@ -14,6 +14,7 @@
 from .sim_state import SimState
 from .state_hierarchy import StateHierarchy
 from .errors import AngrError, SimUnsatError, SimulationManagerError
+from .sim_options import LAZY_SOLVES
 
 l = logging.getLogger(name=__name__)
 
@@ -424,11 +425,16 @@ def step_state(self, state, successor_func=None, **run_args):
                        'unsat': successors.unsat_successors,
                        'unconstrained': successors.unconstrained_successors}
 
-        except (SimUnsatError, claripy.UnsatError):
+        except (SimUnsatError, claripy.UnsatError) as e:
+            if LAZY_SOLVES not in state.options:
+                self._errored.append(ErrorRecord(state, e, sys.exc_info()[2]))
+                stashes = {}
+            else:
+                stashes = {'pruned': [state]}
+
             if self._hierarchy:
                 self._hierarchy.unreachable_state(state)
                 self._hierarchy.simplify()
-            stashes = {'pruned': [state]}
 
         except tuple(self._resilience) as e:
             self._errored.append(ErrorRecord(state, e, sys.exc_info()[2]))


Test patch: