mirror of
https://github.com/crytic/echidna.git
synced 2026-10-12 02:12:32 +00:00
Small standalone fixes: coverage attribution, generic log events (#1599)
* feat: name the function that found new coverage ppWorkerEvent already receives the transaction sequence behind a NewCoverage event but only reported the aggregate counters. Append the last transaction's call to the log line so it is visible which function is making progress. Co-authored-by: gustavo-grieco <[email protected]> * feat: add generic log events Add a WorkerEvent Log constructor for free-form worker messages and a CampaignEvent ServerLog for messages that are not attributable to any worker, reported with a [Server] prefix in the text UI and as a server_log SSE event. All four exhaustive matches over these types (the ToJSON instances, the pretty printers and the SSE event names) land together so the compiler stays happy. Co-authored-by: gustavo-grieco <[email protected]> * fix(tests): return the flag from flags.sol set0/set1 Both functions are declared 'returns (bool)' but fall off the end, so solc warns and they silently return false. Co-authored-by: gustavo-grieco <[email protected]> * chore: gitignore Python artifacts Co-authored-by: gustavo-grieco <[email protected]> * fix(worker): avoid hlint false positive on record-dot access CI runs bare `hlint lib src`, which does not read echidna.cabal and so does not know OverloadedRecordDot is on. It parsed `(last txs).call` as function composition and flagged the parens as a redundant bracket. Taking that suggestion would have broken the build: under OverloadedRecordDot, `last txs.call` parses as `last (txs.call)`. Bind the last transaction first so the expression needs no parens and reads identically to hlint and to GHC. --------- Co-authored-by: gustavo-grieco <[email protected]>
This commit is contained in:
co-authored by
gustavo-grieco
parent
07282dac29
commit
d87e720342
@@ -22,3 +22,9 @@ crytic-export/
|
||||
echidna.cabal
|
||||
*.swp
|
||||
profiles/
|
||||
|
||||
# Python helper scripts and test suites
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.pytest_cache/
|
||||
.venv/
|
||||
|
||||
@@ -36,6 +36,10 @@ instance ToJSON SSE where
|
||||
object [ "timestamp" .= time
|
||||
, "filename" .= filename
|
||||
]
|
||||
toJSON (SSE (time, ServerLog msg)) =
|
||||
object [ "timestamp" .= time
|
||||
, "data" .= msg
|
||||
]
|
||||
|
||||
runSSEServer :: MVar () -> Env -> Word16 -> Int -> IO ()
|
||||
runSSEServer serverStopVar env port nworkers = do
|
||||
@@ -53,11 +57,13 @@ runSSEServer serverStopVar env port nworkers = do
|
||||
NewCoverage {} -> "new_coverage"
|
||||
SymExecLog _ -> "sym_exec_log"
|
||||
SymExecError _ -> "sym_exec_error"
|
||||
Log _ -> "log"
|
||||
TxSequenceReplayed {} -> "tx_sequence_replayed"
|
||||
TxSequenceReplayFailed {} -> "tx_sequence_replay_failed"
|
||||
WorkerStopped _ -> "worker_stopped"
|
||||
Failure _err -> "failure"
|
||||
ReproducerSaved _ -> "saved_reproducer"
|
||||
ServerLog _ -> "server_log"
|
||||
|
||||
let serverEvent = ServerEvent
|
||||
{ eventName = Just (eventName campaignEvent)
|
||||
|
||||
@@ -11,6 +11,7 @@ data CampaignEvent
|
||||
= WorkerEvent WorkerId WorkerType WorkerEvent
|
||||
| Failure String
|
||||
| ReproducerSaved String -- filename
|
||||
| ServerLog String
|
||||
|
||||
data WorkerEvent
|
||||
= TestFalsified !EchidnaTest
|
||||
@@ -18,6 +19,7 @@ data WorkerEvent
|
||||
| NewCoverage { points :: !Int, numCodehashes :: !Int, corpusSize :: !Int, transactions :: [Tx] }
|
||||
| SymExecError !String
|
||||
| SymExecLog !String
|
||||
| Log !String
|
||||
| TxSequenceReplayed FilePath !Int !Int
|
||||
| TxSequenceReplayFailed FilePath Tx
|
||||
| WorkerStopped WorkerStopReason
|
||||
|
||||
@@ -34,6 +34,8 @@ ppLogLine vm (time, event@(WorkerEvent workerId FuzzWorker _)) =
|
||||
((timePrefix time <> "[Worker " <> show workerId <> "] ") <>) <$> ppCampaignEventLog vm event
|
||||
ppLogLine vm (time, event@(WorkerEvent workerId SymbolicWorker _)) =
|
||||
((timePrefix time <> "[Worker " <> show workerId <> ", symbolic] ") <>) <$> ppCampaignEventLog vm event
|
||||
ppLogLine vm (time, event@(ServerLog _)) =
|
||||
((timePrefix time <> "[Server] ") <>) <$> ppCampaignEventLog vm event
|
||||
ppLogLine vm (time, event) =
|
||||
((timePrefix time <> " ") <>) <$> ppCampaignEventLog vm event
|
||||
|
||||
|
||||
+16
-3
@@ -12,6 +12,7 @@ import Echidna.ABI (encodeSig)
|
||||
import Echidna.Types.Campaign
|
||||
import Echidna.Types.Config (Env(..), EConfig(..))
|
||||
import Echidna.Types.Test
|
||||
import Echidna.Types.Tx (Tx(..), TxCall(..))
|
||||
import Echidna.Types.Worker
|
||||
import Echidna.Utility (getTimestamp)
|
||||
|
||||
@@ -30,6 +31,7 @@ instance ToJSON WorkerEvent where
|
||||
object [ "coverage" .= points, "contracts" .= numCodehashes, "corpus_size" .= corpusSize]
|
||||
SymExecError msg -> object [ "msg" .= msg ]
|
||||
SymExecLog msg -> object [ "msg" .= msg ]
|
||||
Log msg -> object [ "msg" .= msg ]
|
||||
TxSequenceReplayed file current total ->
|
||||
object [ "file" .= file, "current" .= current, "total" .= total ]
|
||||
TxSequenceReplayFailed file tx ->
|
||||
@@ -97,6 +99,7 @@ ppCampaignEvent = \case
|
||||
WorkerEvent _ _ e -> ppWorkerEvent e
|
||||
Failure err -> err
|
||||
ReproducerSaved f -> "Saved reproducer to " <> f
|
||||
ServerLog msg -> msg
|
||||
|
||||
ppWorkerEvent :: WorkerEvent -> String
|
||||
ppWorkerEvent = \case
|
||||
@@ -105,14 +108,24 @@ ppWorkerEvent = \case
|
||||
TestOptimized test ->
|
||||
let name = case test.testType of OptimizationTest n _ -> n; _ -> error "fixme"
|
||||
in "New maximum value of " <> unpack name <> ": " <> show test.value
|
||||
NewCoverage { points, numCodehashes, corpusSize } ->
|
||||
"New coverage: " <> show points <> " instr, "
|
||||
NewCoverage { points, numCodehashes, corpusSize, transactions } ->
|
||||
let -- the coverage is credited to the last transaction of the sequence
|
||||
culprit = case transactions of
|
||||
[] -> "init"
|
||||
txs -> let tx = last txs in case tx.call of
|
||||
SolCall (name, _) -> unpack name
|
||||
SolCreate _ -> "constructor"
|
||||
SolCalldata _ -> "fallback"
|
||||
NoCall -> "no call"
|
||||
in "New coverage: " <> show points <> " instr, "
|
||||
<> show numCodehashes <> " contracts, "
|
||||
<> show corpusSize <> " seqs in corpus"
|
||||
<> show corpusSize <> " seqs in corpus (" <> culprit <> ")"
|
||||
SymExecError err ->
|
||||
"Symbolic execution failed: " <> err
|
||||
SymExecLog msg ->
|
||||
"Symbolic execution log: " <> msg
|
||||
Log msg ->
|
||||
msg
|
||||
TxSequenceReplayed file current total ->
|
||||
"Sequence replayed from corpus file " <> file <> " (" <> show current <> "/" <> show total <> ")"
|
||||
TxSequenceReplayFailed file tx ->
|
||||
|
||||
@@ -5,13 +5,15 @@ contract Test {
|
||||
bool private flag1 = true;
|
||||
|
||||
function set0(int val) public returns (bool){
|
||||
if (val % 100 == 0)
|
||||
if (val % 100 == 0)
|
||||
flag0 = false;
|
||||
return flag0;
|
||||
}
|
||||
|
||||
function set1(int val) public returns (bool){
|
||||
if (val % 10 == 0 && !flag0)
|
||||
if (val % 10 == 0 && !flag0)
|
||||
flag1 = false;
|
||||
return flag1;
|
||||
}
|
||||
|
||||
function echidna_alwaystrue() public returns (bool){
|
||||
|
||||
Reference in New Issue
Block a user