Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Source coverage printing after fuzzing campaign #516

Merged
merged 41 commits into from
Feb 8, 2021
Merged

Conversation

ggrieco-tob
Copy link
Member

@ggrieco-tob ggrieco-tob commented Sep 28, 2020

This PR allows Echidna to use source information from hevm to print which lines were covered during a fuzzing campaign. There is no need to configure anything, the source information is included in the crytic-compile output. The user should only enable coverage and corpusDir. However, the user must update to crytic-compile 0.1.10 (or newer) to make sure the source files are properly sorted when compiling.

The directly structure for the corpus directly was slight changed. For instance, after running flags.sol, the corpus directory will looks like this:

$ ls corpus/ -R
corpus/:
coverage  covered.txt

corpus/coverage:
-4293026413532156583.txt  4589328475588012129.txt

where the covered.txt file looks like this:

examples/solidity/basic/flags.sol
*r  |contract Test {
  bool private flag0 = true;
  bool private flag1 = true;

*   |  function set0(int val) public returns (bool){
*   |    if (val % 100 == 0)
*   |      flag0 = false;
  }

*   |  function set1(int val) public returns (bool){
*   |    if (val % 10 == 0 && !flag0)
*   |      flag1 = false;
  }

  function echidna_alwaystrue() public returns (bool){
    return(true);
  }

  function echidna_revert_always() public returns (bool){
    revert();
  }

  function echidna_sometimesfalse() public returns (bool){
    return(flag1);
  }

}

Constructors (#539) and echidna properties are not counting coverage. This should be fixed in another PR.

Each reachable line will have zero or more 'markers', signaling if how the sequence of transactions ended when covering that line. So far, we have support for the following markers:

  • * for sequences ended with a return.
  • r for sequences ended with a revert.
  • o for sequences ended with an out-of-gas error.
  • e for every other type of error (e.g. self-destruction, insufficient balance, etc).

It also introduced a small refactoring to move the corpus functions into a separated module inside Output.

@ggrieco-tob ggrieco-tob mentioned this pull request Nov 16, 2020
@ggrieco-tob ggrieco-tob mentioned this pull request Nov 18, 2020
@ggrieco-tob ggrieco-tob marked this pull request as ready for review November 23, 2020 18:28
lib/Echidna/Exec.hs Outdated Show resolved Hide resolved
lib/Echidna/Exec.hs Outdated Show resolved Hide resolved
lib/Echidna/Solidity.hs Outdated Show resolved Hide resolved
lib/Echidna/Output/Source.hs Show resolved Hide resolved
lib/Echidna/Output/Source.hs Outdated Show resolved Hide resolved
lib/Echidna/Output/Source.hs Outdated Show resolved Hide resolved
lib/Echidna.hs Outdated
@@ -39,14 +40,14 @@ import qualified Data.List.NonEmpty as NE
-- * A list of transaction sequences to initialize the corpus
prepareContract :: (MonadCatch m, MonadRandom m, MonadReader x m, MonadIO m, MonadFail m,
Has TxConf x, Has SolConf x)
=> EConfig -> NE.NonEmpty FilePath -> Maybe String -> Seed -> m (VM, World, [SolTest], Maybe GenDict, [[Tx]])
=> EConfig -> NE.NonEmpty FilePath -> Maybe String -> Seed -> m (VM, SourceCache, [SolcContract], World, [SolTest], Maybe GenDict, [[Tx]])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe prepareContract should take [SolcContract] provided by prior calling of Echidna.Solidity.contracts as an argument rather than FilePaths?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean exactly?

lib/Echidna/Output/Source.hs Outdated Show resolved Hide resolved
lib/Echidna/Solidity.hs Outdated Show resolved Hide resolved
@@ -161,7 +161,7 @@ execTxOptC t = do
res <- execTxWith vmExcept (usingCoverage $ pointCoverage cov) t
let vmr = getResult $ fst res
-- Update the coverage map with the proper binary according to the vm result
cov %= mapWithKey (\_ s -> DS.map (set _2 vmr) s)
cov %= mapWithKey (\_ s -> DS.map (\(i, j, _) -> (i, j, vmr)) s)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set _3

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


-- | Capture the current PC and bytecode (without metadata). This should identify instructions uniquely.
pointCoverage :: (MonadState x m, Has VM x) => Lens' x CoverageMap -> m ()
pointCoverage l = do
v <- use hasLens
l %= M.insertWith (const . S.insert $ (v ^. state . pc, Success))
l %= M.insertWith (const . S.insert $ ( v ^. state . pc, fromJust $ vmOpIx v, Success))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rogue space

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@@ -22,7 +22,7 @@ data Campaign = Campaign
, _error :: Maybe String
, _tests :: [Test]
, seed :: Int
, coverage :: Map String [(Int, TxResult)]
, coverage :: Map String [(Int, Int, TxResult)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use your new types?

@@ -0,0 +1,122 @@
{-# LANGUAGE DataKinds #-}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DataKinds?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.


-- | Mark one particular line, from a list of lines, keeping the order of them
markLine :: Int -> TxResult -> FilePathText -> [(FilePathText, Text)] -> [(FilePathText, Text)]
markLine n r cf ls = case splitAt (n-1) ls of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's make this multiline and indent a bit less

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think now it is better


import Echidna.Types.Tx

listDirectory :: FilePath -> IO [FilePath]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe move these directory functions to a util module

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


type FilePathText = Text

saveCoveredCode :: Maybe FilePath -> SourceCache -> [SolcContract] -> CoverageMap -> IO ()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rename this to saveCoverage

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@@ -73,7 +73,7 @@ runContract :: FilePath -> Maybe ContractName -> EConfig -> IO Campaign
runContract f c cfg =
flip runReaderT cfg $ do
g <- getRandom
(v, w, ts, d, txs) <- prepareContract cfg (f :| []) c g
(v, _, _, w, ts, d, txs) <- prepareContract cfg (f :| []) c g
-- start ui and run tests
campaign (pure ()) v w ts d txs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now that we have a bunch of things from prepareContract can we make a custom type for prepareContract and campaign and ui to ingest the custom type instead of adding a new parameter every time? feel free to make an issue and refactor in a separate PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create a new issue to track down this.

import Data.List.NonEmpty (NonEmpty)
import Data.ByteString (ByteString)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alphabetize these imports

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

import Control.Monad.Random.Strict (MonadRandom, getRandomR, uniform)
import Control.Monad.Reader.Class (MonadReader)
import Control.Monad.State.Strict (MonadState, State, runState, get, put)
import Data.Aeson (ToJSON(..), decodeStrict, encodeFile)
import Control.Monad (join, liftM2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alphabetize imports

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@ggrieco-tob ggrieco-tob merged commit 4e0a217 into master Feb 8, 2021
@ggrieco-tob ggrieco-tob deleted the dev-sources branch February 8, 2021 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants