Skip to content

Commit 7157051

Browse files
committed
Don't dispatch SMT queries that can be proven False via simp
Using a function specifically tailored to do this. Thanks to @blishko for the help and idea! Cleanup
1 parent 27aefc5 commit 7157051

File tree

5 files changed

+56
-48
lines changed

5 files changed

+56
-48
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3939
so we don't get too large buffers as counterexamples
4040
- More symbolic overapproximation for Balance and ExtCodeHash opcodes, fixing
4141
CodeHash SMT representation
42-
42+
- We no longer dispatch Props to SMT that can be solved by a simplification
4343

4444
## Fixed
4545
- We now try to simplify expressions fully before trying to cast them to a concrete value

src/EVM/Fetch.hs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -270,19 +270,20 @@ getSolutions solvers symExprPreSimp numBytes pathconditions = do
270270
-- will be pruned anyway.
271271
checkBranch :: App m => SolverGroup -> Prop -> Prop -> m BranchCondition
272272
checkBranch solvers branchcondition pathconditions = do
273-
conf <- readConfig
274-
liftIO $ checkSat solvers (assertProps conf [(branchcondition .&& pathconditions)]) >>= \case
273+
let props = [pathconditions .&& branchcondition]
274+
checkSatWithProps solvers props >>= \case
275275
-- the condition is unsatisfiable
276-
Unsat -> -- if pathconditions are consistent then the condition must be false
276+
(Unsat, _) -> -- if pathconditions are consistent then the condition must be false
277277
pure $ Case False
278278
-- Sat means its possible for condition to hold
279-
Sat _ -> do -- is its negation also possible?
280-
checkSat solvers (assertProps conf [(pathconditions .&& (PNeg branchcondition))]) >>= \case
279+
(Sat {}, _) -> do -- is its negation also possible?
280+
let propsNeg = [pathconditions .&& (PNeg branchcondition)]
281+
checkSatWithProps solvers propsNeg >>= \case
281282
-- No. The condition must hold
282-
Unsat -> pure $ Case True
283+
(Unsat, _) -> pure $ Case True
283284
-- Yes. Both branches possible
284-
Sat _ -> pure EVM.Types.Unknown
285-
-- If the query times out, or can't be executed (e.g. symbolic copyslice) we simply explore both paths
285+
(Sat {}, _) -> pure EVM.Types.Unknown
286+
-- If the query times out, or can't be executed (e.g. symbolic copyslice) we simply explore both paths
286287
_ -> pure EVM.Types.Unknown
287288
-- If the query times out, or can't be executed (e.g. symbolic copyslice) we simply explore both paths
288289
_ -> pure EVM.Types.Unknown

src/EVM/Solvers.hs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import EVM.Fuzz (tryCexFuzz)
3030
import Numeric (readHex)
3131
import Data.Bits ((.&.))
3232
import Numeric (showHex)
33+
import EVM.Expr (simplifyProps)
3334

3435
import EVM.SMT
3536
import EVM.Types
@@ -106,6 +107,19 @@ checkMulti (SolverGroup taskQueue) smt2 multiSol = do
106107
-- collect result
107108
readChan resChan
108109

110+
checkSatWithProps :: App m => SolverGroup -> [Prop] ->m (CheckSatResult, Err SMT2)
111+
checkSatWithProps (SolverGroup taskQueue) props = do
112+
conf <- readConfig
113+
let psSimp = simplifyProps props
114+
if psSimp == [PBool False] then pure (Unsat, Right mempty)
115+
else do
116+
let smt2 = assertProps conf psSimp
117+
if isLeft smt2 then
118+
let err = getError smt2 in pure (EVM.Solvers.Unknown err, Left err)
119+
else do
120+
res <- liftIO $ checkSat (SolverGroup taskQueue) smt2
121+
pure (res, Right (getNonError smt2))
122+
109123
checkSat :: SolverGroup -> Err SMT2 -> IO CheckSatResult
110124
checkSat (SolverGroup taskQueue) smt2 = do
111125
if isLeft smt2 then pure $ Error $ getError smt2

src/EVM/SymExec.hs

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -550,8 +550,7 @@ flattenExpr = go []
550550
-- TODO: handle errors properly
551551
reachable :: App m => SolverGroup -> Expr End -> m ([SMT2], Expr End)
552552
reachable solvers e = do
553-
conf <- readConfig
554-
res <- liftIO $ go conf [] e
553+
res <- go [] e
555554
pure $ second (fromMaybe (internalError "no reachable paths found")) res
556555
where
557556
{-
@@ -560,24 +559,23 @@ reachable solvers e = do
560559
If reachable return the expr wrapped in a Just. If not return Nothing.
561560
When walking back up the tree drop unreachable subbranches.
562561
-}
563-
go :: Config -> [Prop] -> Expr End -> IO ([SMT2], Maybe (Expr End))
564-
go conf pcs = \case
562+
go :: (App m, MonadUnliftIO m) => [Prop] -> Expr End -> m ([SMT2], Maybe (Expr End))
563+
go pcs = \case
565564
ITE c t f -> do
566-
(tres, fres) <- concurrently
567-
(go conf (PEq (Lit 1) c : pcs) t)
568-
(go conf (PEq (Lit 0) c : pcs) f)
565+
(tres, fres) <- withRunInIO $ \env -> concurrently
566+
(env $ go (PEq (Lit 1) c : pcs) t)
567+
(env $ go (PEq (Lit 0) c : pcs) f)
569568
let subexpr = case (snd tres, snd fres) of
570569
(Just t', Just f') -> Just $ ITE c t' f'
571570
(Just t', Nothing) -> Just t'
572571
(Nothing, Just f') -> Just f'
573572
(Nothing, Nothing) -> Nothing
574573
pure (fst tres <> fst fres, subexpr)
575574
leaf -> do
576-
let query = assertProps conf pcs
577-
res <- checkSat solvers query
575+
(res, smt2) <- checkSatWithProps solvers pcs
578576
case res of
579-
Sat _ -> pure ([getNonError query], Just leaf)
580-
Unsat -> pure ([getNonError query], Nothing)
577+
Sat _ -> pure ([getNonError smt2], Just leaf)
578+
Unsat -> pure ([getNonError smt2], Nothing)
581579
r -> internalError $ "Invalid solver result: " <> show r
582580

583581
-- | Extract constraints stored in Expr End nodes
@@ -766,35 +764,32 @@ equivalenceCheck' solvers branchesA branchesB = do
766764
-- the solver if we can determine unsatisfiability from the cache already
767765
-- the last element of the returned tuple indicates whether the cache was
768766
-- used or not
769-
check :: Config -> UnsatCache -> (Set Prop) -> IO (EquivResult, Bool)
770-
check conf knownUnsat props = do
771-
let smt = assertProps conf (Set.toList props)
772-
ku <- readTVarIO knownUnsat
773-
res <- if subsetAny props ku
774-
then pure (True, Unsat)
775-
else (fmap ((False),) (checkSat solvers smt))
767+
check :: App m => UnsatCache -> Set Prop -> m (EquivResult, Bool)
768+
check knownUnsat props = do
769+
ku <- liftIO $ readTVarIO knownUnsat
770+
res <- if subsetAny props ku then pure (True, Unsat)
771+
else do
772+
(res, _) <- checkSatWithProps solvers $ Set.toList props
773+
pure (False, res)
776774
case res of
777775
(_, Sat x) -> pure (Cex x, False)
778-
(quick, Unsat) ->
779-
case quick of
780-
True -> pure (Qed (), quick)
781-
False -> do
782-
-- nb: we might end up with duplicates here due to a
783-
-- potential race, but it doesn't matter for correctness
784-
atomically $ readTVar knownUnsat >>= writeTVar knownUnsat . (props :)
785-
pure (Qed (), False)
776+
(True, Unsat) -> pure (Qed (), True)
777+
(False, Unsat) -> do
778+
-- nb: we might end up with duplicates here due to a
779+
-- potential race, but it doesn't matter for correctness
780+
liftIO $ atomically $ readTVar knownUnsat >>= writeTVar knownUnsat . (props :)
781+
pure (Qed (), False)
786782
(_, EVM.Solvers.Unknown _) -> pure (EVM.SymExec.Unknown (), False)
787783
(_, EVM.Solvers.Error txt) -> pure (EVM.SymExec.Error txt, False)
788784

789785
-- Allows us to run it in parallel. Note that this (seems to) run it
790786
-- from left-to-right, and with a max of K threads. This is in contrast to
791787
-- mapConcurrently which would spawn as many threads as there are jobs, and
792788
-- run them in a random order. We ordered them correctly, though so that'd be bad
793-
checkAll :: App m => [(Set Prop)] -> UnsatCache -> Int -> m [(EquivResult, Bool)]
794-
checkAll input cache numproc = do
795-
conf <- readConfig
796-
wrap <- liftIO $ pool numproc
797-
liftIO $ parMapIO (wrap . (check conf cache)) input
789+
checkAll :: (App m, MonadUnliftIO m) => [(Set Prop)] -> UnsatCache -> Int -> m [(EquivResult, Bool)]
790+
checkAll input cache numproc = withRunInIO $ \env -> do
791+
wrap <- pool numproc
792+
parMapIO (\e -> wrap (env $ check cache e)) input
798793

799794

800795
-- Takes two branches and returns a set of props that will need to be

test/test.hs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1770,7 +1770,7 @@ tests = testGroup "hevm"
17701770
|]
17711771
(e, [Qed _]) <- withDefaultSolver $
17721772
\s -> checkAssert s defaultPanicCodes c (Just (Sig "fun(uint256)" [AbiUIntType 256])) [] (defaultVeriOpts{ maxIter = Just 5 })
1773-
assertBoolM "The expression is not partial" $ Expr.containsNode isPartial e
1773+
assertBoolM "The expression MUST be partial" $ Expr.containsNode isPartial e
17741774
, test "inconsistent-paths" $ do
17751775
Just c <- solcRuntime "C"
17761776
[i|
@@ -1829,9 +1829,8 @@ tests = testGroup "hevm"
18291829
-- askSmtIters is low enough here to avoid the inconsistent path
18301830
-- conditions, so we never hit maxIters
18311831
opts = defaultVeriOpts{ maxIter = Just 5, askSmtIters = 1 }
1832-
(e, [Qed _]) <- withDefaultSolver $
1833-
\s -> checkAssert s defaultPanicCodes c sig [] opts
1834-
assertBoolM "The expression is partial" $ not (Expr.containsNode isPartial e)
1832+
(e, [Qed _]) <- withDefaultSolver $ \s -> checkAssert s defaultPanicCodes c sig [] opts
1833+
assertBoolM "The expression MUST NOT be partial" $ not (Expr.containsNode isPartial e)
18351834
]
18361835
, testGroup "Symbolic Addresses"
18371836
[ test "symbolic-address-create" $ do
@@ -4590,16 +4589,15 @@ checkEquivAndLHS orig simp = do
45904589

45914590
checkEquivBase :: (Eq a, App m) => (a -> a -> Prop) -> a -> a -> Bool -> m (Maybe Bool)
45924591
checkEquivBase mkprop l r expect = do
4593-
withSolvers Z3 1 1 (Just 1) $ \solvers -> liftIO $ do
4594-
let smt = assertPropsNoSimp [mkprop l r]
4595-
res <- checkSat solvers smt
4592+
withSolvers Z3 1 1 (Just 1) $ \solvers -> do
4593+
(res, _) <- checkSatWithProps solvers [mkprop l r]
45964594
let
45974595
ret = case res of
45984596
Unsat -> Just True
45994597
Sat _ -> Just False
46004598
EVM.Solvers.Error _ -> Just (not expect)
46014599
EVM.Solvers.Unknown _ -> Nothing
4602-
when (ret == Just (not expect)) $ print res
4600+
when (ret == Just (not expect)) $ liftIO $ print res
46034601
pure ret
46044602

46054603
-- | Takes a runtime code and calls it with the provided calldata

0 commit comments

Comments
 (0)