Skip to content

Commit ad31b07

Browse files
authored
Merge pull request #633 from ethereum/better-error-messages
Better error messages for JSON parsing
2 parents a7e947d + 9b0fd4e commit ad31b07

File tree

2 files changed

+33
-31
lines changed

2 files changed

+33
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
- Using the SMT solver to get a single concrete value for a symbolic expression
1616
and continue running, whenever possible
1717
- STATICCALL abstraction is now performed in case of symbolic arguments
18+
- Better error messages for JSON parsing
1819

1920
## Fixed
2021
- We now try to simplify expressions fully before trying to cast them to a concrete value

src/EVM/Solidity.hs

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ import System.FilePath.Posix
8585
import System.Process
8686
import Text.Read (readMaybe)
8787
import Witch (unsafeInto)
88-
88+
import Data.Either.Extra (maybeToEither)
8989

9090
data StorageItem = StorageItem
9191
{ slotType :: SlotType
@@ -285,7 +285,7 @@ makeSrcMaps = (\case (_, Fe, _) -> Nothing; x -> Just (done x))
285285
go c (xs, state, p) = (xs, internalError ("srcmap: y u " ++ show c ++ " in state" ++ show state ++ "?!?"), p)
286286

287287
-- | Reads all solc output json files found under the provided filepath and returns them merged into a BuildOutput
288-
readBuildOutput :: App m => FilePath -> ProjectType -> m (Either String BuildOutput)
288+
readBuildOutput :: App m => FilePath -> ProjectType -> m (Err BuildOutput)
289289
readBuildOutput root CombinedJSON = do
290290
let outDir = root </> "out"
291291
jsons <- liftIO $ findJsonFiles outDir
@@ -342,18 +342,19 @@ lineSubrange xs (s1, n1) i =
342342
then Nothing
343343
else Just (s1 - s2, min (s2 + n2 - s1) n1)
344344

345-
readSolc :: App m => ProjectType -> FilePath -> FilePath -> m (Either String BuildOutput)
345+
readSolc :: App m => ProjectType -> FilePath -> FilePath -> m (Err BuildOutput)
346346
readSolc pt root fp = do
347347
-- NOTE: we cannot and must not use Data.Text.IO.readFile because that takes the locale
348348
-- and may fail with very strange errors when the JSON it's reading
349349
-- contains any UTF-8 character -- which it will with foundry
350350
fileContents <- liftIO $ fmap Data.Text.Encoding.decodeUtf8 $ Data.ByteString.readFile fp
351351
let contractName = T.pack $ takeBaseName fp
352352
case readJSON pt contractName fileContents of
353-
Nothing -> pure . Left $ "unable to parse " <> show pt <> " project JSON: " <> fp <> " Contract: " <> show contractName
354-
Just (contracts, asts, sources) -> do
353+
Left err -> pure . Left $ "unable to parse " <> show pt <> " project JSON: " <> fp
354+
<> " Contract: " <> show contractName <> "\nError: " <> err
355+
Right (contracts, asts, sources) -> do
355356
conf <- readConfig
356-
when (conf.debug) $ liftIO $ putStrLn $ "Parsed constract: " <> show contractName <> " file: " <> fp
357+
when (conf.debug) $ liftIO $ putStrLn $ "Parsed contract: " <> show contractName <> " file: " <> fp
357358
sourceCache <- liftIO $ makeSourceCache root sources asts
358359
pure (Right (BuildOutput contracts sourceCache))
359360

@@ -406,31 +407,31 @@ functionAbi f = do
406407
force :: String -> Maybe a -> a
407408
force s = fromMaybe (internalError s)
408409

409-
readJSON :: ProjectType -> Text -> Text -> Maybe (Contracts, Asts, Sources)
410+
readJSON :: ProjectType -> Text -> Text -> Err (Contracts, Asts, Sources)
410411
readJSON CombinedJSON _ json = readCombinedJSON json
411412
readJSON _ contractName json = readFoundryJSON contractName json
412413

413414
-- | Reads a foundry json output
414-
readFoundryJSON :: Text -> Text -> Maybe (Contracts, Asts, Sources)
415+
readFoundryJSON :: Text -> Text -> Err (Contracts, Asts, Sources)
415416
readFoundryJSON contractName json = do
416-
runtime <- json ^? key "deployedBytecode"
417-
runtimeCode <- (toCode contractName) . strip0x'' <$> runtime ^? key "object" % _String
417+
runtime <- maybeToEither "missing 'deployedBytecode' field" $ json ^? key "deployedBytecode"
418+
runtimeCode <- maybeToEither "missing 'deployedBytecode.object' field" $
419+
(toCode contractName) . strip0x'' <$> runtime ^? key "object" % _String
418420
runtimeSrcMap <- case runtime ^? key "sourceMap" % _String of
419-
Nothing -> makeSrcMaps ""
420-
smap -> makeSrcMaps =<< smap
421+
Nothing -> Right $ force "Source map creation error" $ makeSrcMaps "" -- sourceMap is optional
422+
Just smap -> maybeToEither "invalid sourceMap format" $ makeSrcMaps smap
421423

422-
creation <- json ^? key "bytecode"
423-
creationCode <- (toCode contractName) . strip0x'' <$> creation ^? key "object" % _String
424+
creation <- maybeToEither "missing 'bytecode' field" $ json ^? key "bytecode"
425+
creationCode <- maybeToEither "missing 'bytecode.object' field" $
426+
(toCode contractName) . strip0x'' <$> creation ^? key "object" % _String
424427
creationSrcMap <- case creation ^? key "sourceMap" % _String of
425-
Nothing -> makeSrcMaps ""
426-
smap -> makeSrcMaps =<< smap
427-
428-
ast <- json ^? key "ast"
429-
path <- ast ^? key "absolutePath" % _String
430-
431-
abi <- toList <$> json ^? key "abi" % _Array
428+
Nothing -> Right $ force "Source map creation error" $ makeSrcMaps "" -- sourceMap is optional
429+
Just smap -> maybeToEither "invalid sourceMap format" $ makeSrcMaps smap
432430

433-
id' <- unsafeInto <$> json ^? key "id" % _Integer
431+
ast <- maybeToEither "missing 'ast' field. Recompile with `forge clean && forge build --ast`" $ json ^? key "ast"
432+
path <- maybeToEither "missing 'ast.absolutePath' field" $ ast ^? key "absolutePath" % _String
433+
abi <- maybeToEither "missing or invalid 'abi' array" $ toList <$> json ^? key "abi" % _Array
434+
id' <- maybeToEither "missing or invalid 'id' field" $ unsafeInto <$> json ^? key "id" % _Integer
434435

435436
let contract = SolcContract
436437
{ runtimeCodehash = keccak' (stripBytecodeMetadata runtimeCode)
@@ -447,10 +448,10 @@ readFoundryJSON contractName json = do
447448
, storageLayout = mkStorageLayout $ json ^? key "storageLayout"
448449
, immutableReferences = mempty -- TODO: foundry doesn't expose this?
449450
}
450-
pure ( Contracts $ Map.singleton (path <> ":" <> contractName) contract
451-
, Asts $ Map.singleton path ast
452-
, Sources $ Map.singleton (SrcFile id' (T.unpack path)) Nothing
453-
)
451+
Right ( Contracts $ Map.singleton (path <> ":" <> contractName) contract
452+
, Asts $ Map.singleton path ast
453+
, Sources $ Map.singleton (SrcFile id' (T.unpack path)) Nothing
454+
)
454455

455456
-- | Parses the standard json output from solc
456457
readStdJSON :: Text -> Maybe (Contracts, Asts, Sources)
@@ -508,18 +509,18 @@ readStdJSON json = do
508509
}, fromMaybe mempty srcContents))
509510

