Skip to content

Commit b0c0df3

Browse files
committed
Early abort flag
Update No need for this Update Update Faster early abort
1 parent c14c125 commit b0c0df3

File tree

4 files changed

+61
-27
lines changed

4 files changed

+61
-27
lines changed

cli/cli.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ data CommonOptions = CommonOptions
101101
, noSimplify ::Bool
102102
, onlyDeployed ::Bool
103103
, cacheDir ::Maybe String
104+
, earlyAbort ::Bool
104105
}
105106

106107
commonOptions :: Parser CommonOptions
@@ -130,6 +131,7 @@ commonOptions = CommonOptions
130131
<*> (switch $ long "no-simplify" <> help "Don't perform simplification of expressions")
131132
<*> (switch $ long "only-deployed" <> help "When trying to resolve unknown addresses, only use addresses of deployed contracts")
132133
<*> (optional $ strOption $ long "cache-dir" <> help "Directory to save and load RPC cache")
134+
<*> (switch $ long "early-abort" <> help "Stop exploration immediately upon finding the first counterexample")
133135

134136
data CommonExecOptions = CommonExecOptions
135137
{ address ::Maybe Addr
@@ -372,6 +374,7 @@ main = do
372374
, verb = cOpts.verb
373375
, simp = Prelude.not cOpts.noSimplify
374376
, onlyDeployed = cOpts.onlyDeployed
377+
, earlyAbort = cOpts.earlyAbort
375378
} }
376379

377380

src/EVM/Effects.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ data Config = Config
4646
, verb :: Int
4747
, simp :: Bool
4848
, onlyDeployed :: Bool
49+
, earlyAbort :: Bool
4950
}
5051
deriving (Show, Eq)
5152

@@ -65,6 +66,7 @@ defaultConfig = Config
6566
, verb = 0
6667
, simp = True
6768
, onlyDeployed = False
69+
, earlyAbort = False
6870
}
6971

7072
-- Write to the console

src/EVM/SymExec.hs

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import Control.Concurrent.Spawn (parMapIO, pool)
1414
import Control.Concurrent.STM (writeTChan, newTChan, TChan, readTChan, atomically, isEmptyTChan, STM)
1515
import Control.Concurrent.STM.TVar (TVar, newTVarIO, modifyTVar, readTVar, writeTVar)
1616
import Control.Concurrent.STM.TMVar (TMVar, putTMVar, takeTMVar, newEmptyTMVarIO)
17-
import Control.Monad (when, forM_, forM, forever)
17+
import Control.Monad (when, unless, forM_, forM, forever, void)
1818
import Control.Monad.Loops (whileM)
1919
import Control.Monad.IO.Unlift
2020
import Control.Monad.Operational qualified as Operational
@@ -330,6 +330,7 @@ data InterpTask m a = InterpTask
330330
, numTasks :: TVar Natural
331331
, stepper :: Stepper Symbolic (Expr End)
332332
, handler :: Expr End -> m a
333+
, shouldAbort :: TVar Bool
333334
}
334335

335336
data Process m a = Process
@@ -341,10 +342,11 @@ interpret :: forall m a . App m
341342
=> Fetch.Fetcher Symbolic m
342343
-> IterConfig
343344
-> VM Symbolic
345+
-> TVar Bool
344346
-> Stepper Symbolic (Expr End)
345347
-> (Expr End -> m a)
346348
-> m [a]
347-
interpret fetcher iterConf vm stepper handler = do
349+
interpret fetcher iterConf vm shouldAbort stepper handler = do
348350
conf <- readConfig
349351
taskQ <- liftIO newChan
350352
processQ <- liftIO newChan
@@ -382,6 +384,7 @@ interpret fetcher iterConf vm stepper handler = do
382384
, numTasks = numTasks
383385
, stepper = stepper
384386
, handler = handler
387+
, shouldAbort = shouldAbort
385388
}
386389
liftIO $ writeChan taskQ interpTask
387390

@@ -398,29 +401,37 @@ interpret fetcher iterConf vm stepper handler = do
398401
taskOrchestrate :: App m
399402
=> Chan (InterpTask m a)
400403
-> Chan () -> Chan (Process m a)
401-
-> TVar Natural -> TVar Natural -> m b
404+
-> TVar Natural -> TVar Natural -> m ()
402405
taskOrchestrate taskQ avail processQ numTasks numProcs = forever $ do
403406
_ <- liftIO $ readChan avail
404407
task <- liftIO $ readChan taskQ
405-
runTask' <- toIO $ getOneExpr task avail processQ numTasks numProcs
406-
liftIO $ forkIO runTask'
408+
abortFlag <- liftIO $ atomically $ readTVar shouldAbort
409+
if abortFlag
410+
then liftIO $ writeChan avail ()
411+
else do
412+
runTask' <- toIO $ getOneExpr task avail processQ numTasks numProcs
413+
void $ liftIO $ forkIO runTask'
407414

408415
-- processing orchestrator loop
409-
processOrchestrate :: App m => Chan (Process m a) -> Chan () -> TChan a -> TVar Natural -> TVar Natural -> TMVar () -> m b
410-
processOrchestrate processQ availProcessors resChan numProcs numTasks allProcessDone = forever $ do
411-
_ <- liftIO $ readChan availProcessors
416+
processOrchestrate :: App m => Chan (Process m a) -> Chan () -> TChan a -> TVar Natural -> TVar Natural -> TMVar () -> m ()
417+
processOrchestrate processQ avail resChan numProcs numTasks allProcessDone = forever $ do
418+
_ <- liftIO $ readChan avail
412419
proc <- liftIO $ readChan processQ
413-
runProcess' <- toIO $ processOne proc availProcessors resChan numProcs numTasks allProcessDone
414-
liftIO $ forkIO runProcess'
420+
abortFlag <- liftIO $ atomically $ readTVar shouldAbort
421+
if abortFlag
422+
then liftIO $ writeChan avail ()
423+
else do
424+
runProcess' <- toIO $ processOne proc avail resChan numProcs numTasks allProcessDone
425+
void $ liftIO $ forkIO runProcess'
415426

416427
-- process one task
417428
processOne :: App m => Process m a -> Chan () -> TChan a -> TVar Natural -> TVar Natural -> TMVar () -> m ()
418-
processOne task availProcs resChan numProcs numTasks allProcessDone = do
429+
processOne task avail resChan numProcs numTasks allProcessDone = do
419430
processed <- task.handler task.result
420431
liftIO . atomically $ writeTChan resChan processed
421432

422433
-- Return instance to pool immediately after processing
423-
liftIO $ writeChan availProcs ()
434+
liftIO $ writeChan avail ()
424435

425436
-- Decrement and check if all done
426437
liftIO $ atomically $ do
@@ -486,19 +497,25 @@ interpretInternal t@InterpTask{..} = eval (Operational.view stepper)
486497
runOne frozen newDepth (v:rest) = do
487498
conf <- readConfig
488499
(ra, vma) <- liftIO $ stToIO $ runStateT (continue v) frozen { result = Nothing, exploreDepth = newDepth }
489-
liftIO $ atomically $ modifyTVar numTasks (+1)
490-
when (conf.debug && conf.verb >=2) $ liftIO $ putStrLn $ "Queuing new task for ForkMany at depth " <> show newDepth
491-
liftIO $ writeChan taskQ t { vm = vma, stepper = (k ra) }
500+
-- Check abort flag before queuing new task
501+
abortFlag <- liftIO $ atomically $ readTVar shouldAbort
502+
unless (conf.earlyAbort && abortFlag) $ do
503+
liftIO $ atomically $ modifyTVar numTasks (+1)
504+
when (conf.debug && conf.verb >=2) $ liftIO $ putStrLn $ "Queuing new task for ForkMany at depth " <> show newDepth
505+
liftIO $ writeChan taskQ t { vm = vma, stepper = (k ra) }
492506
runOne frozen newDepth rest
493507
runOne _ _ [] = internalError "unreachable"
494508
Stepper.Fork (PleaseRunBoth continue) -> do
495509
conf <- readConfig
496510
frozen <- liftIO $ stToIO $ freezeVM vm
497511
let newDepth = vm.exploreDepth+1
498512
(ra, vma) <- liftIO $ stToIO $ runStateT (continue True) frozen { result = Nothing, exploreDepth = newDepth }
499-
liftIO $ atomically $ modifyTVar numTasks (+1)
500-
liftIO $ writeChan taskQ $ t { vm = vma, stepper = (k ra) }
501-
when (conf.debug && conf.verb >= 2) $ liftIO $ putStrLn $ "Queued new task for Fork at depth " <> show newDepth
513+
-- Check abort flag before queuing new task
514+
abortFlag <- liftIO $ atomically $ readTVar shouldAbort
515+
unless (conf.earlyAbort && abortFlag) $ do
516+
liftIO $ atomically $ modifyTVar numTasks (+1)
517+
liftIO $ writeChan taskQ $ t { vm = vma, stepper = (k ra) }
518+
when (conf.debug && conf.verb >= 2) $ liftIO $ putStrLn $ "Queued new task for Fork at depth " <> show newDepth
502519

503520
(rb, vmb) <- liftIO $ stToIO $ runStateT (continue False) frozen { result = Nothing, exploreDepth = newDepth }
504521
when (conf.debug && conf.verb >=2) $ liftIO $ putStrLn $ "Continuing task for Fork at depth " <> show newDepth
@@ -613,7 +630,8 @@ getExprEmptyStore solvers c signature' concreteArgs opts = do
613630
conf <- readConfig
614631
calldata <- mkCalldata signature' concreteArgs
615632
preState <- liftIO $ stToIO $ loadEmptySymVM (RuntimeCode (ConcreteRuntimeCode c)) (Lit 0) calldata
616-
paths <- interpret (Fetch.oracle solvers Nothing opts.rpcInfo) opts.iterConf preState runExpr pure
633+
shouldAbort <- liftIO $ newTVarIO False
634+
paths <- interpret (Fetch.oracle solvers Nothing opts.rpcInfo) opts.iterConf preState shouldAbort runExpr pure
617635
if conf.simp then (pure $ map Expr.simplify paths) else pure paths
618636

619637
-- Used only in testing
@@ -629,7 +647,8 @@ getExpr solvers c signature' concreteArgs opts = do
629647
conf <- readConfig
630648
calldata <- mkCalldata signature' concreteArgs
631649
preState <- liftIO $ stToIO $ abstractVM calldata c Nothing False
632-
paths <- interpret (Fetch.oracle solvers Nothing opts.rpcInfo) opts.iterConf preState runExpr pure
650+
shouldAbort <- liftIO $ newTVarIO False
651+
paths <- interpret (Fetch.oracle solvers Nothing opts.rpcInfo) opts.iterConf preState shouldAbort runExpr pure
633652
if conf.simp then (pure $ map Expr.simplify paths) else pure paths
634653

