Repository: angr/angr

Issue:
Semantics of preconstrainer interface is confusing or wrong
<!--
*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.](https://angr.io/invite/)*
-->

**Describe the bug.**
Consider:
```
preconstrainer.preconstrain(value, exp1)
preconstrainer.remove_preconstraints()
preconstrainer.preconstrain(value, exp2)
```

After this sequence of calls, I expect `exp2 == value`, but **not** `exp1 == value`.  Right now, angr constrains both.  If this is intentional, how do I obtain my desired behavior?  If not, that is the bug.

**Describe the solution you would like.**
<!--
A clear and concise description of what you want to happen.
-->
My personal preference would be to adjust the preconstrainer to match my expected behavior.  Also I would like 10 BTC and a unicorn.

**Please include a sample of what *should* work if this feature is implemented.**
<!--
If this is related to a Python user interface/experience feature, please include an example of what this may look like.
If this is related to a certain kind of binary program, please attach one if possible.
-->
See above.

**Describe alternatives you have considered.**
<!--
A clear and concise description of any alternative solutions or features you've considered.
-->
I am going to manually clear `preconstrainer.preconstraints` and `preconstrainer.variable_map` but I shouldn't have to do that as the user.



Discussion:
The intended behavior of remove_preconstraints is not to discard all preconstriaints, but rather, to pull the preconstraints out of the solver to free up the state space, perhaps temporarily. One example of how this is useful is the `reconstraint` method, which re-adds subsets of the preconstraints based on which independent variable sets are difficult to solve.

I can add a warning when you preconstraint the same thing twice, but you'd be better off migrating your constraints to a different solver if you want to add different preconstraints.

Resolving patch:
diff --git a/angr/state_plugins/preconstrainer.py b/angr/state_plugins/preconstrainer.py
--- a/angr/state_plugins/preconstrainer.py
+++ b/angr/state_plugins/preconstrainer.py
@@ -64,12 +64,14 @@ def preconstrain(self, value, variable):
         l.debug("Preconstraint: %s", constraint)
 
         # add the constraint for reconstraining later
+        if next(iter(variable.variables)) in self.variable_map:
+            l.warning("%s is already preconstrained. Are you misusing preconstrainer?", next(iter(variable.variables)))
         self.variable_map[next(iter(variable.variables))] = constraint
         self.preconstraints.append(constraint)
         if o.REPLACEMENT_SOLVER in self.state.options:
             self.state.solver._solver.add_replacement(variable, value, invalidate_cache=False)
         else:
-            self.state.add_constraints(*self.preconstraints)
+            self.state.add_constraints(constraint)
         if not self.state.satisfiable():
             l.warning("State went unsat while adding preconstraints")
 


Test patch: