packages feed

g2-0.2.0.0: src/G2/Liquid/Inference/Verify.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}

module G2.Liquid.Inference.Verify ( VerifyResult (..)
                                  , verifyVarToName
                                  , tryToVerifyOnly
                                  , checkGSCorrect
                                  , verify
                                  , ghcInfos
                                  , defLHConfig
                                  , tryToVerify) where

import qualified G2.Language.Syntax as G2
import G2.Liquid.Helpers
import G2.Liquid.Types
import G2.Liquid.Inference.Config
import G2.Liquid.Inference.GeneratedSpecs

import Data.Maybe
import GHC
#if MIN_VERSION_liquidhaskell(0,8,10)
import Language.Haskell.Liquid.Types
        hiding (TargetInfo (..), TargetSrc (..), TargetSpec (..), GhcSrc (..), GhcSpec (..))
#else
import Language.Haskell.Liquid.Types
#endif
import Language.Haskell.Liquid.UX.CmdLine
import Text.PrettyPrint.HughesPJ

#if MIN_VERSION_GLASGOW_HASKELL(9,0,2,0)
import GHC.Core
import GHC.Types.Var as V
import GHC.Driver.Types

import Liquid.GHC.Interface
import Liquid.GHC.Misc
#else
import CoreSyn
import HscTypes (SourceError)
import Var as V

import Language.Haskell.Liquid.GHC.Interface
import           Language.Haskell.Liquid.GHC.Misc (showCBs, ignoreCoreBinds)
#endif

---------------------------------------------------------------------------
---------------------------------------------------------------------------
-- Copied from LiquidHaskell (because checkMany not exported)
import Control.Monad (when)
import Control.Monad.IO.Class 
import qualified Control.Exception as Ex
import Language.Haskell.Liquid.UX.Tidy
import Language.Haskell.Liquid.Constraint.Generate
import Language.Haskell.Liquid.Constraint.ToFixpoint
import Language.Haskell.Liquid.Constraint.Types
import Language.Haskell.Liquid.Misc
import Language.Fixpoint.Solver
import qualified Language.Fixpoint.Types as F
import qualified Language.Fixpoint.Types.Errors as F (FixResult (..))

#if MIN_VERSION_liquidhaskell(0,8,6)
import qualified Language.Haskell.Liquid.Termination.Structural as ST
import qualified Data.HashSet as S
#else
import           Language.Haskell.Liquid.GHC.Misc (showCBs)
#endif

-- For Show instance of Cinfo
import Language.Haskell.Liquid.Liquid ()

---------------------------------------------------------------------------
---------------------------------------------------------------------------

import qualified Language.Haskell.Liquid.UX.DiffCheck as DC

data VerifyResult v = Safe
                    | Crash [(Integer, Cinfo)] String
                    | Unsafe [v]
                    deriving Show

verifyVarToName :: VerifyResult V.Var -> VerifyResult G2.Name
verifyVarToName Safe = Safe
verifyVarToName (Crash ic s) = Crash ic s
verifyVarToName (Unsafe v) = Unsafe (map varToName v)

-- Tries to verify the assertions, specifically for the set of functions,
-- that we care about. If that fails, removes any synthesized
-- assertions/assumptions on the failing functions.
tryHardToVerifyIgnoring :: (InfConfigM m, MonadIO m)
                        => [GhcInfo]
                        -> GeneratedSpecs
                        -> [G2.Name]
                        -> m (Either [G2.Name] GeneratedSpecs)