635654
{- | Checks if an assertion violation has been encountered
@@ -714,7 +733,8 @@ exploreContract solvers theCode signature' concreteArgs opts maybepre = do
714733
calldata <- mkCalldata signature' concreteArgs
715734
preState <- liftIO $ stToIO $ abstractVM calldata theCode maybepre False
716735
let fetcher = Fetch.oracle solvers Nothing opts.rpcInfo
717-
executeVM fetcher opts.iterConf preState pure
736+
shouldAbort <- liftIO $ newTVarIO False
737+
executeVM fetcher opts.iterConf preState shouldAbort pure
718738

719739
-- | Stepper that parses the result of Stepper.runFully into an Expr End
720740
runExpr :: Stepper.Stepper Symbolic (Expr End)
@@ -779,8 +799,8 @@ getPartials = mapMaybe go
779799

780800

781801
-- | Symbolically execute the VM and return the representention of the execution
782-
executeVM :: forall m a . App m => Fetch.Fetcher Symbolic m -> IterConfig -> VM Symbolic -> (Expr End -> m a) -> m [a]
783-
executeVM fetcher iterConfig preState handlePath = interpret fetcher iterConfig preState runExpr handlePath
802+
executeVM :: forall m a . App m => Fetch.Fetcher Symbolic m -> IterConfig -> VM Symbolic -> TVar Bool -> (Expr End -> m a) -> m [a]
803+
executeVM fetcher iterConfig preState shouldAbort handlePath = interpret fetcher iterConfig preState shouldAbort runExpr handlePath
784804

785805
-- | Symbolically execute the VM and check all endstates against the
786806
-- postcondition, if available.
@@ -824,7 +844,8 @@ verifyInputsWithHandler solvers opts fetcher preState post cexHandler = do
824844
putStrLn $ " Keccak preimages in state: " <> (show $ length preState.keccakPreImgs)
825845
putStrLn $ " Exploring call " <> call
826846

827-
results <- executeVM fetcher opts.iterConf preState $ \leaf -> do
847+
shouldAbort <- liftIO $ newTVarIO False
848+
results <- executeVM fetcher opts.iterConf preState shouldAbort $ \leaf -> do
828849
-- Extract partial if applicable
829850
let mPartial = case leaf of
830851
Partial _ _ p -> Just (p, leaf)
@@ -838,7 +859,10 @@ verifyInputsWithHandler solvers opts fetcher preState post cexHandler = do
838859
when (conf.debug && conf.verb >=2) $ liftIO $ putStrLn $ " Checking leaf with props: " <> show props <> " SMT result: " <> show res
839860
-- Call custom handler if provided (for immediate Cex processing/validation/printing)
840861
case (cexHandler, res) of
841-
(Just handler, cex@(Cex _)) -> handler preState cex leaf
862+
(Just handler, cex@(Cex _)) -> do
863+
handler preState cex leaf
864+
when conf.earlyAbort $ liftIO $ atomically $ writeTVar shouldAbort True
865+
(_, (Cex _)) -> when conf.earlyAbort $ liftIO $ atomically $ writeTVar shouldAbort True
842866
_ -> pure ()
843867
pure (res, leaf)
844868
else pure (Qed, leaf)
@@ -936,7 +960,8 @@ equivalenceCheck solvers sess bytecodeA bytecodeB opts calldata create = do
936960
getBranches bs = do
937961
let bytecode = if BS.null bs then BS.pack [0] else bs
938962
prestate <- liftIO $ stToIO $ abstractVM calldata bytecode Nothing create
939-
interpret (Fetch.oracle solvers sess Fetch.noRpc) opts.iterConf prestate runExpr pure
963+
shouldAbort <- liftIO $ newTVarIO False
964+
interpret (Fetch.oracle solvers sess Fetch.noRpc) opts.iterConf prestate shouldAbort runExpr pure
940965
filterQeds (EqIssues res partials) = EqIssues (filter (\(r, _) -> not . isQed $ r) res) partials
941966

942967
rewriteFresh :: Text -> [Expr a] -> [Expr a]

test/EVM/Test/FuzzSymExec.hs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ concretely through Expr.simplify, then check that against evmtool's output.
1212
-}
1313
module EVM.Test.FuzzSymExec where
1414

15+
import Control.Concurrent.STM.TVar (newTVarIO)
1516
import Control.Monad (when)
1617
import Control.Monad.IO.Unlift
1718
import Control.Monad.ST (ST, stToIO, RealWorld)
@@ -23,6 +24,7 @@ import Data.ByteString (ByteString)
2324
import Data.ByteString qualified as BS
2425
import Data.ByteString.Char8 qualified as Char8
2526
import Data.Maybe (fromJust, isJust, mapMaybe)
27+
2628
import Data.Map.Strict qualified as Map
2729
import Data.Text.IO qualified as T
2830
import Data.Vector qualified as Vector
@@ -406,7 +408,9 @@ runCodeWithTrace rpcinfo evmEnv alloc txn fromAddr toAddress = withSolvers Z3 0
406408
code' = alloc.code
407409
iterConf = IterConfig { maxIter = Nothing, askSmtIters = 1, loopHeuristic = Naive }
408410
fetcherSym = Fetch.oracle solvers Nothing rpcinfo
409-
buildExpr vm = interpret fetcherSym iterConf vm runExpr pure
411+
buildExpr vm = do
412+
abortFlag <- liftIO $ newTVarIO False
413+
interpret fetcherSym iterConf vm abortFlag runExpr pure
410414
origVM <- liftIO $ stToIO $ vmForRuntimeCode code' calldata' evmEnv alloc txn fromAddr toAddress
411415
expr <- buildExpr $ symbolify origVM
412416

0 commit comments

Comments
 (0)