510511
-- deprecate me soon
511-
readCombinedJSON :: Text -> Maybe (Contracts, Asts, Sources)
512+
readCombinedJSON :: Text -> Err (Contracts, Asts, Sources)
512513
readCombinedJSON json = do
513-
contracts <- f . KeyMap.toHashMapText <$> (json ^? key "contracts" % _Object)
514-
sources <- toList . fmap (preview _String) <$> json ^? key "sourceList" % _Array
514+
contracts <- maybeToEither "missing or invalid 'contracts' field" $ f . KeyMap.toHashMapText <$> (json ^? key "contracts" % _Object)
515+
sources <- maybeToEither "missing or invalid 'sourceList' field" $ toList . fmap (preview _String) <$> json ^? key "sourceList" % _Array
516+
astsPre <- maybeToEither "JSON lacks abstract syntax trees (ast). Recompile with `forge clean && forge build --ast`" $ json ^? key "sources" % _Object
515517
pure ( Contracts contracts
516-
, Asts (Map.fromList (HMap.toList asts))
518+
, Asts (Map.fromList (HMap.toList (KeyMap.toHashMapText astsPre)))
517519
, Sources $ Map.fromList $
518520
(\(path, id') -> (SrcFile id' (T.unpack path), Nothing)) <$>
519521
zip (catMaybes sources) [0..]
520522
)
521523
where
522-
asts = KeyMap.toHashMapText $ fromMaybe (error "JSON lacks abstract syntax trees.") (json ^? key "sources" % _Object)
523524
f x = Map.fromList . HMap.toList $ HMap.mapWithKey g x
524525
g s x =
525526
let

0 commit comments

Comments
 (0)