Skip to content

Commit 6c0a717

Browse files
committed
coverage: count number of executions per line
1 parent 53a0a91 commit 6c0a717

File tree

3 files changed

+29
-18
lines changed

3 files changed

+29
-18
lines changed

Diff for: lib/Echidna/Exec.hs

+7-6
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,8 @@ execTxWithCov tx = do
247247
Just (vec, pc) -> do
248248
let txResultBit = fromEnum $ getResult $ fst r
249249
VMut.read vec pc >>= \case
250-
(opIx, depths, txResults) | not (txResults `testBit` txResultBit) -> do
251-
VMut.write vec pc (opIx, depths, txResults `setBit` txResultBit)
250+
(opIx, depths, txResults, execQty) | not (txResults `testBit` txResultBit) -> do
251+
VMut.write vec pc (opIx, depths, txResults `setBit` txResultBit, execQty)
252252
pure True -- we count this as new coverage
253253
_ -> pure False
254254
_ -> pure False
@@ -286,7 +286,7 @@ execTxWithCov tx = do
286286
-- IO for making a new vec
287287
vec <- VMut.new size
288288
-- We use -1 for opIx to indicate that the location was not covered
289-
forM_ [0..size-1] $ \i -> VMut.write vec i (-1, 0, 0)
289+
forM_ [0..size-1] $ \i -> VMut.write vec i (-1, 0, 0, 0)
290290
pure $ Just vec
291291

292292
case maybeCovVec of
@@ -299,10 +299,11 @@ execTxWithCov tx = do
299299
-- of `contract` for everything; it may be safe to remove this check.
300300
when (pc < VMut.length vec) $
301301
VMut.read vec pc >>= \case
302-
(_, depths, results) | depth < 64 && not (depths `testBit` depth) -> do
303-
VMut.write vec pc (opIx, depths `setBit` depth, results `setBit` fromEnum Stop)
302+
(_, depths, results, execQty) | depth < 64 && not (depths `testBit` depth) -> do
303+
VMut.write vec pc (opIx, depths `setBit` depth, results `setBit` fromEnum Stop, execQty + 1)
304304
writeIORef covContextRef (True, Just (vec, pc))
305-
_ ->
305+
(opIx', depths, results, execQty) -> do
306+
VMut.write vec pc (opIx', depths, results, execQty + 1)
306307
modifyIORef' covContextRef $ \(new, _) -> (new, Just (vec, pc))
307308

308309
-- | Get the VM's current execution location

Diff for: lib/Echidna/Output/Source.hs

+17-10
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import EVM.Solidity (SourceCache(..), SrcMap, SolcContract(..))
2929

3030
import Echidna.Types.Campaign (CampaignConf(..))
3131
import Echidna.Types.Config (Env(..), EConfig(..))
32-
import Echidna.Types.Coverage (OpIx, unpackTxResults, CoverageMap, CoverageFileType (..))
32+
import Echidna.Types.Coverage (OpIx, unpackTxResults, CoverageMap, CoverageFileType (..), ExecQty)
3333
import Echidna.Types.Tx (TxResult(..))
3434

3535
saveCoverages
@@ -103,7 +103,7 @@ ppCoveredCode fileType sc cs s | null s = pure "Coverage map is empty"
103103
pure $ topHeader <> T.unlines (map ppFile allFiles)
104104

105105
-- | Mark one particular line, from a list of lines, keeping the order of them
106-
markLines :: CoverageFileType -> V.Vector Text -> S.Set Int -> Map Int [TxResult] -> V.Vector Text
106+
markLines :: CoverageFileType -> V.Vector Text -> S.Set Int -> Map Int ([TxResult], ExecQty) -> V.Vector Text
107107
markLines fileType codeLines runtimeLines resultMap =
108108
V.map markLine . V.filter shouldUseLine $ V.indexed codeLines
109109
where
@@ -112,7 +112,7 @@ markLines fileType codeLines runtimeLines resultMap =
112112
_ -> True
113113
markLine (i, codeLine) =
114114
let n = i + 1
115-
results = fromMaybe [] (Map.lookup n resultMap)
115+
(results, execs) = fromMaybe ([], 0) (Map.lookup n resultMap)
116116
markers = sort $ nub $ getMarker <$> results
117117
wrapLine :: Text -> Text
118118
wrapLine line = case fileType of
@@ -123,11 +123,16 @@ markLines fileType codeLines runtimeLines resultMap =
123123
where
124124
cssClass = if n `elem` runtimeLines then getCSSClass markers else "neutral"
125125
result = case fileType of
126-
Lcov -> pack $ printf "DA:%d,%d" n (length results)
127-
_ -> pack $ printf " %*d | %-4s| %s" lineNrSpan n markers (wrapLine codeLine)
126+
Lcov -> pack $ printf "DA:%d,%d" n execs
127+
Html -> pack $ printf "%*d | %4s | %-4s| %s" lineNrSpan n (prettyExecs execs) markers (wrapLine codeLine)
128+
_ -> pack $ printf "%*d | %-4s| %s" lineNrSpan n markers (wrapLine codeLine)
128129

129130
in result
130131
lineNrSpan = length . show $ V.length codeLines + 1
132+
prettyExecs x = prettyExecs' x 0
133+
prettyExecs' x n | x >= 1000 = prettyExecs' (x `div` 1000) (n + 1)
134+
| x < 1000 && n == 0 = show x
135+
| otherwise = show x <> [" kMGTPEZY" !! n]
131136

132137
getCSSClass :: String -> Text
133138
getCSSClass markers =
@@ -146,16 +151,16 @@ getMarker ErrorOutOfGas = 'o'
146151
getMarker _ = 'e'
147152

148153
-- | Given a source cache, a coverage map, a contract returns a list of covered lines
149-
srcMapCov :: SourceCache -> CoverageMap -> [SolcContract] -> IO (Map FilePath (Map Int [TxResult]))
154+
srcMapCov :: SourceCache -> CoverageMap -> [SolcContract] -> IO (Map FilePath (Map Int ([TxResult], ExecQty)))
150155
srcMapCov sc covMap contracts = do
151156
Map.unionsWith Map.union <$> mapM linesCovered contracts
152157
where
153-
linesCovered :: SolcContract -> IO (Map FilePath (Map Int [TxResult]))
158+
linesCovered :: SolcContract -> IO (Map FilePath (Map Int ([TxResult], ExecQty)))
154159
linesCovered c =
155160
case Map.lookup c.runtimeCodehash covMap of
156161
Just vec -> VU.foldl' (\acc covInfo -> case covInfo of
157-
(-1, _, _) -> acc -- not covered
158-
(opIx, _stackDepths, txResults) ->
162+
(-1, _, _, _) -> acc -- not covered
163+
(opIx, _stackDepths, txResults, execQty) ->
159164
case srcMapForOpLocation c opIx of
160165
Just srcMap ->
161166
case srcMapCodePos sc srcMap of
@@ -167,8 +172,10 @@ srcMapCov sc covMap contracts = do
167172
where
168173
innerUpdate =
169174
Map.alter
170-
(Just . (<> unpackTxResults txResults) . fromMaybe mempty)
175+
updateLine
171176
line
177+
updateLine (Just (r, q)) = Just ((<> unpackTxResults txResults) r, max q execQty)
178+
updateLine Nothing = Just (unpackTxResults txResults, execQty)
172179
Nothing -> acc
173180
Nothing -> acc
174181
) mempty vec

Diff for: lib/Echidna/Types/Coverage.hs

+5-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import Echidna.Types.Tx (TxResult)
1818
type CoverageMap = Map W256 (IOVector CoverageInfo)
1919

2020
-- | Basic coverage information
21-
type CoverageInfo = (OpIx, StackDepths, TxResults)
21+
type CoverageInfo = (OpIx, StackDepths, TxResults, ExecQty)
2222

2323
-- | Index per operation in the source code, obtained from the source mapping
2424
type OpIx = Int
@@ -29,6 +29,9 @@ type StackDepths = Word64
2929
-- | Packed TxResults used for coverage, corresponding bits are set
3030
type TxResults = Word64
3131

32+
-- | Hit count
33+
type ExecQty = Word64
34+
3235
-- | Given good point coverage, count the number of unique points but
3336
-- only considering the different instruction PCs (discarding the TxResult).
3437
-- This is useful to report a coverage measure to the user
@@ -37,7 +40,7 @@ scoveragePoints cm = do
3740
sum <$> mapM (V.foldl' countCovered 0) (Map.elems cm)
3841

3942
countCovered :: Int -> CoverageInfo -> Int
40-
countCovered acc (opIx,_,_) = if opIx == -1 then acc else acc + 1
43+
countCovered acc (opIx,_,_,_) = if opIx == -1 then acc else acc + 1
4144

4245
unpackTxResults :: TxResults -> [TxResult]
4346
unpackTxResults txResults =

0 commit comments

Comments
 (0)