-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sBranch is incompatible with sharing #180
Comments
Here's the import Data.SBV
foo :: SWord8 -> SWord8
foo x = ite (x .== 0) y (y+1)
where y = sAssert "x must be zero" (x .== 0) x We get: *Main> safe foo
No safety violations detected.
*Main> foo 0
0 :: SWord8
*Main> foo 1
*** Exception: Assertion failure: "x must be zero"
*** Fails in all assignments to inputs for very much the same reasons. So; sharing must be turned off in the presence of |
.. which allows for sBranch/sAssert. Note that this is preliminary, and partially addresses #180. Not quite done yet.
Turns out the problem is actually much deeper. Consider this expression: foo :: SWord8 -> SWord8
foo x = sBranch (x .== 0) y (y+1)
where y = sBranch (x .== 0) x (x+1) The problem here is not just the way SBV internally caches expressions. While that is indeed a problem, we also are facing the fact that the "Haskell" variable 'y' is cached as well! So, once the inner What this is saying is that |
After some more thought; there's just no easy way to implement Instead, we shall add a function Note that this is quite a different approach: The |
Current implementation of
sBranch
interacts badly with the stable-named based sharing. The current implementation is unsound. To wit, consider:Note the nested
sBranch
calls here. A nested if-then-else structure is necessary to exhibit this bug. Here's what happens:sBranch
runs. SBV asks the solver whetherx .== 0
is satisfiable. It of course is. Then it asks the solver whether it's negation is satisfiable, i.e.,bnot (x .== 0)
which also is satisfiable.then
branch, recording the path condition thatx .== 0
holds. Under this assumption the value ofy
is determined correctly to bex
. This value is returned; and is recorded as part of the regular sharing for the expressiony
.else
branch, i.e.,y+1
. When this computation needs the value ofy
, SBV looks up the shared value ofy
and incorrectly takes it to bex
again. The else-branch of the secondsBranch
is skipped, and the else-branch of the first thus evaluates tox+1
. This is a major bug, since sharing this value is only valid under the assumptionx .== 0
, which no longer holds.foo
is then computed to be simplyite (x .== 0) x (x+1)
Which is plain wrong:
The funny thing is that I damn well knew sharing under branch conditions were unsound, and wrote a long commentary for it long time ago when
ite
was implemented: https://github.com/LeventErkok/sbv/blob/master/Data/SBV/BitVectors/Operations.hs#L371But when Cryptol implemented the "smart" if-then-else and I implemented the same in SBV, I completely missed the point that the exact same problem is in
sBranch
as well.Long story short; sharing without being careful about path-conditions is just unsound. While the sharing implementation can be changed to support path conditions, I'm wary of its more general implications for performance; as
sBranch
is not that often used.sAssert
suffers from the same problem, as it also does computations under path-assumption.Proposed solution I'm leaning towards still supporting
sBranch/sAssert
but turning off all sharing ifsBranch/sAssert
are used. This will be a dynamic check; the types of these functions unfortunately don't allow state to be accessible at the time of use. So, we'll have a new parameter as part ofSMTConfig
:interactive
, which will default toFalse
False
we shall dynamically rejectsBranch
/sAssert
callsTrue
we will supportsBranch
/sAssert
but sharing will be turned-off to preserve soundness.@brianhuffman I'd like to hear your thoughts on the proposed solution.
The text was updated successfully, but these errors were encountered: