Skip to content

Commit ea375a9

Browse files
Read assert locations and determinate if they were executed or not
Co-authored-by: ggrieco-tob <[email protected]>
1 parent 6956030 commit ea375a9

File tree

6 files changed

+59
-7
lines changed

6 files changed

+59
-7
lines changed

lib/Echidna.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ prepareContract
5151
-> BuildOutput
5252
-> Maybe ContractName
5353
-> Seed
54-
-> IO (VM Concrete RealWorld, Env, GenDict)
54+
-> IO (VM Concrete RealWorld, Env, GenDict, AssertMappingByContract)
5555
prepareContract cfg solFiles buildOutput selectedContract seed = do
5656
let solConf = cfg.solConf
5757
(Contracts contractMap) = buildOutput.contracts
@@ -90,7 +90,7 @@ prepareContract cfg solFiles buildOutput selectedContract seed = do
9090
seed
9191
(returnTypes contracts)
9292

93-
pure (vm, env, dict)
93+
pure (vm, env, dict, slitherInfo.asserts)
9494

9595
loadInitialCorpus :: Env -> IO [(FilePath, [Tx])]
9696
loadInitialCorpus env = do

lib/Echidna/Output/Source.hs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module Echidna.Output.Source where
44

55
import Prelude hiding (writeFile)
66

7+
import Control.Monad (unless)
78
import Data.ByteString qualified as BS
89
import Data.Foldable
910
import Data.IORef (readIORef)
@@ -31,6 +32,7 @@ import Echidna.Types.Campaign (CampaignConf(..))
3132
import Echidna.Types.Config (Env(..), EConfig(..))
3233
import Echidna.Types.Coverage (OpIx, unpackTxResults, CoverageMap, CoverageFileType (..))
3334
import Echidna.Types.Tx (TxResult(..))
35+
import Echidna.SourceAnalysis.Slither (AssertMappingByContract, AssertLocation(..))
3436

3537
saveCoverages
3638
:: Env
@@ -188,3 +190,26 @@ buildRuntimeLinesMap sc contracts =
188190
where
189191
srcMaps = concatMap
190192
(\c -> toList $ c.runtimeSrcmap <> c.creationSrcmap) contracts
193+
194+
checkAssertionsCoverage
195+
:: SourceCache
196+
-> [SolcContract]
197+
-> CoverageMap
198+
-> AssertMappingByContract
199+
-> IO ()
200+
checkAssertionsCoverage sc cs covMap assertMap = do
201+
covLines <- srcMapCov sc covMap cs
202+
let asserts = concat $ concatMap Map.elems $ Map.elems assertMap
203+
mapM_ (checkAssertionReached covLines) asserts
204+
205+
checkAssertionReached :: Map String (Map Int [TxResult]) -> AssertLocation -> IO ()
206+
checkAssertionReached covLines assert =
207+
maybe
208+
warnAssertNotReached checkCoverage
209+
(Map.lookup assert.filenameAbsolute covLines)
210+
where
211+
checkCoverage coverage = let lineNumbers = Map.keys coverage in
212+
unless ((head assert.assertLines) `elem` lineNumbers) warnAssertNotReached
213+
warnAssertNotReached =
214+
putStrLn $ "WARNING: assertion at file: " ++ assert.filenameRelative
215+
++ " starting at line: " ++ show (head assert.assertLines) ++ " was never reached"

lib/Echidna/Solidity.hs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ mkWorld SolConf{sender, testMode} sigMap maybeContract slitherInfo contracts =
342342
let
343343
eventMap = Map.unions $ map (.eventMap) contracts
344344
payableSigs = filterResults maybeContract slitherInfo.payableFunctions
345-
as = if isAssertionMode testMode then filterResults maybeContract slitherInfo.asserts else []
345+
as = if isAssertionMode testMode then filterResults maybeContract (getAssertFns <$> slitherInfo.asserts) else []
346346
cs = if isDapptestMode testMode then [] else filterResults maybeContract slitherInfo.constantFunctions \\ as
347347
(highSignatureMap, lowSignatureMap) = prepareHashMaps cs as $
348348
filterFallbacks slitherInfo.fallbackDefined slitherInfo.receiveDefined contracts sigMap
@@ -352,6 +352,9 @@ mkWorld SolConf{sender, testMode} sigMap maybeContract slitherInfo contracts =
352352
, payableSigs
353353
, eventMap
354354
}
355+
where
356+
getAssertFns :: Map FunctionName [AssertLocation] -> [FunctionName]
357+
getAssertFns fnToAsserts = map fst $ filter (not . null . snd) $ Map.toList fnToAsserts
355358

356359
-- | This function is used to filter the lists of function names according to the supplied
357360
-- contract name (if any) and returns a list of hashes

lib/Echidna/SourceAnalysis/Slither.hs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,32 @@ enhanceConstants si =
4040
enh (AbiString s) = makeArrayAbiValues s
4141
enh v = [v]
4242

43+
data AssertLocation = AssertLocation
44+
{ start :: Int
45+
, filenameRelative :: String
46+
, filenameAbsolute :: String
47+
, assertLines :: [Int]
48+
, startColumn :: Int
49+
, endingColumn :: Int
50+
} deriving (Show)
51+
52+
type AssertMappingByContract = Map ContractName (Map FunctionName [AssertLocation])
53+
54+
instance FromJSON AssertLocation where
55+
parseJSON = withObject "" $ \o -> do
56+
start <- o.: "start"
57+
filenameRelative <- o.: "filename_relative"
58+
filenameAbsolute <- o.: "filename_absolute"
59+
assertLines <- o.: "lines"
60+
startColumn <- o.: "starting_column"
61+
endingColumn <- o.: "ending_column"
62+
pure AssertLocation {..}
63+
4364
-- we loose info on what constants are in which functions
4465
data SlitherInfo = SlitherInfo
4566
{ payableFunctions :: Map ContractName [FunctionName]
4667
, constantFunctions :: Map ContractName [FunctionName]
47-
, asserts :: Map ContractName [FunctionName]
68+
, asserts :: AssertMappingByContract
4869
, constantValues :: Map ContractName (Map FunctionName [AbiValue])
4970
, generationGraph :: Map ContractName (Map FunctionName [FunctionName])
5071
, solcVersions :: [Version]

src/Main.hs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,18 @@ main = withUtf8 $ withCP65001 $ do
6262

6363
-- take the seed from config, otherwise generate a new one
6464
seed <- maybe (getRandomR (0, maxBound)) pure cfg.campaignConf.seed
65-
(vm, env, dict) <- prepareContract cfg cliFilePath buildOutput cliSelectedContract seed
65+
(vm, env, dict, asserts) <- prepareContract cfg cliFilePath buildOutput cliSelectedContract seed
6666

6767
initialCorpus <- loadInitialCorpus env
6868
-- start ui and run tests
6969
_campaign <- runReaderT (ui vm dict initialCorpus cliSelectedContract) env
7070

7171
tests <- traverse readIORef env.testRefs
7272

73+
let contracts = Map.elems env.dapp.solcByName
74+
coverage <- readIORef env.coverageRef
75+
checkAssertionsCoverage buildOutput.sources contracts coverage asserts
76+
7377
Onchain.saveRpcCache env
7478

7579
-- save corpus
@@ -108,7 +112,6 @@ main = withUtf8 $ withCP65001 $ do
108112
Onchain.saveCoverageReport env runId
109113

110114
-- save source coverage reports
111-
let contracts = Map.elems env.dapp.solcByName
112115
saveCoverages env runId dir buildOutput.sources contracts
113116

114117
if isSuccessful tests then exitSuccess else exitWith (ExitFailure 1)

src/test/Common.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ runContract f selectedContract cfg workerType = do
9494
seed <- maybe (getRandomR (0, maxBound)) pure cfg.campaignConf.seed
9595
buildOutput <- compileContracts cfg.solConf (f :| [])
9696

97-
(vm, env, dict) <- prepareContract cfg (f :| []) buildOutput selectedContract seed
97+
(vm, env, dict, _) <- prepareContract cfg (f :| []) buildOutput selectedContract seed
9898

9999
(_stopReason, finalState) <- flip runReaderT env $
100100
runWorker workerType (pure ()) vm dict 0 [] cfg.campaignConf.testLimit selectedContract

0 commit comments

Comments
 (0)