Skip to content

Commit

Permalink
Upgrade build to GHC 9.6 (#1245)
Browse files Browse the repository at this point in the history
* Upgrade hevm to latest `echidna-patches` commit

* Upgrade build to GHC 9.6

* Refactor terminal and signal support in preparation for Windows UI

* Enable UI on all platforms

* Use pkgsStatic for Linux

pkgsMusl fails to build for some reason.

* Remove conditional UI compilation

All platforms now support the Echidna UI

* Enable UI on ConHost as well

See the introduction on https://hackage.haskell.org/package/ansi-terminal-1.1.1/docs/System-Console-ANSI.html

* Enable `eager-blackholing`

Observed a ~2% speedup on longer runs when compiling both hevm and
echidna with this flag. Besides, the Haskell documentation encourages
enabling it for parallel code:

https://downloads.haskell.org/~ghc/9.6.5/docs/users_guide/using-concurrent.html#compile-time-options-for-smp-parallelism

* Resolve hlint warnings

* Update Linux Stack CI to 9.6

* Fix compilation failure after merge

* Upgrade hevm to `echidna-patches-20240725`

* Fix lint warnings, unused imports

* flake: align nixpkgs with hevm

* flake: fix libiconv for x86_64 darwin

       > app/utf8-troubleshoot/cbits/locale.c:10:10: error:
       >      fatal error: 'libcharset.h' file not found
       >    |
       > 10 | #include <libcharset.h>
       >    |          ^
       > #include <libcharset.h>
       >          ^~~~~~~~~~~~~~

Also link iconv dynamically on Darwin as well
  • Loading branch information
elopez authored Jul 25, 2024
1 parent a550094 commit 182580e
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 137 deletions.
8 changes: 4 additions & 4 deletions .github/container-linux-static/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM alpine:3.18.4
FROM alpine:3.18.6
# Based on https://github.com/fpco/alpine-haskell-stack/blob/9.2.8v2/ghc-Dockerfile

RUN apk upgrade --no-cache &&\
Expand Down Expand Up @@ -36,12 +36,12 @@ RUN ln -s /usr/lib/libncurses.a /usr/lib/libtinfo.a
COPY stack-config.yaml /root/.stack/config.yaml

RUN cd /tmp && \
curl -sSLo /tmp/ghc.tar.xz https://downloads.haskell.org/~ghc/9.4.7/ghc-9.4.7-x86_64-alpine3_12-linux.tar.xz && \
curl -sSLo /tmp/ghc.tar.xz https://downloads.haskell.org/~ghc/9.6.5/ghc-9.6.5-x86_64-alpine3_12-linux.tar.xz && \
tar xf ghc.tar.xz && \
cd ghc-9.4.7-x86_64-unknown-linux && \
cd ghc-9.6.5-x86_64-unknown-linux && \
./configure --prefix=/usr/local && \
make install && \
rm -rf /tmp/ghc.tar.xz /tmp/ghc-9.4.7-x86_64-unknown-linux
rm -rf /tmp/ghc.tar.xz /tmp/ghc-9.6.5-x86_64-unknown-linux

RUN apk upgrade --no-cache &&\
apk add --no-cache \
Expand Down
5 changes: 5 additions & 0 deletions .github/container-linux-static/stack-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
extra-include-dirs:
- /usr/include
extra-lib-dirs:
- /lib
- /usr/lib
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ on:

env:
# Tag for cache invalidation
CACHE_VERSION: v6
CACHE_VERSION: v7

jobs:
build:
Expand All @@ -22,7 +22,7 @@ jobs:
include:
- os: ubuntu-20.04
shell: bash
container: "{\"image\": \"elopeztob/alpine-haskell-stack-echidna:ghc-9.4.7\", \"options\": \"--user 1001\"}"
container: "{\"image\": \"elopeztob/alpine-haskell-stack-echidna:ghc-9.6.5\", \"options\": \"--user 1001\"}"
- os: macos-13 # x86_64 macOS
shell: bash
- os: windows-latest
Expand Down Expand Up @@ -65,7 +65,7 @@ jobs:
id: stack
if: matrix.container == ''
with:
ghc-version: '9.4'
ghc-version: '9.6'
enable-stack: true
stack-version: 'latest'

Expand Down
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 50 additions & 20 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
pkgs = nixpkgs.legacyPackages.${system};
# prefer musl on Linux, static glibc + threading does not work properly
# TODO: maybe only override it for echidna-redistributable?
pkgsStatic = if pkgs.stdenv.hostPlatform.isLinux then pkgs.pkgsMusl else pkgs;
pkgsStatic = if pkgs.stdenv.hostPlatform.isLinux then pkgs.pkgsStatic else pkgs;
# this is not perfect for development as it hardcodes solc to 0.5.7, test suite runs fine though
# would be great to integrate solc-select to be more flexible, improve this in future
solc = pkgs.stdenv.mkDerivation {
Expand Down Expand Up @@ -47,39 +47,61 @@

ncurses-static = pkgsStatic.ncurses.override { enableStatic = true; };

hevm = pkgs: pkgs.haskell.lib.dontCheck (
pkgs.haskellPackages.callCabal2nix "hevm" (pkgs.fetchFromGitHub {
hsPkgs = ps :
ps.haskellPackages.override {
overrides = hfinal: hprev: {
with-utf8 =
if (with ps.stdenv; hostPlatform.isDarwin && hostPlatform.isx86)
then ps.haskell.lib.compose.overrideCabal (_ : { extraLibraries = [ps.libiconv]; }) hprev.with-utf8
else hprev.with-utf8;
};
};

cc-workaround-nix-23138 =
pkgs.writeScriptBin "cc-workaround-nix-23138" ''
if [ "$1" = "--print-file-name" ] && [ "$2" = "c++" ]; then
echo c++
else
exec cc "$@"
fi
'';

hevm = pkgs: pkgs.lib.pipe ((hsPkgs pkgs).callCabal2nix "hevm" (pkgs.fetchFromGitHub {
owner = "trail-of-forks";
repo = "hevm";
rev = "7d4344c5e71d14466e86331af064bab61d06bdad";
sha256 = "sha256-kts6mdwx5KUrVdNztzewWgNM9xGViAhFIZPnWOUllOU=";
}) { secp256k1 = pkgs.secp256k1; });
rev = "3aba82f06a2d1e0a4a4c26458f747a46dad0e7e2";
sha256 = "sha256-NXXhEqHTQEL2N9RhXa1eczIsQtIM3mvPfyWXlBXpxK4=";
}) { secp256k1 = pkgs.secp256k1; })
([
pkgs.haskell.lib.compose.dontCheck
] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
(pkgs.haskell.lib.compose.appendConfigureFlag "--ghc-options=-pgml=${cc-workaround-nix-23138}/bin/cc-workaround-nix-23138")
]);

# FIXME: figure out solc situation, it conflicts with the one from
# solc-select that is installed with slither, disable tests in the meantime
echidna = pkgs: pkgs.haskell.lib.dontCheck (
with pkgs; lib.pipe
(haskellPackages.callCabal2nix "echidna" ./. { hevm = hevm pkgs; })
[
echidna = pkgs: with pkgs; lib.pipe
((hsPkgs pkgs).callCabal2nix "echidna" ./. { hevm = hevm pkgs; })
([
# FIXME: figure out solc situation, it conflicts with the one from
# solc-select that is installed with slither, disable tests in the meantime
haskell.lib.compose.dontCheck
(haskell.lib.compose.addTestToolDepends [ haskellPackages.hpack slither-analyzer solc ])
(haskell.lib.compose.disableCabalFlag "static")
] ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [
(pkgs.haskell.lib.compose.appendConfigureFlag "--ghc-options=-pgml=${cc-workaround-nix-23138}/bin/cc-workaround-nix-23138")
]);

echidna-static = with pkgsStatic; lib.pipe
(echidna pkgsStatic)
[
(haskell.lib.compose.appendConfigureFlags
([
[
"--extra-lib-dirs=${stripDylib (gmp.override { withStatic = true; })}/lib"
"--extra-lib-dirs=${stripDylib secp256k1-static}/lib"
"--extra-lib-dirs=${stripDylib (libff.override { enableStatic = true; })}/lib"
"--extra-lib-dirs=${zlib.static}/lib"
"--extra-lib-dirs=${zlib.override { static = true; shared = false; }}/lib"
"--extra-lib-dirs=${stripDylib (libffi.overrideAttrs (_: { dontDisableStatic = true; }))}/lib"
"--extra-lib-dirs=${stripDylib (ncurses-static)}/lib"
] ++ (if stdenv.hostPlatform.isDarwin then [
"--extra-lib-dirs=${stripDylib (libiconv.override { enableStatic = true; })}/lib"
"--extra-lib-dirs=${stripDylib (libcxxabi)}/lib"
] else [])))
])
(haskell.lib.compose.enableCabalFlag "static")
];

Expand Down Expand Up @@ -108,10 +130,14 @@
# get the list of dynamic libs from otool and tidy the output
libs=$(${otool} -L $out/bin/echidna | tail -n +2 | sed 's/^[[:space:]]*//' | cut -d' ' -f1)
# get the path for libcxx
cxx=$(echo "$libs" | ${grep} '^/nix/store/.*-libcxx-')
cxx=$(echo "$libs" | ${grep} '^/nix/store/.*/libc++\.')
cxxabi=$(echo "$libs" | ${grep} '^/nix/store/.*/libc++abi\.')
iconv=$(echo "$libs" | ${grep} '^/nix/store/.*/libiconv\.')
# rewrite /nix/... library paths to point to /usr/lib
chmod 777 $out/bin/echidna
${install_name_tool} -change "$cxx" /usr/lib/libc++.1.dylib $out/bin/echidna
${install_name_tool} -change "$cxxabi" /usr/lib/libc++abi.dylib $out/bin/echidna
${install_name_tool} -change "$iconv" /usr/lib/libiconv.dylib $out/bin/echidna
# fix TERMINFO path in ncurses
${perl} -i -pe 's#(${ncurses-static}/share/terminfo)#"/usr/share/terminfo" . "\x0" x (length($1) - 19)#e' $out/bin/echidna
# check that no nix deps remain
Expand Down Expand Up @@ -145,7 +171,11 @@
devShell = with pkgs;
haskellPackages.shellFor {
packages = _: [ (echidna pkgs) ];
shellHook = "hpack";
shellHook = ''
hpack
'' + (if pkgs.stdenv.isDarwin then ''
cabal configure --ghc-options=-pgml=${cc-workaround-nix-23138}/bin/cc-workaround-nix-23138
'' else "");
buildInputs = [
solc
slither-analyzer
Expand Down
55 changes: 20 additions & 35 deletions lib/Echidna/UI.hs
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}

module Echidna.UI where

#ifdef INTERACTIVE_UI
import Brick
import Brick.BChan
import Brick.Widgets.Dialog qualified as B
import Data.Sequence ((|>))
import Graphics.Vty (Config, Event(..), Key(..), Modifier(..), defaultConfig, inputMap, mkVty)
import Graphics.Vty qualified as Vty
import System.Posix
import Echidna.UI.Widgets
#endif

import Control.Concurrent (killThread, threadDelay)
import Control.Exception (AsyncException)
import Control.Monad
Expand All @@ -24,14 +15,20 @@ import Control.Monad.ST (RealWorld)
import Data.ByteString.Lazy qualified as BS
import Data.List.Split (chunksOf)
import Data.Map (Map)
import Data.Maybe (fromMaybe, isJust)
import Data.Maybe (isJust)
import Data.Sequence ((|>))
import Data.Text (Text)
import Data.Time
import Graphics.Vty.Config (VtyUserConfig, defaultConfig, configInputMap)
import Graphics.Vty.CrossPlatform (mkVty)
import Graphics.Vty.Input.Events
import Graphics.Vty qualified as Vty
import System.Console.ANSI (hNowSupportsANSI)
import System.Signal
import UnliftIO
( MonadUnliftIO, IORef, newIORef, readIORef, hFlush, stdout , writeIORef, timeout)
import UnliftIO.Concurrent hiding (killThread, threadDelay)

import EVM.Solidity (SolcContract)
import EVM.Types (Addr, Contract, VM, VMType(Concrete), W256)

import Echidna.ABI
Expand All @@ -43,11 +40,10 @@ import Echidna.Types.Campaign
import Echidna.Types.Config
import Echidna.Types.Corpus qualified as Corpus
import Echidna.Types.Coverage (scoveragePoints)
import Echidna.Types.Solidity (SolConf(..))
import Echidna.Types.Test (EchidnaTest(..), didFail, isOptimizationTest)
import Echidna.Types.Tx (Tx)
import Echidna.Types.World (World)
import Echidna.UI.Report
import Echidna.UI.Widgets
import Echidna.Utility (timePrefix, getTimestamp)

data UIEvent =
Expand Down Expand Up @@ -93,7 +89,6 @@ ui vm dict initialCorpus cliSelectedContract = do
uncurry (spawnWorker env perWorkerTestLimit)

case effectiveMode of
#ifdef INTERACTIVE_UI
Interactive -> do
-- Channel to push events to update UI
uiChannel <- liftIO $ newBChan 1000
Expand Down Expand Up @@ -157,20 +152,17 @@ ui vm dict initialCorpus cliSelectedContract = do
liftIO . putStrLn =<< ppCampaign vm states

pure states
#else
Interactive -> error "Interactive UI is not available"
#endif

NonInteractive outputFormat -> do
serverStopVar <- newEmptyMVar
#ifdef INTERACTIVE_UI
-- Handles ctrl-c, TODO: this doesn't work on Windows

-- Handles ctrl-c
liftIO $ forM_ [sigINT, sigTERM] $ \sig ->
let handler = Catch $ do
let handler _ = do
stopWorkers workers
void $ tryPutMVar serverStopVar ()
in installHandler sig handler Nothing
#endif
in installHandler sig handler

let forwardEvent ev = putStrLn =<< runReaderT (ppLogLine vm ev) env
uiEventsForwarderStopVar <- spawnListener forwardEvent

Expand Down Expand Up @@ -245,20 +237,19 @@ ui vm dict initialCorpus cliSelectedContract = do
workerStates workers =
forM workers $ \(_, stateRef) -> readIORef stateRef

#ifdef INTERACTIVE_UI
-- | Order the workers to stop immediately
stopWorkers :: MonadIO m => [(ThreadId, IORef WorkerState)] -> m ()
stopWorkers workers =
forM_ workers $ \(threadId, workerStateRef) -> do
workerState <- readIORef workerStateRef
liftIO $ mapM_ killThread (threadId : workerState.runningThreads)

vtyConfig :: IO Config
vtyConfig :: IO VtyUserConfig
vtyConfig = do
config <- Vty.standardIOConfig
pure config { inputMap = (Nothing, "\ESC[6;2~", EvKey KPageDown [MShift]) :
(Nothing, "\ESC[5;2~", EvKey KPageUp [MShift]) :
inputMap defaultConfig }
pure defaultConfig { configInputMap = [
(Nothing, "\ESC[6;2~", EvKey KPageDown [MShift]),
(Nothing, "\ESC[5;2~", EvKey KPageUp [MShift])
] }

-- | Check if we should stop drawing (or updating) the dashboard, then do the right thing.
monitor :: MonadReader Env m => m (App UIState UIEvent Name)
Expand Down Expand Up @@ -336,16 +327,10 @@ monitor = do
, appAttrMap = const attrs
, appChooseCursor = neverShowCursor
}
#endif

-- | Heuristic check that we're in a sensible terminal (not a pipe)
isTerminal :: IO Bool
isTerminal =
#ifdef INTERACTIVE_UI
(&&) <$> queryTerminal (Fd 0) <*> queryTerminal (Fd 1)
#else
pure False
#endif
isTerminal = hNowSupportsANSI stdout

-- | Composes a compact text status line of the campaign
statusLine
Expand Down
13 changes: 4 additions & 9 deletions lib/Echidna/UI/Widgets.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

module Echidna.UI.Widgets where

#ifdef INTERACTIVE_UI

import Brick hiding (style)
import Brick.AttrMap qualified as A
import Brick.Widgets.Border
Expand Down Expand Up @@ -147,14 +145,14 @@ logPane uiState =

showLogLine :: (LocalTime, CampaignEvent) -> Widget Name
showLogLine (time, event@(WorkerEvent workerId workerType _)) =
(withAttr (attrName "time") $ str $ (timePrefix time) <> "[Worker " <> show workerId <> symSuffix <> "] ")
withAttr (attrName "time") (str $ timePrefix time <> "[Worker " <> show workerId <> symSuffix <> "] ")
<+> strBreak (ppCampaignEvent event)
where
symSuffix = case workerType of
SymbolicWorker -> ", symbolic"
_ -> ""
showLogLine (time, event) =
(withAttr (attrName "time") $ str $ (timePrefix time) <> " ") <+> strBreak (ppCampaignEvent event)
withAttr (attrName "time") (str $ timePrefix time <> " ") <+> strBreak (ppCampaignEvent event)

summaryWidget :: Env -> UIState -> Widget Name
summaryWidget env uiState =
Expand Down Expand Up @@ -187,7 +185,7 @@ summaryWidget env uiState =
<=>
str ("New coverage: " <> timeElapsed uiState uiState.lastNewCov <> " ago") <+> fill ' '
rightSide =
padLeft (Pad 1) $
padLeft (Pad 1)
(rpcInfoWidget uiState.fetchedContracts uiState.fetchedSlots env.chainId)

timeElapsed :: UIState -> LocalTime -> String
Expand Down Expand Up @@ -324,7 +322,7 @@ tracesWidget vm = do
let traces = stripAnsiEscapeCodes $ showTraceTree dappInfo vm
pure $
if T.null traces then str ""
else str "Traces" <+> str ":" <=> (txtBreak traces)
else str "Traces" <+> str ":" <=> txtBreak traces

failWidget
:: MonadReader Env m
Expand All @@ -343,7 +341,6 @@ failWidget b test = do
( failureBadge <+> str (" with " ++ show test.result)
, shrinkWidget b test <=> titleWidget <=> s <=> str " " <=> traces
)
where

optWidget
:: MonadReader Env m
Expand Down Expand Up @@ -406,5 +403,3 @@ strBreak = strWrapWith $ defaultWrapSettings { breakLongWords = True }

txtBreak :: Text -> Widget n
txtBreak = txtWrapWith $ defaultWrapSettings { breakLongWords = True }

#endif
Loading

0 comments on commit 182580e

Please sign in to comment.