diff --git a/lib/Echidna/Types/Agent.hs b/lib/Echidna/Types/Agent.hs index ebcd58e94..53bea0a22 100644 --- a/lib/Echidna/Types/Agent.hs +++ b/lib/Echidna/Types/Agent.hs @@ -31,6 +31,27 @@ data Agent , stateRef :: IORef WorkerState } +-- | Construct a fuzzing agent with an explicit state publication reference. +-- +-- Keeping the reference as an argument lets timeout-triggered auxiliary passes +-- use a throwaway reference instead of overwriting the reporting state. +mkFuzzerAgent + :: VM Concrete + -> GenDict + -> Int + -> IORef WorkerState + -> [(FilePath, [Tx])] + -> Int + -> Agent +mkFuzzerAgent vm dict workerId stateRef initialCorpus testLimit = + FuzzerAgent { fuzzerId = workerId + , initialVm = vm + , initialDict = dict + , initialCorpus + , testLimit + , stateRef + } + -- | The worker id this agent runs as. There is at most one symbolic worker and -- it is always worker 0. workerIdOf :: Agent -> WorkerId diff --git a/lib/Echidna/UI.hs b/lib/Echidna/UI.hs index 582f603a3..c9f89f99d 100644 --- a/lib/Echidna/UI.hs +++ b/lib/Echidna/UI.hs @@ -38,7 +38,7 @@ import Echidna.Output.Corpus (saveCorpusEvent) import Echidna.Output.JSON qualified import Echidna.Server (runSSEServer) import Echidna.SourceAnalysis.Slither (isEmptySlitherInfo) -import Echidna.Types.Agent (Agent(..), workerTypeOf) +import Echidna.Types.Agent (Agent(..), mkFuzzerAgent, workerTypeOf) import Echidna.Types.Campaign import Echidna.Types.Config import Echidna.Types.Corpus qualified as Corpus @@ -249,17 +249,10 @@ ui vm dict initialCorpus cliSelectedContract = do spawnWorker env testLimit corpusChunk workerId = do stateRef <- newIORef initialWorkerState - let fuzzerAgent corpus limit = - FuzzerAgent { fuzzerId = workerId - , initialVm = vm - , initialDict = dict - , initialCorpus = corpus - , testLimit = limit - , stateRef - } + let fuzzerAgent = mkFuzzerAgent vm dict workerId agent = case workerIDToType env.cfg.campaignConf workerId of - FuzzWorker -> fuzzerAgent corpusChunk testLimit + FuzzWorker -> fuzzerAgent stateRef corpusChunk testLimit SymbolicWorker -> SymbolicAgent { initialVm = vm , initialDict = dict @@ -286,8 +279,9 @@ ui vm dict initialCorpus cliSelectedContract = do case stopReason of TimeLimitReached | workerType == FuzzWorker -> do tests <- traverse readIORef env.testRefs - when (any needsShrinking tests) $ void $ - runAgent (fuzzerAgent [] 0) env + when (any needsShrinking tests) $ do + shrinkStateRef <- newIORef initialWorkerState + void $ runAgent (fuzzerAgent shrinkStateRef [] 0) env _ -> pure () time <- liftIO getTimestamp diff --git a/src/test/Spec.hs b/src/test/Spec.hs index 8c17015ec..b236e8f7c 100644 --- a/src/test/Spec.hs +++ b/src/test/Spec.hs @@ -1,6 +1,7 @@ import System.Directory (withCurrentDirectory) import Test.Tasty (defaultMain, testGroup) import Tests.ABIv2 (abiv2Tests) +import Tests.Agent (agentTests) import Tests.Assertion (assertionTests) import Tests.Cheat (cheatTests) import Tests.Compile (compilationTests) @@ -20,7 +21,8 @@ import Tests.Values (valuesTests) main :: IO () main = withCurrentDirectory "./tests/solidity" . defaultMain $ testGroup "Echidna" - [ configTests + [ agentTests + , configTests , compilationTests , seedTests , integrationTests diff --git a/src/test/Tests/Agent.hs b/src/test/Tests/Agent.hs new file mode 100644 index 000000000..7b6835046 --- /dev/null +++ b/src/test/Tests/Agent.hs @@ -0,0 +1,17 @@ +module Tests.Agent (agentTests) where + +import Data.IORef (newIORef) +import Test.Tasty (TestTree, testGroup) +import Test.Tasty.HUnit (assertBool, testCase) + +import Echidna.Types.Agent (mkFuzzerAgent, stateRefOf) +import Echidna.Types.Campaign (initialWorkerState) + +agentTests :: TestTree +agentTests = testGroup "Agent tests" + [ testCase "fuzzer agent uses the supplied state reference" $ do + stateRef <- newIORef initialWorkerState + let agent = mkFuzzerAgent undefined undefined 0 stateRef [] 0 + assertBool "agent must publish through the supplied reference" $ + stateRefOf agent == stateRef + ]