module Ethereum.Executable.Analyze ( analyzeMain ) where import Protolude hiding ((<.>)) import Compiler.Hoopl (runSimpleUniqueMonad) import Data.Time.Clock import Data.Time.Format import Ethereum.Analyzer.Debug import Ethereum.Analyzer.Solidity hiding (value) import Ethereum.Analyzer.Util import Options.Applicative import Options.Applicative.Text import System.Directory import System.FilePath import qualified Data.Text as T data AnalyzeFlags = AnalyzeFlags { astJson :: Text , workDir :: Text , debug :: Bool } deriving (Eq, Show) analyzeFlags :: Parser AnalyzeFlags analyzeFlags = AnalyzeFlags <$> textOption (long "astJson" <> value "" <> metavar "PATH" <> help "Path to the ast-json file.") <*> textOption (long "workDir" <> value "work" <> metavar "PATH" <> help "Path to the work directory (for outputs and intermediate files).") <*> switch (long "debug" <> help "Whether to print debug info") analyzeMain :: IO () analyzeMain = analyze =<< execParser opts where opts = info (analyzeFlags <**> helper) (fullDesc <> progDesc (toS ("Analyze the contract specified at PATH" :: Text)) <> header (toS ("ea-analyze - CLI interface for ethereum-analyzer" :: Text))) analyze :: AnalyzeFlags -> IO () analyze flags@AnalyzeFlags { astJson = theAstJson , workDir = theWorkDir , debug = _debug } = do tmpDirname <- getTmpDirname let sessionDir = toS theWorkDir toS tmpDirname createDirectoryIfMissing True sessionDir when _debug $ putText $ show flags content <- if theAstJson == "" || theAstJson == "-" then getContents else readFile $ toS theAstJson case decodeContracts content of Right contracts -> do savePrettyContracts contracts (toS $ sessionDir "contracts.ir") when _debug $ pprintContracts contracts saveCfgs contracts (toS $ sessionDir "cfgs") putText "Findings: \n" putText ("\n" `T.intercalate` concatMap findingsFor contracts) Left err -> putText err return () getTmpDirname :: IO Text getTmpDirname = do t <- getCurrentTime let formated = formatTime defaultTimeLocale (iso8601DateFormat (Just "%H:%M:%S")) t return $ toS formated savePrettyContracts :: [Contract] -> Text -> IO () savePrettyContracts cs filepath = writeFile (toS filepath) (prettyContracts cs) saveCfgs :: [Contract] -> Text -> IO () saveCfgs cs dirpath = do createDirectoryIfMissing True (toS dirpath) let hcs = runSimpleUniqueMonad $ mapM hoopleOf cs _ <- mapM writeContractCfgs hcs return () where writeContractCfgs hc = do _ <- mapM (writeFunCfgs (toS dirpath toS (hcName hc))) (hcFunctions hc) return () writeFunCfgs contractPrefix hf = do let dot = toDotText (hfCFG hf) writeFile (contractPrefix <.> (toS $ unIdfr $ hfName hf) <.> "CFG" <.> ".dot") dot