on master help sign in
Give more helpful error messages when deserializing elmi files

Provides an error message indicating incorrect compiler version or corrupt file. Exit status will be 1 in either failure case and error message is printed to stderr. Uses the same checks against interfaces.data as well as standard .elmi files since both are susceptible to breaking in the same way. The following will be displayed when an elmi file has a different compiler version than the elm compiler that tries to load it: justin ~/Code/elm-lang.org/public/examples/Intermediate $ ~/Code/elm/dist/build/elm/elm Pong.elm Found build artifacts created by a different Elm compiler version. Please rebuilt cache/Pong.elmi and try again. The following will be displayed when a file that cannot be deserialized is encountered: justin ~/Code/elm-lang.org/public/examples/Intermediate $ ~/Code/elm/dist/build/elm/elm Pong.elm Got an error, 'demandInput: not enough bytes' at offset 10303 of cache/Pong.elmi. This error may be due to an outdated or corrupt artifact from a previous build. Please rebuild cache/Pong.elmi and try again.

commitc845e586edd07ed95ea419299077ca80b931a70c authorJustin Leitgeb <justin@stackbuilders.com> dateWed 30 Oct 2013, 17:44 (13 years ago) parenta24872f
7 files changed+125−44
Elm.cabal+8−4 compiler/Compiler.hs+30−24 compiler/Initialize.hs+1−1 compiler/InterfaceSerialization.hs+50 compiler/Metadata/Prelude.hs+27−10 compiler/SourceSyntax/Module.hs+7−2 compiler/Transform/Canonicalize.hs+2−3
Elm.cabal+8−4
@@ -53,6 +53,7 @@ Library
5353 Transform.Optimize,
5454 Metadata.Prelude,
5555 Initialize,
56+ InterfaceSerialization,
5657 Parse.Binop,
5758 Parse.Declaration,
5859 Parse.Expression,
@@ -79,13 +80,14 @@ Library
7980 Paths_Elm
8081
8182 Build-depends: base >=4.2 && <5,
82− binary,
83+ binary >= 0.6.4.0,
8384 blaze-html == 0.5.* || == 0.6.*,
84− blaze-markup == 0.5.1.*,
85+ blaze-markup,
8586 bytestring,
8687 cmdargs,
8788 containers >= 0.3,
8889 directory,
90+ either-unwrap,
8991 filepath,
9092 indents,
9193 language-ecmascript < 1,
@@ -122,6 +124,7 @@ Executable elm
122124 Transform.Optimize,
123125 Metadata.Prelude,
124126 Initialize,
127+ InterfaceSerialization,
125128 Parse.Binop,
126129 Parse.Declaration,
127130 Parse.Expression,
@@ -148,7 +151,7 @@ Executable elm
148151 Paths_Elm
149152
150153 Build-depends: base >=4.2 && <5,
151− binary,
154+ binary >= 0.6.4.0,
152155 blaze-html == 0.5.* || == 0.6.*,
153156 blaze-markup == 0.5.1.*,
154157 bytestring,
@@ -156,6 +159,7 @@ Executable elm
156159 containers >= 0.3,
157160 directory,
158161 filepath,
162+ either-unwrap,
159163 indents,
160164 language-ecmascript < 1,
161165 mtl >= 2,
@@ -191,7 +195,7 @@ Executable elm-doc
191195 Build-depends: aeson,
192196 aeson-pretty,
193197 base >=4.2 && <5,
194− binary,
198+ binary >= 0.6.4.0,
195199 bytestring,
196200 cmdargs,
197201 containers >= 0.3,
compiler/Compiler.hs+30−24
@@ -24,6 +24,7 @@ import Parse.Module (getModuleName)
2424import Initialize (buildFromSource, getSortedDependencies)
2525import Generate.JavaScript (jsModule)
2626import Generate.Html (createHtml, JSSource(..))
27+import qualified InterfaceSerialization as IS
2728import Paths_Elm
2829
2930import SourceSyntax.PrettyPrint (pretty, variable)
@@ -39,9 +40,9 @@ data Flags =
3940 , print_types :: Bool
4041 , scripts :: [FilePath]
4142 , no_prelude :: Bool
42− , cache_dir :: FilePath
43− , build_dir :: FilePath
44− , src_dir :: [FilePath]
43+ , cache_dir :: FilePath
44+ , build_dir :: FilePath
45+ , src_dir :: [FilePath]
4546 }
4647 deriving (Data,Typeable,Show,Eq)
4748
@@ -93,27 +94,30 @@ elmo flags filePath = cachePath flags filePath "elmo"
9394elmi :: Flags -> FilePath -> FilePath
9495elmi flags filePath = cachePath flags filePath "elmi"
9596
97+buildFile :: Flags -> Int -> Int -> Interfaces -> FilePath -> IO (String, ModuleInterface)
98+buildFile flags moduleNum numModules interfaces filePath = do
99+ compiled <- alreadyCompiled
100+ if compiled then do
101+ bytes <- IS.loadInterface (elmi flags filePath)
102+ case bytes >>= IS.interfaceDecode (elmi flags filePath) >>=
103+ IS.validVersion filePath of
96104
97−buildFile :: Flags -> Int -> Int -> Interfaces -> FilePath -> IO (String,ModuleInterface)
98−buildFile flags moduleNum numModules interfaces filePath =
99− do compiled <- alreadyCompiled
100− case compiled of
101− False -> compile
102− True -> do
103− handle <- openBinaryFile (elmi flags filePath) ReadMode
104− bits <- L.hGetContents handle
105− let info :: (String, ModuleInterface)
106− info = Binary.decode bits
107− modInterface = snd info
105+ Left err -> do
106+ hPutStrLn stderr err
107+ exitFailure
108108
109− when (print_types flags)
110− (printTypes interfaces
111− (iTypes modInterface)
112− (iAliases modInterface)
113− (iImports modInterface))
109+ Right (name, interface) -> do
110+ when (print_types flags)
111+ (printTypes interfaces
112+ (iTypes interface)
113+ (iAliases interface)
114+ (iImports interface))
115+
116+ return (name, interface)
117+
118+ else
119+ compile
114120
115− L.length bits `seq` hClose handle
116− return info
117121 where
118122 alreadyCompiled :: IO Bool
119123 alreadyCompiled = do
@@ -140,6 +144,7 @@ buildFile flags moduleNum numModules interfaces filePath =
140144
141145 createDirectoryIfMissing True (cache_dir flags)
142146 createDirectoryIfMissing True (build_dir flags)
147+
143148 metaModule <-
144149 case buildFromSource (no_prelude flags) interfaces source of
145150 Left errors -> do
@@ -152,6 +157,7 @@ buildFile flags moduleNum numModules interfaces filePath =
152157 (types metaModule) (aliases metaModule) (imports metaModule))
153158
154159 let interface = Canonical.interface name $ ModuleInterface {
160+ iVersion = showVersion version,
155161 iTypes = types metaModule,
156162 iImports = imports metaModule,
157163 iAdts = datatypes metaModule,
@@ -206,12 +212,12 @@ build flags rootFile = do
206212 where
207213 appendToOutput :: BS.ByteString -> FilePath -> IO BS.ByteString
208214 appendToOutput js filePath =
209− do src <- BS.readFile (elmo flags filePath)
210− return (BS.append src js)
215+ do
216+ src <- BS.readFile (elmo flags filePath)
217+ return (BS.append src js)
211218
212219 sources js = map Link (scripts flags) ++ [ Source js ]
213220
214−
215221buildFiles :: Flags -> Int -> Interfaces -> String -> [FilePath] -> IO (String, Interfaces)
216222buildFiles _ _ interfaces moduleName [] = return (moduleName, interfaces)
217223buildFiles flags numModules interfaces _ (filePath:rest) = do
compiler/Initialize.hs+1−1
@@ -92,7 +92,7 @@ readDeps srcDirs noPrelude root = evalStateT (go root) Set.empty
9292 case Parse.dependencies txt of
9393 Left err -> liftIO (putStrLn msg >> print err >> exitFailure)
9494 where msg = "Error resolving dependencies in " ++ root' ++ ":"
95−
95+
9696 Right (name,deps) ->
9797 do seen <- get
9898 let realDeps = Set.difference (Set.fromList deps) builtIns
compiler/InterfaceSerialization.hsnew file+50
@@ -0,0 +1,50 @@
1+module InterfaceSerialization ( loadInterface
2+ , interfaceDecode
3+ , validVersion
4+ ) where
5+
6+import qualified Data.ByteString.Lazy as L
7+import qualified Data.Binary as Binary
8+
9+import Paths_Elm (version)
10+import Data.Version (showVersion)
11+import System.Directory (doesFileExist)
12+import System.IO (hPutStrLn, stderr)
13+import System.Exit (exitFailure)
14+
15+import SourceSyntax.Module
16+
17+loadInterface :: FilePath -> IO (Either String L.ByteString)
18+loadInterface filePath = do
19+ exists <- doesFileExist filePath
20+ if exists
21+ then do
22+ byteString <- L.readFile filePath
23+ return $ Right byteString
24+
25+ else
26+ return $ Left $ "Unable to find file " ++ filePath ++
27+ " for deserialization!"
28+
29+interfaceDecode :: Binary.Binary a => FilePath -> L.ByteString ->
30+ Either String a
31+interfaceDecode filePath bytes = do
32+ case Binary.decodeOrFail bytes of
33+ Left (_, offset, err) -> do
34+ Left $ "Got an error, '" ++ err ++ "' at offset " ++
35+ show offset ++ " of " ++ filePath ++ ".\n\n" ++
36+ "This error may be due to an outdated or corrupt " ++
37+ "artifact from a previous build. Please rebuild " ++
38+ filePath ++ " and try again."
39+
40+ Right (_, _, binaryInfo) -> Right binaryInfo
41+
42+validVersion :: FilePath -> (String, ModuleInterface) ->
43+ Either String (String, ModuleInterface)
44+validVersion filePath (name, interface) =
45+ if (iVersion interface) == showVersion version then
46+ Right (name, interface)
47+ else
48+ Left $ "Found build artifacts created by a different " ++
49+ "Elm compiler version. Please rebuild " ++
50+ filePath ++ " and try again."
compiler/Metadata/Prelude.hs+27−10
@@ -11,7 +11,9 @@ import System.IO.Unsafe (unsafePerformIO)
1111import SourceSyntax.Module
1212import qualified Data.Binary as Binary
1313import qualified Data.ByteString.Lazy as BS
14+import Data.Either.Unwrap (fromRight)
1415
16+import qualified InterfaceSerialization as IS
1517
1618add :: Module t v -> Module t v
1719add (Module name exs ims stmts) = Module name exs (customIms ++ ims) stmts
@@ -46,14 +48,29 @@ safeReadDocs name =
4648 , " and specify your versions of Elm and your OS" ]
4749 exitFailure
4850
51+firstModuleInterface :: Interfaces -> Either String (String, ModuleInterface)
52+firstModuleInterface interfaces =
53+ if Map.null interfaces then
54+ Left "No interfaces found in serialized Prelude!"
55+ else
56+ Right $ head $ Map.toList interfaces
57+
4958readDocs :: FilePath -> IO Interfaces
50−readDocs name = do
51− exists <- doesFileExist name
52− case exists of
53− False -> ioError . userError $ "File Not Found"
54− True -> do
55− handle <- openBinaryFile name ReadMode
56− bits <- BS.hGetContents handle
57− let ifaces = Map.fromList (Binary.decode bits)
58− BS.length bits `seq` hClose handle
59− return ifaces
59+readDocs filePath = do
60+ bytes <- IS.loadInterface filePath
61+ let interfaces = bytes >>= IS.interfaceDecode filePath
62+
63+ -- Although every ModuleInterface that is deserialized in this collection
64+ -- contains the compiler version, we only check the first ModuleInterface
65+ -- since it doesn't make sense that different modules in Prelude would have
66+ -- been compiled by different compiler versions.
67+ case interfaces >>= firstModuleInterface >>= IS.validVersion filePath of
68+ Left err -> do
69+ hPutStrLn stderr err
70+ exitFailure
71+
72+ -- Unwrapping the Right value here is safe since the whole above chain
73+ -- returns a Right value. The toList/fromList is necessary because of a
74+ -- problem with looking up keys in the Map after deserialization. Example
75+ -- at https://gist.github.com/jsl/7294493.
76+ Right _ -> return $ Map.fromList $ Map.toList $ fromRight interfaces
compiler/SourceSyntax/Module.hs+7−2
@@ -14,6 +14,9 @@ import SourceSyntax.Type
1414import System.FilePath (joinPath)
1515import Control.Monad (liftM)
1616
17+import Paths_Elm (version)
18+import Data.Version (showVersion)
19+
1720data Module tipe var =
1821 Module [String] Exports Imports [Declaration tipe var]
1922 deriving (Show)
@@ -52,12 +55,13 @@ data MetadataModule t v = MetadataModule {
5255 datatypes :: [ (String, [String], [(String,[Type])]) ],
5356 foreignImports :: [(String, LExpr t v, String, Type)],
5457 foreignExports :: [(String, String, Type)]
55−}
58+} deriving Show
5659
5760type Interfaces = Map.Map String ModuleInterface
5861type ADT = (String, [String], [(String,[Type])])
5962
6063data ModuleInterface = ModuleInterface {
64+ iVersion :: String,
6165 iTypes :: Map.Map String Type,
6266 iImports :: [(String, ImportMethod)],
6367 iAdts :: [ADT],
@@ -67,8 +71,9 @@ data ModuleInterface = ModuleInterface {
6771
6872
6973instance Binary ModuleInterface where
70− get = ModuleInterface <$> get <*> get <*> get <*> get <*> get
74+ get = ModuleInterface <$> get <*> get <*> get <*> get <*> get <*> get
7175 put modul = do
76+ put (iVersion modul)
7277 put (iTypes modul)
7378 put (iImports modul)
7479 put (iAdts modul)
compiler/Transform/Canonicalize.hs+2−3
@@ -15,11 +15,11 @@ import qualified SourceSyntax.Type as Type
1515import qualified Transform.SortDefinitions as SD
1616import Text.PrettyPrint as P
1717
18−
1918interface :: String -> ModuleInterface -> ModuleInterface
2019interface moduleName iface =
2120 ModuleInterface
22− { iTypes = Map.mapKeys prefix (Map.map renameType' (iTypes iface))
21+ { iVersion = iVersion iface
22+ , iTypes = Map.mapKeys prefix (Map.map renameType' (iTypes iface))
2323 , iImports = iImports iface
2424 , iAdts = map (both prefix renameCtors) (iAdts iface)
2525 , iAliases = map (both prefix renameType') (iAliases iface)
@@ -95,7 +95,6 @@ metadataModule ifaces modul =
9595 realImports = filter (not . List.isPrefixOf "Native." . fst) (imports modul)
9696 initialEnv = Map.fromList (concatMap canon realImports ++ localEnv ++ globalEnv)
9797
98−
9998type Env = Map.Map String String
10099
101100extend :: Env -> Pattern -> Env