module Hint.Sandbox ( sandboxed ) where
import Hint.Base
import Hint.Conversions
import Hint.Context
import Hint.Typecheck ( typeChecks_unsandboxed )
import qualified GHC
import qualified DriverPhases as DP
import Data.Char
import Control.Monad.Error
import System.Directory
import System.FilePath
import System.Random
import qualified System.IO.UTF8 as UTF (writeFile)
type Expr = String
sandboxed :: (Expr -> Interpreter a) -> (Expr -> Interpreter a)
sandboxed do_stuff = \expr -> do dont_need_sandbox <- fromConf all_mods_in_scope
if dont_need_sandbox
then do_stuff expr
else usingAModule do_stuff expr
usingAModule :: (Expr -> Interpreter a) -> (Expr -> Interpreter a)
usingAModule do_stuff_on = \expr ->
do (mod_name, mod_file) <- mkModName
type_checks <- typeChecks_unsandboxed expr
case type_checks of
False -> do_stuff_on expr
True ->
do (loaded, imports) <- modulesInContext
let e = safeBndFor expr
let mod_text no_prel = textify [
["{-# OPTIONS_GHC -fno-monomorphism-restriction #-}"],
["{-# OPTIONS_GHC -fno-implicit-prelude #-}" | no_prel],
["module " ++ mod_name],
["where"],
["import " ++ m | m <- loaded ++ imports],
[e ++ " = " ++ expr] ]
let write_mod = liftIO . UTF.writeFile mod_file . mod_text
let t = fileTarget mod_file
setTopLevelModules []
setImports []
let go = do addTarget t
setTopLevelModules [mod_name]
do_stuff_on e
write_mod True
r <- go
`catchError` (\err -> case err of
WontCompile _ -> do removeTarget t
write_mod False
go
_ -> throwError err)
removeTarget t
setTopLevelModules loaded
setImports imports
return r
`finally`
clean_up mod_file
where textify = unlines . concat
clean_up f = liftIO $ do exists <- doesFileExist f
when exists $
return ()
addTarget :: GHC.Target -> Interpreter ()
addTarget t = do ghc_session <- fromSessionState ghcSession
mayFail $ do GHC.addTarget ghc_session t
res <- GHC.load ghc_session GHC.LoadAllTargets
return $ guard (isSucceeded res) >> Just ()
removeTarget :: GHC.Target -> Interpreter ()
removeTarget t = do ghc_session <- fromSessionState ghcSession
mayFail $ do GHC.removeTarget ghc_session (targetId t)
res <- GHC.load ghc_session GHC.LoadAllTargets
return $ guard (isSucceeded res) >> Just ()
targetId :: GHC.Target -> GHC.TargetId
targetId (GHC.Target _id _) = _id
fileTarget :: FilePath -> GHC.Target
fileTarget f = GHC.Target (GHC.TargetFile f $ Just next_phase) Nothing
where next_phase = DP.Cpp DP.HsSrcFile
safeBndFor :: Expr -> String
safeBndFor expr = "e_1" ++ filter isDigit expr
mkModName :: Interpreter (ModuleName, FilePath)
mkModName =
do n <- liftIO randomIO :: Interpreter Int
(ls,is) <- modulesInContext
let nums = concat [show (abs n), filter isDigit $ concat (ls ++ is)]
let mod_name = 'M':nums
tmp_dir <- liftIO getTemporaryDirectory
return (mod_name, tmp_dir </> nums)
modulesInContext :: Interpreter ([ModuleName], [ModuleName])
modulesInContext =
do ghc_session <- fromSessionState ghcSession
(l, i) <- liftIO $ GHC.getContext ghc_session
return (map fromGhcRep_ l, map fromGhcRep_ i)