packages feed

g2-0.2.0.0: src/G2/Liquid/AddCFBranch.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}

module G2.Liquid.AddCFBranch ( CounterfactualName
                             , addCounterfactualBranch
                             , onlyCounterfactual
                             , elimNonTop

                             , instFuncTickName
                             , existentialInstId ) where

import G2.Language
import qualified G2.Language.ExprEnv as E
import G2.Language.Monad
import G2.Liquid.Config
import G2.Liquid.Types
import G2.Liquid.TyVarBags

import qualified Data.HashSet as S
import Data.List

type CounterfactualName = Name

-- Enables finding abstract counterexamples, by adding counterfactual branches
-- with two states.
-- (a) One state is exactly the same as the current reduce function: we lookup
--     up the var, and set the curr expr to its definition.
-- (b) [StateB] The other branch introduces a new symbolic variable x_s,
--     with the same type of s, and sets the curr expr to
--     let x = x_s in Assert (a x'_1 ... x'_n x_s) x_s
--     appropriately binding x'_i to x_i in the expression environment
--
--     This allows us to choose any value for the return type of the function.
--     In this rule, we also return a b, oldb `mappend` [(f, [x_1, ..., x_n], x)]
--     This is essentially abstracting away the function definition, leaving
--     only the information that LH also knows (that is, the information in the
--     refinment type.)
addCounterfactualBranch :: CFModules
                        -> [Name] -- ^ Which functions to consider abstracting
                        -> LHStateM CounterfactualName
addCounterfactualBranch cf_mod ns = do
    let ns' = case cf_mod of
                CFAll -> ns
                CFOnly mods -> filter (\(Name n m _ _) -> (n, m) `S.member` mods) ns

    bag_func_ns <- return . concat =<< mapM argumentNames ns'
    inst_func_ns <- return . concat =<< mapM returnNames ns'

    createBagAndInstFuncs bag_func_ns inst_func_ns

    cfn <- freshSeededStringN "cf"
    mapWithKeyME (addCounterfactualBranch' cfn ns')
    return cfn

addCounterfactualBranch' :: CounterfactualName -> [Name] -> Name -> Expr -> LHStateM Expr
addCounterfactualBranch' cfn ns n =
    if n `elem` ns then insertInLamsE (\_ -> addCounterfactualBranch'' cfn) else return

addCounterfactualBranch'' :: CounterfactualName -> Expr -> LHStateM Expr
addCounterfactualBranch'' cfn
    orig_e@(Let 
            [(b, _)]
            (Assert (Just (FuncCall { funcName = fn, arguments = ars, returns = r })) a _)) = do
        sg <- cfRetValue ars rt

        -- If the type of b is not the same as e's type, we have no assumption,
        -- so we get a new b.  Otherwise, we just keep our current b,
        -- in case it is used in the assertion
        b' <- if typeOf b == rt then return b else freshIdN rt

        let fc = FuncCall { funcName = fn, arguments = ars, returns = (Var b')}
            e' = Let [(b', sg)] $ Tick (NamedLoc cfn) $ Assume (Just fc) a (Var b')

        return $ NonDet [orig_e, e']
        where
            rt = typeOf r
addCounterfactualBranch'' cfn e = modifyChildrenM (addCounterfactualBranch'' cfn) e

cfRetValue :: [Expr] -- ^ Arguments
           -> Type -- ^ Type of return value
           -> LHStateM Expr
cfRetValue ars rt
    | tvs <- tyVarIds rt
    , not (null tvs)  = do
        let all_tvs = tvs ++ tyVarIds ars
        ty_bags <- getTyVarBags

        ex_vrs <- freshIdsN (map TyVar all_tvs)
        let ex_tvs_to_vrs = zip all_tvs ex_vrs

        ex_ty_clls <- mapM 
                        (\tv -> wrapExtractCalls
                              . filter nullNonDet
                              . concat
                              =<< mapM (extractTyVarCall ty_bags ex_tvs_to_vrs tv) ars) (nub all_tvs)

        let ex_let_bnds = zip ex_vrs ex_ty_clls

        dUnit <- mkUnitE

        insts_f <- getInstFuncs
        inst_ret <- instTyVarCall insts_f ex_tvs_to_vrs rt
        let inst_ret_call = App inst_ret dUnit
        ir_bndr <- freshIdN (typeOf inst_ret_call)
        
        return . Let ((ir_bndr, inst_ret_call):ex_let_bnds) $ Tick (NamedLoc instFuncTickName) (Var ir_bndr)
    | otherwise = do 
        return (SymGen SNoLog rt)

nullNonDet :: Expr -> Bool
nullNonDet (NonDet []) = False
nullNonDet _ = True

instFuncTickName :: Name
instFuncTickName = Name "INST_FUNC_TICK" Nothing 0 Nothing

argumentNames :: ExState s m => Name -> m [Name]
argumentNames n = do
    e <- lookupE n
    case e of
        Just e' -> return . concatMap tyConNames $ anonArgumentTypes e'
        Nothing -> return []

returnNames :: ExState s m => Name -> m [Name]
returnNames n = do
    e <- lookupE n
    case e of
        Just e' -> return . tyConNames $ returnType e'
        Nothing -> return []

tyConNames :: Type -> [Name]
tyConNames (TyCon n t) = n:tyConNames t 
tyConNames t = evalChildren tyConNames t

-- | Eliminates the real branch of the non-deterministic choices, leaving only
-- the abstract branch.  Assumes all non-determinisitic choices are for
-- counterfactual symbolic execution.
onlyCounterfactual :: ASTContainer m Expr => m -> m
onlyCounterfactual = modifyASTs onlyCounterfactual'

onlyCounterfactual' :: Expr -> Expr
onlyCounterfactual' (NonDet [_, e]) = e
onlyCounterfactual' e = e

-- | Eliminate all Asserts, except for the functions with names in the HashSet
elimNonTop :: S.HashSet Name -> State t -> State t
elimNonTop hs s@(State { expr_env = eenv }) = s { expr_env = E.mapWithKey (elimNonTop' hs) eenv }

elimNonTop' :: S.HashSet Name -> Name -> Expr -> Expr
elimNonTop' hs n e = if n `S.member` hs then e else elimAsserts e