{-# LANGUAGE BangPatterns #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} module G2.Liquid.Inference.G2Calls ( MeasureExs , MaxMeasures , PreEvals , PostEvals , FCEvals , Evals (..) , SpreadOutSolver (..) , gatherAllowedCalls , runLHInferenceAll , runLHInferenceCore , runLHCExSearch , checkFuncCall , checkCounterexample , emptyEvals , preEvals , postEvals , checkPre , checkPost , lookupEvals , mapEvals , mapAccumLEvals , deleteEvalsForFunc , printEvals , evalMeasures , formMeasureComps , chainReturnType) where import G2.Config import G2.Execution import G2.Execution.PrimitiveEval import G2.Interface import G2.Language as G2 import qualified G2.Language.ExprEnv as E import qualified G2.Language.PathConds as PC import G2.Lib.Printers import G2.Liquid.Inference.Config import G2.Liquid.AddCFBranch import G2.Liquid.Config import G2.Liquid.Conversion import G2.Liquid.ConvertCurrExpr import G2.Liquid.G2Calls import G2.Liquid.Helpers import G2.Liquid.Interface import G2.Liquid.LHReducers import G2.Liquid.TCValues import G2.Liquid.Types import G2.Liquid.TyVarBags import G2.Liquid.Inference.InfStack import G2.Liquid.Inference.Initalization import G2.Solver hiding (Assert) import G2.Translation import Language.Haskell.Liquid.Types hiding (Config, hs) import qualified Data.HashSet as HS import qualified Data.HashMap.Lazy as HM import Data.List import qualified Data.Map as M import Data.Maybe import qualified Data.Sequence as S import qualified Data.Text as T import Data.Tuple.Extra import Control.Monad import Control.Monad.Extra import Control.Exception import Control.Monad.IO.Class import qualified Control.Monad.State as SM import Data.Function import Data.Monoid ------------------------------------- -- Solvers ------------------------------------- -- | A solver that adds soft asserts to (try to) spread out integer values -- returned in a model. data SpreadOutSolver solver = SpreadOutSolver Integer solver instance Solver solver => Solver (SpreadOutSolver solver) where check (SpreadOutSolver _ solver) s pc = check solver s pc solve (SpreadOutSolver mx_size solver) s b is pc = let int_vs = filter (isInteger . typeOf) is max_vs = map (\i -> Id (Name "MAX_INT_ORD__??__" Nothing i Nothing) TyLitInt) . map fst $ zip [1..] int_vs max_vs_eq = map (flip ExtCond True) $ map (\mv -> foldr1 or_expr $ map (\iv -> abs_expr (Var iv) `eq` Var mv) int_vs) max_vs max_ord = map (flip ExtCond True) . map (\(x, y) -> Var x `le_expr` Var y) $ adj max_vs soft_space = map SoftPC . map (flip ExtCond True) . map (\(v, vs) -> sum_vars vs `lt_expr` Var v) . map (\(v:vs) -> (v, vs)) . filter (not . null) . inits $ reverse max_vs pc' = PC.fromList $ max_vs_eq ++ max_ord ++ soft_space pc_union = pc `PC.union` pc' in case null int_vs of False -> solve solver s b is pc_union True -> solve solver s b is pc where isInteger TyLitInt = True isInteger _ = False abs_expr x = App (Prim Abs TyUnknown) x eq x y = App (App (Prim Eq TyUnknown) x) y or_expr x y = App (App (Prim Or TyUnknown) x) y le_expr x y = App (App (Prim Le TyUnknown) x) y lt_expr x y = App (App (Prim Lt TyUnknown) x) y plus_expr x y = App (App (Prim Plus TyUnknown) x) y mult_expr x y = App (App (Prim Mult TyUnknown) x) y sum_vars = foldr plus_expr (Lit (LitInt mx_size)) . map (mult_expr (Lit (LitInt mx_size))) . map Var adj xs = zip xs $ tail xs close (SpreadOutSolver _ solver) = close solver ------------------------------------- -- Calling G2 ------------------------------------- {-# SPECIALISE runLHG2Inference :: (Solver solver, Simplifier simplifier) => Config -> SomeReducer (SM.StateT PrettyGuide IO) LHTracker -> SomeHalter (SM.StateT PrettyGuide IO) LHTracker -> SomeOrderer (SM.StateT PrettyGuide IO) LHTracker -> solver -> simplifier -> MemConfig -> Id -> State LHTracker -> Bindings -> SM.StateT PrettyGuide IO ([ExecRes AbstractedInfo], Bindings) #-} runLHG2Inference :: (MonadIO m, Solver solver, Simplifier simplifier) => Config -> SomeReducer m LHTracker -> SomeHalter m LHTracker -> SomeOrderer m LHTracker -> solver -> simplifier -> MemConfig -> Id -> State LHTracker -> Bindings -> m ([ExecRes AbstractedInfo], Bindings) runLHG2Inference config red hal ord solver simplifier pres_names init_id final_st bindings = do let only_abs_st = addTicksToDeepSeqCases (deepseq_walkers bindings) final_st (ret, final_bindings) <- case (red, hal, ord) of (SomeReducer red', SomeHalter hal', SomeOrderer ord') -> runG2ThroughExecution red' hal' ord' pres_names only_abs_st bindings ret' <- filterM (satState solver) ret let ret'' = onlyMinimalStates $ map (earlyExecRes final_bindings) ret' cleanupResultsInference solver simplifier config init_id final_bindings ret'' cleanupResultsInference :: (MonadIO m, Solver solver, Simplifier simplifier) => solver -> simplifier -> Config -> Id -> Bindings -> [ExecRes LHTracker] -> m ([ExecRes AbstractedInfo], Bindings) cleanupResultsInference solver simplifier config init_id bindings ers = do let ers2 = map (\er -> er { final_state = putSymbolicExistentialInstInExprEnv (final_state er) }) ers let ers3 = map (replaceHigherOrderNames (idName init_id) (input_names bindings)) ers2 (bindings', ers4) <- liftIO $ mapAccumM (reduceCalls runG2ThroughExecutionInference solver simplifier config) bindings ers3 ers5 <- liftIO $ mapM (checkAbstracted runG2ThroughExecutionInference solver simplifier config init_id bindings') ers4 ers6 <- liftIO $ mapM (runG2SolvingInference solver simplifier bindings') ers5 let ers7 = map (\er@(ExecRes { final_state = s }) -> (er { final_state = s {track = mapAbstractedInfoFCs (evalPrims (type_env s) (known_values s) . subVarFuncCall True (model s) (expr_env s) (type_classes s)) $ track s } })) ers6 return (ers7, bindings') replaceHigherOrderNames :: Name -> [Name] -> ExecRes LHTracker -> ExecRes LHTracker replaceHigherOrderNames init_name input_names er@(ExecRes { final_state = s@(State { expr_env = eenv, track = t })}) = let higher = higher_order_calls t input_names' = filter (hasFuncType . (E.!) eenv) input_names higher_num_init = zip input_names' (map (higherOrderName (nameOcc init_name)) [1..]) higher_num_all = concatMap (\fc -> let as = map nameFromVar $ filter hasFuncType (arguments fc) in zip as (map (higherOrderName (nameOcc $ funcName fc)) [1..]) ) . map (\fc -> if nameOcc (funcName fc) == "INITIALLY_CALLED_FUNC" then fc { funcName = init_name } else fc) . nubBy ((==) `on` funcName) $ all_calls t higher_num = higher_num_init ++ higher_num_all higher' = map (\fc -> fc { funcName = lookupErr (funcName fc) higher_num }) higher in er { final_state = s { track = t { higher_order_calls = higher' }}} where lookupErr x xs = case lookup x xs of Just v -> v Nothing -> x -- error $ "replaceHigherOrderNames: missing function name" ++ "\nhigher_num = " ++ show xs ++ "\nx = " ++ show x nameFromVar (Var (Id n _)) = n nameFromVar e = error $ "nameFromVar: not Var" ++ show e higherOrderName :: T.Text -> Int -> Name higherOrderName n i = Name n (Just "HIGHER_ORDER_??_") i Nothing runG2ThroughExecutionInference :: G2Call solver simplifier runG2ThroughExecutionInference red hal ord _ _ pres s b = do (fs, fb) <- case (red, hal, ord) of (SomeReducer red', SomeHalter hal', SomeOrderer ord') -> runG2ThroughExecution red' hal' ord' pres s b return (map (earlyExecRes fb) fs, fb) runG2SolvingInference :: (MonadIO m, Solver solver, Simplifier simplifier) => solver -> simplifier -> Bindings -> ExecRes AbstractedInfo -> m (ExecRes AbstractedInfo) runG2SolvingInference solver simplifier bindings (ExecRes { final_state = s }) = do let abs_resemble_real = softAbstractResembleReal s pc_with_soft = PC.union abs_resemble_real (path_conds s) s_with_soft_pc = s { path_conds = pc_with_soft } er_solving <- liftIO $ runG2SolvingResult solver simplifier bindings s_with_soft_pc case er_solving of SAT er_solving' -> do let er_solving'' = if fmap funcName (violated er_solving') == Just initiallyCalledFuncName then er_solving' { violated = Nothing } else er_solving' return er_solving'' UNSAT _ -> error "runG2SolvingInference: solving failed" Unknown _ _ -> do er_solving_no_min <- liftIO $ runG2SolvingResult solver simplifier bindings s case er_solving_no_min of SAT er_solving_no_min' -> do let er_solving_no_min'' = if fmap funcName (violated er_solving_no_min') == Just initiallyCalledFuncName then er_solving_no_min' { violated = Nothing } else er_solving_no_min' return er_solving_no_min'' _ -> error "runG2SolvingInference: solving failed with no minimization" earlyExecRes :: Bindings -> State t -> ExecRes t earlyExecRes b s@(State { expr_env = eenv, curr_expr = CurrExpr _ cexpr, sym_gens = gens }) = let viol = assert_ids s viol' = if fmap funcName viol == Just initiallyCalledFuncName then Nothing else viol in ExecRes { final_state = s , conc_args = fixed_inputs b ++ mapMaybe getArg (input_names b) , conc_out = cexpr , conc_sym_gens = fmap fromJust . S.filter isJust $ fmap getArg gens , violated = viol' } where getArg n = case E.lookup n eenv of Just e@(Lam _ _ _) -> Just . Var $ Id n (typeOf e) Just e -> Just e Nothing -> Nothing satState :: ( MonadIO m , Named t , ASTContainer t Expr , ASTContainer t Type , Solver solver) => solver -> State t -> m Bool satState solver s | true_assert s = do r <- liftIO $ check solver s (path_conds s) case r of SAT _ -> return True UNSAT _ -> return False Unknown _ _ -> return False | otherwise = return False -- | Generate soft path constraints that encourage the `abstract` function call arguments -- to be the same as the `real` function call arguments. softAbstractResembleReal :: State AbstractedInfo -> PathConds softAbstractResembleReal = foldr PC.union PC.empty . map softAbstractResembleReal' . abs_calls . track softAbstractResembleReal' :: Abstracted -> PathConds softAbstractResembleReal' abstracted = let ars_pairs = zip (arguments $ abstract abstracted) (arguments $ real abstracted) ret_pair = (returns $ abstract abstracted, returns $ real abstracted) in foldr PC.union PC.empty . map PC.fromList $ map (uncurry softPair) (ret_pair:ars_pairs) softPair :: Expr -> Expr -> [PathCond] softPair v1@(Var (Id _ t1)) e2 | isPrimType t1 = assert (t1 == typeOf e2) [MinimizePC $ App (Prim Abs TyUnknown) (App (App (Prim Minus TyUnknown) v1) e2)] softPair e1 v2@(Var (Id _ t2)) | isPrimType t2 = assert (typeOf e1 == t2) [MinimizePC $ App (Prim Abs TyUnknown) (App (App (Prim Minus TyUnknown) e1) v2)] softPair (App e1 e2) (App e1' e2') = softPair e1 e1' ++ softPair e2 e2' softPair _ _ = [] ------------------------------------- -- Generating Allowed Inputs/Outputs ------------------------------------- -- By symbolically executing from user-specified functions, and gathering -- all called functions, we can get functions calls that MUST be allowed by -- the specifications gatherAllowedCalls :: T.Text -> Maybe T.Text -> LiquidReadyState -> [GhcInfo] -> InferenceConfig -> Config -> LHConfig -> IO [FuncCall] gatherAllowedCalls entry m lrs ghci infconfig config lhconfig = do let config' = config -- { only_top = False } LiquidData { ls_state = s , ls_bindings = bindings , ls_memconfig = pres_names } <- processLiquidReadyStateWithCall lrs ghci entry m config' lhconfig mempty let (s', bindings') = (s, bindings) -- execStateM addTrueAssertsAll s bindings SomeSolver solver <- initSolver config' let simplifier = IdSimplifier s'' = repCFBranch $ s' { true_assert = True , track = [] :: [FuncCall] } (red, hal, ord) <- gatherReducerHalterOrderer infconfig config' lhconfig solver simplifier (exec_res, bindings'') <- SM.evalStateT (runG2WithSomes red hal ord solver simplifier pres_names s'' bindings') (mkPrettyGuide ()) putStrLn $ "length exec_res = " ++ show (length exec_res) let called = concatMap (\er -> let fs = final_state er in map (fs,) $ track fs) exec_res fc_red = SomeReducer (stdRed (sharing config') retReplaceSymbFuncVar solver simplifier) (_, red_calls) <- mapAccumM (\b (fs, fc) -> do (_, b', rfc) <- reduceFuncCall runG2WithSomes fc_red solver simplifier fs b fc return (b', rfc)) bindings'' called close solver return red_calls repCFBranch :: ASTContainer t Expr => t -> t repCFBranch = modifyASTs repCFBranch' repCFBranch' :: Expr -> Expr repCFBranch' nd@(NonDet (e:_)) | Let b (Assert fc ae1 ae2) <- e = Let b $ Assume fc ae1 ae2 | otherwise = nd repCFBranch' (Let b (Assert fc ae1 ae2)) = Let b $ Assume fc ae1 ae2 repCFBranch' e = e gatherReducerHalterOrderer :: (MonadIO m, Solver solver, Simplifier simplifier) => InferenceConfig -> Config -> LHConfig -> solver -> simplifier -> IO ( SomeReducer (SM.StateT PrettyGuide m) [FuncCall] , SomeHalter (SM.StateT PrettyGuide m) [FuncCall] , SomeOrderer (SM.StateT PrettyGuide m) [FuncCall]) gatherReducerHalterOrderer infconfig config lhconfig solver simplifier = do let share = sharing config state_name = Name "state" Nothing 0 Nothing m_logger = fmap SomeReducer $ getLogger config timer_halter <- stdTimerHalter (timeout_se infconfig * 3) let red = case m_logger of Just logger -> logger .~> SomeReducer (gathererReducer ~> stdRed share retReplaceSymbFuncVar solver simplifier) Nothing -> SomeReducer (gathererReducer ~> stdRed share retReplaceSymbFuncVar solver simplifier) return (red .== Finished .--> (taggerRed state_name :== Finished --> nonRedPCRed) , SomeHalter (discardIfAcceptedTagHalter state_name <~> switchEveryNHalter (switch_after lhconfig) <~> swhnfHalter <~> timer_halter) , SomeOrderer (incrAfterN 2000 (adtSizeOrderer 0 Nothing))) ------------------------------- -- Direct Counterexamples Calls ------------------------------- -- This does all the work to take LH source code and run symbolic execution on the named function. -- In the inference algorithm itself, we don't want to use this, since if we did we would be -- needlessly repeatedly reading and compiling the code. But it's to have an "end-to-end" -- function to just running the symbolic execution, for debugging. {-# SPECIALISE runLHInferenceAll :: InferenceConfig -> Config -> LHConfig -> T.Text -> [FilePath] -> [FilePath] -> IO (([ExecRes AbstractedInfo], Bindings), Id) #-} runLHInferenceAll :: MonadIO m => InferenceConfig -> Config -> LHConfig -> T.Text -> [FilePath] -> [FilePath] -> m (([ExecRes AbstractedInfo], Bindings), Id) runLHInferenceAll infconfig config g2lhconfig func proj fp = do -- Initialize LiquidHaskell (ghci, lhconfig) <- liftIO $ getGHCI infconfig proj fp let g2config = config { mode = Liquid , steps = 2000 } transConfig = simplTranslationConfig { simpl = False } (main_mod, exg2) <- liftIO $ translateLoaded proj fp transConfig g2config let (lrs, g2config', g2lhconfig', infconfig') = initStateAndConfig exg2 main_mod g2config g2lhconfig infconfig ghci let configs = Configs { g2_config = g2config', g2lh_config = g2lhconfig', lh_config = lhconfig, inf_config = infconfig'} execInfStack configs newProgress (runLHInferenceCore func main_mod lrs ghci) ------------------------------- -- Generating Counterexamples ------------------------------- runLHInferenceCore :: MonadIO m => T.Text -> Maybe T.Text -> LiquidReadyState -> [GhcInfo] -> InfStack m (([ExecRes AbstractedInfo], Bindings), Id) runLHInferenceCore entry m lrs ghci = do MaxSize max_coeff_sz <- maxSynthCoeffSizeI g2config <- g2ConfigM lhconfig <- g2lhConfigM infconfig <- infConfigM LiquidData { ls_state = final_st , ls_bindings = bindings , ls_id = ifi , ls_counterfactual_name = cfn , ls_memconfig = pres_names } <- liftIO $ processLiquidReadyStateWithCall lrs ghci entry m g2config lhconfig mempty SomeSolver solver <- liftIO $ initSolver g2config -- let solver' = SpreadOutSolver max_coeff_sz solver let simplifier = IdSimplifier final_st' = swapHigherOrdForSymGen bindings final_st (red, hal, ord) <- inferenceReducerHalterOrderer infconfig g2config lhconfig solver simplifier entry m cfn final_st' -- liftIO is important so that we specialize runLHG2Inference (exec_res, final_bindings) <- liftIO $ SM.evalStateT (runLHG2Inference g2config red hal ord solver simplifier pres_names ifi final_st' bindings) (mkPrettyGuide ()) liftIO $ close solver return ((exec_res, final_bindings), ifi) inferenceReducerHalterOrderer :: (MonadIO m, MonadIO m_run, Solver solver, Simplifier simplifier) => InferenceConfig -> Config -> LHConfig -> solver -> simplifier -> T.Text -> Maybe T.Text -> Name -> State LHTracker -> InfStack m ( SomeReducer (SM.StateT PrettyGuide m_run) LHTracker , SomeHalter (SM.StateT PrettyGuide m_run) LHTracker , SomeOrderer (SM.StateT PrettyGuide m_run) LHTracker) inferenceReducerHalterOrderer infconfig config lhconfig solver simplifier entry mb_modname cfn st = do extra_ce <- extraMaxCExI (entry, mb_modname) extra_time <- extraMaxTimeI (entry, mb_modname) -- time <- liftIO $ getCurrentTime let share = sharing config state_name = Name "state" Nothing 0 Nothing abs_ret_name = Name "abs_ret" Nothing 0 Nothing ce_num = max_ce infconfig + extra_ce lh_max_outputs = lhMaxOutputsHalter ce_num timeout = timeout_se infconfig + extra_time m_logger = fmap SomeReducer $ getLogger config -- m_logger = if entry == "mapReduce" then Just (SomeReducer $ PrettyLogger ("a_mapReduce" ++ show time) (mkPrettyGuide ())) else getLogger config liftIO $ putStrLn $ "ce num for " ++ T.unpack entry ++ " is " ++ show ce_num liftIO $ putStrLn $ "timeout for " ++ T.unpack entry ++ " is " ++ show timeout timer_halter <- liftIO $ stdTimerHalter (timeout * 2) lh_timer_halter <- liftIO $ lhStdTimerHalter timeout let halter = lhAbsHalter entry mb_modname (expr_env st) <~> lh_max_outputs <~> switchEveryNHalter (switch_after lhconfig) <~> lhSWHNFHalter <~> timer_halter <~> lh_timer_halter let some_red = existentialInstRed :== NoProgress .--> lhRed cfn :== Finished .--> redArbErrors :== Finished .--> SomeReducer (allCallsRed ~> higherOrderCallsRed ~> stdRed share retReplaceSymbFuncVar solver simplifier) return $ ( (case m_logger of Just logger -> logger .~> some_red Nothing -> some_red) .== Finished .--> (taggerRed state_name :== Finished --> nonRedPCRed) .== Finished .--> (taggerRed abs_ret_name :== Finished --> nonRedAbstractReturnsRed) , SomeHalter (discardIfAcceptedTagHalter state_name <~> halter) , SomeOrderer (incrAfterN 2000 (quotTrueAssert (ordComb (+) (pcSizeOrderer 0) (adtSizeOrderer 0 (Just instFuncTickName)))))) runLHCExSearch :: MonadIO m => T.Text -> Maybe T.Text -> LiquidReadyState -> [GhcInfo] -> InfStack m (([ExecRes AbstractedInfo], Bindings), Id) runLHCExSearch entry m lrs ghci = do g2config <- g2ConfigM lhconfig <- g2lhConfigM infconfig <- infConfigM let lhconfig' = lhconfig { counterfactual = NotCounterfactual , only_top = False} LiquidData { ls_state = final_st , ls_bindings = bindings , ls_id = ifi , ls_counterfactual_name = cfn , ls_memconfig = pres_names } <- liftIO $ processLiquidReadyStateWithCall lrs ghci entry m g2config lhconfig' mempty SomeSolver solver <- liftIO $ initSolver g2config let simplifier = IdSimplifier final_st' = swapHigherOrdForSymGen bindings final_st (red, hal, ord) <- realCExReducerHalterOrderer infconfig g2config lhconfig' entry m solver simplifier cfn -- liftIO is important so that we specialize runLHG2Inference (exec_res, final_bindings) <- liftIO $ SM.evalStateT (runLHG2Inference g2config red hal ord solver simplifier pres_names ifi final_st' bindings) (mkPrettyGuide ()) liftIO $ close solver return ((exec_res, final_bindings), ifi) realCExReducerHalterOrderer :: (MonadIO m, MonadIO m_run, Solver solver, Simplifier simplifier) => InferenceConfig -> Config -> LHConfig -> T.Text -> Maybe T.Text -> solver -> simplifier -> Name -> InfStack m ( SomeReducer (SM.StateT PrettyGuide m_run) LHTracker , SomeHalter (SM.StateT PrettyGuide m_run) LHTracker , SomeOrderer (SM.StateT PrettyGuide m_run) LHTracker) realCExReducerHalterOrderer infconfig config lhconfig entry modname solver simplifier cfn = do extra_ce <- extraMaxCExI (entry, modname) extra_depth <- extraMaxDepthI liftIO . putStrLn $ "extra_depth = " ++ show extra_depth let share = sharing config state_name = Name "state" Nothing 0 Nothing abs_ret_name = Name "abs_ret" Nothing 0 Nothing ce_num = max_ce infconfig + extra_ce lh_max_outputs = lhMaxOutputsHalter ce_num m_logger = fmap SomeReducer $ getLogger config timer_halter <- liftIO $ stdTimerHalter (timeout_se infconfig) let halter = lh_max_outputs <~> switchEveryNHalter (switch_after lhconfig) <~> zeroHalter (0 + extra_depth) <~> lhAcceptIfViolatedHalter <~> timer_halter lh_std_red = lhRed cfn :== Finished --> stdRed share retReplaceSymbFuncVar solver simplifier log_opt_red = case m_logger of Just logger -> logger .~> lh_std_red Nothing -> lh_std_red return $ (log_opt_red .== Finished .--> (taggerRed state_name :== Finished --> nonRedPCRed) .== Finished .--> (taggerRed abs_ret_name :== Finished --> nonRedAbstractReturnsRed) , SomeHalter (discardIfAcceptedTagHalter state_name <~> halter) , SomeOrderer (incrAfterN 1000 (adtSizeOrderer 0 Nothing))) swapHigherOrdForSymGen :: Bindings -> State t -> State t swapHigherOrdForSymGen b s@(State { expr_env = eenv }) = let is = filter (isTyFun . typeOf) $ inputIds s b eenv' = foldr swapForSG eenv is in s { expr_env = eenv' } swapForSG :: Id -> ExprEnv -> ExprEnv swapForSG i eenv = let as = map (\at -> case at of NamedType i' -> (TypeL, i') AnonType t -> (TermL, Id (Name "x" Nothing 0 Nothing) t)) $ spArgumentTypes i r = returnType i sg_i = Id (Name "sym_gen" Nothing 0 Nothing) r in E.insert (idName i) (Let [(sg_i, SymGen SNoLog r)] $ mkLams as (Var sg_i)) eenv ------------------------------- -- Checking Counterexamples ------------------------------- -- Does a given FuncCall (counterexample) violate a specification? -- This allows us to check if a found counterexample violates a user-provided specifications, -- or a synthesized specification. -- Returns True if the original Assertions are True (i.e. not violated) checkFuncCall :: LiquidReadyState -> [GhcInfo] -> Config -> LHConfig -> FuncCall -> IO Bool checkFuncCall = checkCounterexample checkCounterexample :: LiquidReadyState -> [GhcInfo] -> Config -> LHConfig -> FuncCall -> IO Bool checkCounterexample lrs ghci config lhconfig cex@(FuncCall { funcName = Name n m _ _ }) = do let lhconfig' = lhconfig { counterfactual = NotCounterfactual } -- We use the same function to instantiate this state as in runLHInferenceCore, so all the names line up LiquidData { ls_state = s , ls_bindings = bindings } <- processLiquidReadyStateWithCall lrs ghci n m config lhconfig' mempty let s' = checkCounterexample' cex s SomeSolver solver <- initSolver config (fsl, _) <- genericG2Call config solver s' bindings close solver -- We may return multiple states if any of the specifications contained a SymGen return $ any (currExprIsTrue . final_state) fsl checkCounterexample' :: FuncCall -> State t -> State t checkCounterexample' fc@(FuncCall { funcName = n }) s@(State { expr_env = eenv, known_values = kv }) | Just e <- E.lookup n eenv = let e' = toJustSpec kv fc (leadingLamIds e) (inLams e) in s { curr_expr = CurrExpr Evaluate e' , true_assert = True } | otherwise = error $ "checkCounterexample': Name not found " ++ show n toJustSpec :: KnownValues -> FuncCall -> [Id] -> Expr -> Expr toJustSpec _ (FuncCall { arguments = ars, returns = ret }) is (Let [(b, _)] (Assert _ e _)) = let rep = (Var b, ret):zip (map Var is) ars in foldr (uncurry replaceASTs) e rep toJustSpec kv _ _ e = assert (not $ hasAssert e) mkTrue kv hasAssert :: Expr -> Bool hasAssert = getAny . evalASTs hasAssert' hasAssert' :: Expr -> Any hasAssert' (Assert _ _ _) = Any True hasAssert' _ = Any False currExprIsTrue :: State t -> Bool currExprIsTrue (State { curr_expr = CurrExpr _ (Data (DataCon (Name dcn _ _ _) _))}) = dcn == "True" currExprIsTrue _ = False ------------------------------- -- Checking Pre and Post Conditions ------------------------------- type PreEvals b = FCEvals b type PostEvals b = FCEvals b type FCEvals b = HM.HashMap Name (HM.HashMap FuncCall b) data Evals b = Evals { pre_evals :: PreEvals b , post_evals :: PostEvals b } deriving Show emptyEvals :: Evals b emptyEvals = Evals { pre_evals = HM.empty, post_evals = HM.empty } preEvals :: (InfConfigM m, MonadIO m) => Evals Bool -> LiquidReadyState -> [GhcInfo] -> [(FuncCall, [HigherOrderFuncCall])] -> m (Evals Bool) preEvals evals@(Evals { pre_evals = pre }) lrs ghci fcs = do (pre', _) <- foldM (uncurry (runEvals checkPre' ghci lrs)) (pre, HM.empty) fcs return $ evals { pre_evals = pre' } -- return . HM.fromList =<< mapM (\fc -> return . (fc,) =<< checkPre lrs ghci fc) fcs postEvals :: (InfConfigM m, MonadIO m) => Evals Bool -> LiquidReadyState -> [GhcInfo] -> [(FuncCall, [HigherOrderFuncCall])] -> m (Evals Bool) postEvals evals@(Evals { post_evals = post }) lrs ghci fcs = do (post', _) <- foldM (uncurry (runEvals checkPost' ghci lrs)) (post, HM.empty) fcs return $ evals { post_evals = post' } runEvals :: (InfConfigM m, MonadIO m) => (LiquidData -> FuncCall -> [HigherOrderFuncCall] -> m Bool) -> [GhcInfo] -> LiquidReadyState -> FCEvals Bool -> HM.HashMap (T.Text, Maybe T.Text) LiquidData -> (FuncCall, [HigherOrderFuncCall]) -> m (FCEvals Bool, HM.HashMap (T.Text, Maybe T.Text) LiquidData) runEvals f ghci lrs hm ld_m (fc, hfc) = let n = zeroOutName $ funcName fc n_hm = maybe HM.empty id (HM.lookup n hm) in if fc `HM.member` n_hm then return (hm, ld_m) else do let nt = nameTuple (funcName fc) (ld, ld_m') <- case HM.lookup nt ld_m of Just ld' -> return (ld', ld_m) Nothing -> do ld' <- checkPreOrPostLD ghci lrs fc return (ld', HM.insert nt ld' ld_m) pr <- f ld fc hfc return $ (HM.insert n (HM.insert fc pr n_hm) hm, ld_m') checkPre :: (InfConfigM m, MonadIO m) => [GhcInfo] -> LiquidReadyState -> FuncCall -> [HigherOrderFuncCall] -> m Bool checkPre ghci lrs fc hfc = do ld <- checkPreOrPostLD ghci lrs fc checkPre' ld fc hfc checkPre' :: (InfConfigM m, MonadIO m) => LiquidData -> FuncCall -> [HigherOrderFuncCall] -> m Bool checkPre' ld fc@(FuncCall { funcName = n }) hfc = do r <- checkPreOrPost' (M.map thd3 . zeroOutKeys . ls_assumptions) arguments ld fc case r of False -> return False True -> do let assumpts = M.lookup (zeroOutName n) . zeroOutKeys . ls_assumptions $ ld case assumpts of Just (_, higher_assumpts, _) -> do rs <- allM (checkPreHigherOrder ld (catMaybes higher_assumpts)) $ filter (\h -> nameOcc (funcName h) == nameOcc n) hfc return rs Nothing -> return True checkPreHigherOrder :: (InfConfigM m, MonadIO m) => LiquidData -> [Expr] -> HigherOrderFuncCall -> m Bool checkPreHigherOrder ld es (FuncCall {funcName = (Name _ _ i _), arguments = as, returns = r }) = do config <- g2ConfigM SomeSolver solver <- liftIO $ initSolver config let e = es !! (i - 1) e' = insertInLams (\_ in_e -> case in_e of Let [(b, _)] le -> Let [(b, r)] le _ -> error "checkPreHigherOrder: unexpected expresssion form") e s' = (ls_state ld) { curr_expr = CurrExpr Evaluate . modifyASTs repAssumeWithAssumption . mkApp $ e':as , true_assert = True } bindings = ls_bindings ld (fsl, _) <- liftIO $ genericG2Call config solver s' bindings liftIO $ close solver -- We may return multiple states if any of the specifications contained a SymGen return $ any (currExprIsTrue . final_state) fsl where repAssumeWithAssumption (Assume _ e _) = e repAssumeWithAssumption e = e checkPost :: (InfConfigM m, MonadIO m) => [GhcInfo] -> LiquidReadyState -> FuncCall -> [HigherOrderFuncCall] -> m Bool checkPost ghci lrs fc hfc = do ld <- checkPreOrPostLD ghci lrs fc checkPost' ld fc hfc checkPost' :: (InfConfigM m, MonadIO m) => LiquidData -> FuncCall -> [HigherOrderFuncCall] -> m Bool checkPost' ld fc _ = checkPreOrPost' (zeroOutKeys . ls_posts) (\fc_ -> arguments fc_ ++ [returns fc_]) ld fc zeroOutKeys :: M.Map Name v -> M.Map Name v zeroOutKeys = M.mapKeys zeroOutName zeroOutName :: Name -> Name zeroOutName (Name n m _ l) = Name n m 0 l checkPreOrPostLD :: (InfConfigM m, MonadIO m) => [GhcInfo] -> LiquidReadyState -> FuncCall -> m LiquidData checkPreOrPostLD lrs ghci (FuncCall { funcName = Name n m _ _ }) = do config <- g2ConfigM lhconfig <- g2lhConfigM let lhconfig' = lhconfig { counterfactual = NotCounterfactual } -- We use the same function to instantiate this state as in runLHInferenceCore, so all the names line up liftIO $ processLiquidReadyStateWithCall ghci lrs n m config lhconfig' mempty checkPreOrPost' :: (InfConfigM m, MonadIO m) => (LiquidData -> M.Map Name Expr) -> (FuncCall -> [Expr]) -> LiquidData -> FuncCall -> m Bool checkPreOrPost' extract ars ld@(LiquidData { ls_state = s, ls_bindings = bindings }) cex = do config <- g2ConfigM -- We use the same function to instantiate this state as in runLHInferenceCore, so all the names line up case checkFromMap ars (extract ld) cex s of Just s' -> do SomeSolver solver <- liftIO $ initSolver config (fsl, _) <- liftIO $ genericG2Call config solver s' bindings liftIO $ close solver -- We may return multiple states if any of the specifications contained a SymGen return $ any (currExprIsTrue . final_state) fsl -- If there is no explicit specification, the specification is implicitly True Nothing -> return True checkFromMap :: (FuncCall -> [Expr]) -> M.Map Name Expr -> FuncCall -> State t -> Maybe (State t) checkFromMap ars specs fc@(FuncCall { funcName = n }) s = let e = M.lookup (zeroOutName n) specs in case e of Just e' -> let e'' = mkApp $ e':ars fc in Just $ s { curr_expr = CurrExpr Evaluate e'' , true_assert = True } Nothing -> Nothing lookupEvals :: FuncCall -> FCEvals a -> Maybe a lookupEvals fc@(FuncCall { funcName = n }) fce = HM.lookup fc =<< HM.lookup (zeroOutName n) fce mapEvals :: (a -> b) -> Evals a -> Evals b mapEvals f (Evals { pre_evals = pre, post_evals = post }) = Evals { pre_evals = HM.map (HM.map f) pre, post_evals = HM.map (HM.map f) post } mapAccumLEvals :: (a -> b -> (a, c)) -> a -> Evals b -> (a, Evals c) mapAccumLEvals f inital ev = let (inital', pre') = mapAccumL (mapAccumL f) inital (pre_evals ev) (inital'', post') = mapAccumL (mapAccumL f) inital' (post_evals ev) in (inital'', ev { pre_evals = pre', post_evals = post' }) deleteEvalsForFunc :: Name -> Evals a -> Evals a deleteEvalsForFunc n (Evals { pre_evals = pre_ev, post_evals = post_ev }) = Evals { pre_evals = HM.delete (zeroOutName n) pre_ev , post_evals = HM.delete (zeroOutName n) post_ev } printEvals :: (a -> T.Text) -> Evals a -> T.Text printEvals f (Evals { pre_evals = pre, post_evals = post }) = "Evals {\npre_evals = " <> printEvals' f pre <> "\npost_evals = " <> printEvals' f post <> "\n}" printEvals' :: (a -> T.Text) -> FCEvals a -> T.Text printEvals' f = T.intercalate "\n" . map (\(fc, v) -> printFuncCall fc <> " -> " <> f v) . HM.toList . HM.unions . HM.elems ------------------------------- -- Eval Measures ------------------------------- -- Evaluate all relevant measures on a given expression type MeasureExs = HM.HashMap Expr (HM.HashMap [Name] Expr) type MaxMeasures = Int evalMeasures :: (InfConfigM m, ProgresserM m, MonadIO m) => MeasureExs -> LiquidReadyState -> [GhcInfo] -> [Expr] -> m MeasureExs evalMeasures init_meas lrs ghci es = do config <- g2ConfigM let memc = emptyMemConfig { pres_func = presMeasureNames } LiquidData { ls_state = s , ls_bindings = bindings , ls_measures = meas , ls_tcv = tcv , ls_memconfig = pres_names } <- liftIO $ extractWithoutSpecs lrs (Id (Name "" Nothing 0 Nothing) TyUnknown) ghci memc let s' = s { true_assert = True } (final_s, final_b) = markAndSweepPreserving pres_names s' bindings tot_meas = E.filter (isTotal (type_env s)) meas SomeSolver solver <- liftIO $ initSolver config meas_res <- foldM (evalMeasures' (final_s {type_env = type_env s}) final_b solver config tot_meas tcv) init_meas $ filter (not . isError) es liftIO $ close solver return meas_res where meas_names = measureNames ghci meas_nameOcc = map (\(Name n md _ _) -> (n, md)) $ map symbolName meas_names presMeasureNames s _ hs = let eenv = E.filterWithKey (\(Name n md _ _) _ -> (n, md) `elem` meas_nameOcc) (expr_env s) eenv_meas_names = E.keys eenv in foldr HS.insert hs eenv_meas_names isError (Prim Error _) = True isError _ = False isTotal :: TypeEnv -> Expr -> Bool isTotal tenv = getAll . evalASTs isTotal' where isTotal' (Case i _ _ as) | TyCon n _:_ <- unTyApp (typeOf i) , Just adt <- HM.lookup n tenv = All (length (dataCon adt) == length (filter isDataAlt as)) isTotal' (Case _ _ _ _) = All False isTotal' _ = All True isDataAlt (G2.Alt (DataAlt _ _) _) = True isDataAlt _ = False evalMeasures' :: ( InfConfigM m , MonadIO m , ProgresserM m , ASTContainer t Expr , ASTContainer t Type , Named t , Solver solver , Show t) => State t -> Bindings -> solver -> Config -> Measures -> TCValues -> MeasureExs -> Expr -> m MeasureExs evalMeasures' s bindings solver config meas tcv init_meas e = do MaxSize max_meas <- maxSynthFormSizeM let m_sts = evalMeasures'' (fromInteger max_meas) s bindings meas tcv e foldM (\meas_exs (ns, e_in, s_meas) -> do case HM.lookup ns =<< HM.lookup e_in meas_exs of Just _ -> return meas_exs Nothing -> do (er, _) <- liftIO $ genericG2Call config solver s_meas bindings case er of [er'] -> let e_out = conc_out er' in return $ HM.insertWith HM.union e_in (HM.singleton ns e_out) meas_exs [] -> return $ HM.insertWith HM.union e_in (HM.singleton ns (Prim Undefined TyBottom)) meas_exs _ -> error "evalMeasures': Bad G2 Call") init_meas m_sts evalMeasures'' :: Int -> State t -> Bindings -> Measures -> TCValues -> Expr -> [([Name], Expr, State t)] evalMeasures'' mx_meas s b m tcv e = let meas_comps = formMeasureComps mx_meas (type_env s) (typeOf e) m rel_m = mapMaybe (\ns_me -> case chainReturnType (typeOf e) (map snd ns_me) of Just (_, vms) -> Just (ns_me, vms) Nothing -> Nothing) meas_comps in map (\(ns_es, bound) -> let is = map (\(n, me) -> Id n (typeOf me)) ns_es str_call = evalMeasuresCE s b tcv is e bound in (map fst ns_es, e, s { curr_expr = CurrExpr Evaluate str_call }) ) rel_m -- Form all possible measure compositions, up to the maximal length formMeasureComps :: MaxMeasures -- ^ max length -> TypeEnv -> Type -- ^ Type of input value to the measures -> Measures -> [[(Name, Expr)]] formMeasureComps !mx tenv in_t meas = let meas' = E.toExprList $ E.filter (isTotal tenv) meas in formMeasureComps' mx in_t (map (:[]) meas') meas' formMeasureComps' :: MaxMeasures -- ^ max length -> Type -- ^ Type of input value to the measures -> [[(Name, Expr)]] -> [(Name, Expr)] -> [[(Name, Expr)]] formMeasureComps' !mx in_t existing ns_me | mx <= 1 = existing | otherwise = let r = [ ne1:ne2 | ne1@(_, e1) <- ns_me , ne2 <- existing , case (filter notLH $ anonArgumentTypes e1, fmap fst . chainReturnType in_t $ map snd ne2) of ([at], Just t) -> PresType t .:: at _ -> False ] in formMeasureComps' (mx - 1) in_t (r ++ existing) ns_me chainReturnType :: Type -> [Expr] -> Maybe (Type, [M.Map Name Type]) chainReturnType t ne = foldM (\(t', vms) et -> case filter notLH . anonArgumentTypes $ PresType et of [at] | Just vm <- t' `specializes` at -> Just (applyTypeMap vm . returnType $ PresType et, vm:vms) _ -> Nothing) (t, []) (map typeOf $ reverse ne) notLH :: Type -> Bool notLH ty | TyCon (Name n _ _ _) _ <- tyAppCenter ty = n /= "lh" | otherwise = True evalMeasuresCE :: State t -> Bindings -> TCValues -> [Id] -> Expr -> [M.Map Name Type] -> Expr evalMeasuresCE s bindings tcv is e bound = let meas_call = map (uncurry tyAppId) $ zip is bound ds = deepseq_walkers bindings call = foldr App e meas_call str_call = mkStrict_maybe ds call lh_dicts_call = maybe call (fillLHDictArgs ds) str_call in lh_dicts_call where tyAppId i b = let bound_names = map idName $ tyForAllBindings i bound_tys = map (\n -> case M.lookup n b of Just t -> t Nothing -> TyUnknown) bound_names lh_dcts = map (\t -> case lookupTCDict (type_classes s) (lhTC tcv) t of Just tc -> Var tc Nothing -> Prim Undefined TyBottom) bound_tys -- map (const $ Prim Undefined TyBottom) bound_tys in mkApp $ Var i:map Type bound_tys ++ lh_dcts ------------------------------- -- Generic ------------------------------- genericG2Call :: ( MonadIO m , ASTContainer t Expr , ASTContainer t Type , Named t , Solver solver) => Config -> solver -> State t -> Bindings -> m ([ExecRes t], Bindings) genericG2Call config solver s bindings = do let simplifier = IdSimplifier share = sharing config fslb <- runG2WithSomes (SomeReducer (stdRed share retReplaceSymbFuncVar solver simplifier)) (SomeHalter swhnfHalter) (SomeOrderer nextOrderer) solver simplifier PreserveAllMC s bindings return fslb genericG2CallLogging :: ( MonadIO m , ASTContainer t Expr , ASTContainer t Type , Named t , Show t , Solver solver) => Config -> solver -> State t -> Bindings -> String -> (SM.StateT PrettyGuide m) ([ExecRes t], Bindings) genericG2CallLogging config solver s bindings lg = do let simplifier = IdSimplifier share = sharing config fslb <- runG2WithSomes (SomeReducer (prettyLogger lg ~> stdRed share retReplaceSymbFuncVar solver simplifier)) (SomeHalter swhnfHalter) (SomeOrderer nextOrderer) solver simplifier PreserveAllMC s bindings return fslb