tryHardToVerifyIgnoring ghci gs ignore = do
    lhconfig <- lhConfigM
    infconfig <- infConfigM
    liftIO $ do
        let merged_ghci = addSpecsToGhcInfos ghci gs

        putStrLn "---\nVerify"
        putStrLn "gsAsmSigs"
        mapM_ (print . getAssumedSigs) merged_ghci
        putStrLn "gsTySigs"
        mapM_ (print . getTySigs) merged_ghci
        putStrLn "---\nEnd Verify"

        res <- return . verifyVarToName =<< verify infconfig lhconfig merged_ghci
        putStrLn "res"
        case res of
            Unsafe ns
              | f_ns <- filterIgnoring ns
              , f_ns /= [] -> do
                putStrLn $ "filtered = " ++ show ns
                let f_gs = filterOutAssertSpecs f_ns gs
                    f_merged_ghci = addSpecsToGhcInfos ghci f_gs

                filtered_res <- return . verifyVarToName =<<
                                            verify infconfig lhconfig f_merged_ghci
                case filtered_res of
                    Unsafe _ -> return $ Left f_ns
                    Safe -> do
                          liftIO . putStrLn $ "Safe 2 after " ++ show ns 
                          return $ Right f_gs
                    Crash ci err -> error $ "Crash\n" ++ show ci ++ "\n" ++ err
              | otherwise -> do
                putStrLn $ "safe ignoring ns = " ++ show ns
                return $ Right gs
            Safe -> do
                liftIO $ putStrLn "Safe 1"
                return $ Right gs
            Crash ci err -> error $ "Crash\n" ++ show ci ++ "\n" ++ err
        where
            ignore' = map (\(G2.Name n m _ _) -> (n, m)) ignore
            filterIgnoring = filter (\(G2.Name n m _ _) -> (n, m) `notElem` ignore')

tryToVerifyOnly :: (InfConfigM m, MonadIO m) => [GhcInfo] -> [G2.Name] -> m (VerifyResult G2.Name)
tryToVerifyOnly ghci ns = do
    res <- tryToVerify ghci
    case res of
        Safe -> return Safe
        Unsafe unsafe ->
            case filter (\n -> toOccMod n `elem` ns_nm) unsafe of
                [] -> return Safe
                unsafe' -> do
                  return $ Unsafe unsafe'
        x -> error (show x)
    where
        ns_nm = map toOccMod ns
        toOccMod (G2.Name n m _ _) = (n, m)

tryToVerify :: (InfConfigM m, MonadIO m) => [GhcInfo] -> m (VerifyResult G2.Name)
tryToVerify ghci = do
    lhconfig <- lhConfigM
    infconfig <- infConfigM

    liftIO $ do
      putStrLn "-------------------------------"
      putStrLn "-------------------------------"
      putStrLn "tryToVerify"
      mapM (print . getTySigs) ghci
      putStrLn "-------------------------------"
      putStrLn "-------------------------------"

    return . verifyVarToName =<< liftIO (verify infconfig lhconfig ghci)

-- | Confirm that we have actually found exactly the needed specs
checkGSCorrect :: InferenceConfig -> Config -> [GhcInfo] -> GeneratedSpecs -> IO (VerifyResult V.Var)
checkGSCorrect infconfig lhconfig ghci gs
    | nullAssumeGS gs = do
        let merged_ghci = addSpecsToGhcInfos ghci $ switchAssumesToAsserts gs
        verify infconfig lhconfig merged_ghci
    | otherwise = error "Non-null assumes."

verify :: InferenceConfig -> Config ->  [GhcInfo] -> IO (VerifyResult V.Var)
verify infconfig cfg ghci = do
    r <- verify' infconfig cfg ghci
    case F.resStatus r of
#if MIN_VERSION_liquidhaskell(0,9,0)
        F.Safe _ -> return Safe
        F.Crash ci err -> return $ Crash (map fst ci) err
        F.Unsafe _ bad -> do
          putStrLn $ "bad var = " ++ show (map (ci_var . snd) bad)
          putStrLn $ "bad loc = " ++ show (map (ci_loc . snd) bad)
          return . Unsafe . catMaybes $ map (ci_var . snd) bad
#elif MIN_VERSION_liquidhaskell(0,8,10)
        F.Safe _ -> return Safe
        F.Crash ci err -> return $ Crash ci err
        F.Unsafe _ bad -> do
          putStrLn $ "bad var = " ++ show (map (ci_var . snd) bad)
          putStrLn $ "bad loc = " ++ show (map (ci_loc . snd) bad)
          return . Unsafe . catMaybes $ map (ci_var . snd) bad
#else
        F.Safe -> return Safe
        F.Crash ci err -> return $ Crash ci err
        F.Unsafe bad -> do
          putStrLn $ "bad var = " ++ show (map (ci_var . snd) bad)
          putStrLn $ "bad loc = " ++ show (map (ci_loc . snd) bad)
          return . Unsafe . catMaybes $ map (ci_var . snd) bad
#endif


verify' :: InferenceConfig -> Config ->  [GhcInfo] -> IO (F.Result (Integer, Cinfo))
verify' infconfig cfg ghci = checkMany infconfig cfg mempty ghci

ghcInfos :: Maybe HscEnv -> Config -> [FilePath] -> IO [GhcInfo]
ghcInfos me cfg fp = do
#if MIN_VERSION_liquidhaskell(0,8,10)
    (ghci, _) <- getTargetInfos me cfg fp
#else
    (ghci, _) <- getGhcInfos me cfg fp
#endif
    return ghci

defLHConfig :: [FilePath] -> IO Config
defLHConfig  proj = do
    config <- getOpts []
    return config { idirs = idirs config ++ proj
                  , files = files config
                  , ghcOptions = ["-v"]}

---------------------------------------------------------------------------
---------------------------------------------------------------------------
-- Copied from LiquidHaskell (because checkMany not exported)
checkMany :: InferenceConfig -> Config -> F.Result (Integer, Cinfo) -> [GhcInfo] -> IO (F.Result (Integer, Cinfo))
--------------------------------------------------------------------------------
checkMany infconfig cfg d (g:gs) = do
  d' <- checkOne infconfig cfg g
  checkMany infconfig cfg (d `mappend` d') gs

checkMany _ _   d [] =
  return d

--------------------------------------------------------------------------------
checkOne :: InferenceConfig -> Config -> GhcInfo -> IO (F.Result (Integer, Cinfo))
--------------------------------------------------------------------------------
checkOne infconfig cfg g = do
  z <- actOrDie $ liquidOne infconfig g
  case z of
    Left  e -> undefined
    Right r -> return r


actOrDie :: IO a -> IO (Either ErrorResult a)
actOrDie act =
    (Right <$> act)
      `Ex.catch` (\(e :: SourceError) -> handle e)
      `Ex.catch` (\(e :: Error)       -> handle e)
      `Ex.catch` (\(e :: UserError)   -> handle e)
      `Ex.catch` (\(e :: [Error])     -> handle e)

handle :: (Result a) => a -> IO (Either ErrorResult b)
handle = return . Left . result

--------------------------------------------------------------------------------
liquidOne :: InferenceConfig -> GhcInfo -> IO (F.Result (Integer, Cinfo))
--------------------------------------------------------------------------------
liquidOne infconfig info = do
  -- whenNormal $ donePhase Loud "Extracted Core using GHC"
  let cfg   = getConfig info
#if MIN_VERSION_liquidhaskell(0,8,6)
  let tgt   = giTarget (giSrc info)
  let cbs' = giCbs (giSrc info)
#else
  let tgt   = target info
  -- whenLoud  $ do putStrLn $ showpp info
                 -- putStrLn "*************** Original CoreBinds ***************************"
                 -- putStrLn $ render $ pprintCBs (cbs info)
  let cbs' = cbs info -- scopeTr (cbs info)
#endif
  -- whenNormal $ donePhase Loud "Transformed Core"
  -- whenLoud  $ do donePhase Loud "transformRecExpr"
  --                putStrLn "*************** Transform Rec Expr CoreBinds *****************"
  --                putStrLn $ showCBs (untidyCore cfg) cbs'
                 -- putStrLn $ render $ pprintCBs cbs'
                 -- putStrLn $ showPpr cbs'
  edcs <- newPrune      cfg cbs' tgt info
  liquidQueries infconfig cfg      tgt info edcs

#if MIN_VERSION_liquidhaskell(0,8,6)
newPrune :: Config -> [CoreBind] -> FilePath -> GhcInfo -> IO (Either [CoreBind] [DC.DiffCheck])
newPrune cfg cbs tgt info
  | not (null vs) = return . Right $ [DC.thin cbs sp vs]
  | timeBinds cfg = return . Right $ [DC.thin cbs sp [v] | v <- exportedVars (giSrc info) ]
  | diffcheck cfg = maybeEither cbs <$> DC.slice tgt cbs sp
  | otherwise     = return $ Left (ignoreCoreBinds ignores cbs)
  where
    ignores       = gsIgnoreVars (gsVars sp)
    vs            = gsTgtVars    (gsVars sp)
    sp            = giSpec       info

exportedVars :: GhcSrc -> [V.Var]
exportedVars src = filter (isExportedVar src) (giDefVars src)
#else
newPrune :: Config -> [CoreBind] -> FilePath -> GhcInfo -> IO (Either [CoreBind] [DC.DiffCheck])
newPrune cfg cbs tgt info
  | not (null vs) = return . Right $ [DC.thin cbs sp vs]
  | timeBinds cfg = return . Right $ [DC.thin cbs sp [v] | v <- exportedVars info ]
  | diffcheck cfg = maybeEither cbs <$> DC.slice tgt cbs sp
  | otherwise     = return  (Left cbs)
  where
    vs            = gsTgtVars sp
    sp            = spec    info

ignoreCoreBinds :: [V.Var] -> [CoreBind] -> [CoreBind]
ignoreCoreBinds vs cbs 
  | null vs         = cbs 
  | otherwise       = concatMap go cbs
  where
    go :: CoreBind -> [CoreBind]
    go b@(NonRec x _) 
      | x `elem` vs = [] 
      | otherwise   = [b] 
    go (Rec xes)    = [Rec (filter ((`notElem` vs) . fst) xes)]
#endif

-- topLevelBinders :: GhcSpec -> [Var]
-- topLevelBinders = map fst . tySigs

maybeEither :: a -> Maybe b -> Either a [b]
maybeEither d Nothing  = Left d
maybeEither _ (Just x) = Right [x]

liquidQueries :: InferenceConfig -> Config -> FilePath -> GhcInfo -> Either [CoreBind] [DC.DiffCheck] -> IO (F.Result (Integer, Cinfo))
liquidQueries infconfig cfg tgt info (Left cbs')
  = liquidQuery infconfig cfg tgt info (Left cbs')
liquidQueries infconfig cfg tgt info (Right dcs)
  = mconcat <$> mapM (liquidQuery infconfig cfg tgt info . Right) dcs

liquidQuery   :: InferenceConfig -> Config -> FilePath -> GhcInfo -> Either [CoreBind] DC.DiffCheck -> IO (F.Result (Integer, Cinfo))
#if MIN_VERSION_liquidhaskell(0,8,6)
liquidQuery infconfig cfg tgt info edc = do
  let names   = either (const Nothing) (Just . map show . DC.checkedVars)   edc
  let oldOut  = either (const mempty)  DC.oldOutput                         edc
  let info1   = either (const info)    (\z -> info {giSpec = DC.newSpec z}) edc
  let cbs''   = either id              DC.newBinds                          edc
  let info2   = info1 { giSrc = (giSrc info1) {giCbs = cbs''}}
  let info3   = updGhcInfoTermVars info2 
  let cgi     = {-# SCC "generateConstraints" #-} generateConstraints $! info3 
  when False (dumpCs cgi)
  -- whenLoud $ mapM_ putStrLn [ "****************** CGInfo ********************"
                            -- , render (pprint cgi)                            ]
  timedAction names $ solveCs infconfig cfg tgt cgi info3 names

updGhcInfoTermVars    :: GhcInfo -> GhcInfo 
updGhcInfoTermVars i  = updInfo i  (ST.terminationVars i) 
  where 
    updInfo   info vs = info { giSpec = updSpec   (giSpec info) vs }
    updSpec   sp   vs = sp   { gsTerm = updSpTerm (gsTerm sp)   vs }
    updSpTerm gsT  vs = gsT  { gsNonStTerm = S.fromList vs         } 
#else
liquidQuery infconfig cfg tgt info edc = do
  when False (dumpCs cgi)
  timedAction names $ solveCs infconfig cfg tgt cgi info' names
  where
    cgi    = {-# SCC "generateConstraints" #-} generateConstraints $! info' {cbs = cbs''}
    cbs''  = either id              DC.newBinds                        edc
    info'  = either (const info)    (\z -> info {spec = DC.newSpec z}) edc
    names  = either (const Nothing) (Just . map show . DC.checkedVars) edc
    oldOut = either (const mempty)  DC.oldOutput                       edc
#endif


dumpCs :: CGInfo -> IO ()
dumpCs cgi = do
  putStrLn "***************************** SubCs *******************************"
  putStrLn $ render $ pprintMany (hsCs cgi)
  putStrLn "***************************** FixCs *******************************"
  putStrLn $ render $ pprintMany (fixCs cgi)
  putStrLn "***************************** WfCs ********************************"
  putStrLn $ render $ pprintMany (hsWfs cgi)

pprintMany :: (PPrint a) => [a] -> Doc
pprintMany xs = vcat [ F.pprint x $+$ text " " | x <- xs ]

-- instance Show Cinfo where
--   show = show . F.toFix

solveCs :: InferenceConfig -> Config -> FilePath -> CGInfo -> GhcInfo -> Maybe [String] -> IO (F.Result (Integer, Cinfo))
solveCs infconfig cfg tgt cgi info names = do
  finfo            <- cgInfoFInfo info cgi
  -- We only want qualifiers we have found with G2 Inference, so we have to force the correct set here
  let finfo' = finfo { F.quals = (getQualifiers $ info) ++ if keep_quals infconfig then F.quals finfo else [] }
#if MIN_VERSION_liquid_fixpoint(0,9,0)
  fres@(F.Result r sol _ _) <- solve (fixConfig tgt cfg) finfo'
#else
  fres@(F.Result r sol _) <- solve (fixConfig tgt cfg) finfo'
#endif
  -- let resErr        = applySolution sol . cinfoError . snd <$> r
  -- resModel_        <- fmap (e2u cfg sol) <$> getModels info cfg resErr
  -- let resModel      = resModel_  `addErrors` (e2u cfg sol <$> logErrors cgi)
  -- let out0          = mkOutput cfg resModel sol (annotMap cgi)
  --     out1          = out0 { o_vars    = names    }
  --                          { o_result  = resModel }
  -- DC.saveResult       tgt  out1
  -- exitWithResult cfg [tgt] out1

  return fres


-- e2u :: Config -> F.FixSolution -> Error -> UserError
-- e2u cfg s = fmap F.pprint . tidyError cfg s