Skip to content
Snippets Groups Projects
API.hs 3.64 KiB
Newer Older
  • Learn to ignore specific revisions
  • module LogicIR.Backend.Z3.API
           ( equivalentTo
           , showZ3AST
           )
           where
    
    import qualified Z3.Base  as Z3
    import           Z3.Monad hiding (Model)
    
    import           Control.Exception.Base (tryJust)
    
    import           Control.Monad          (forM)
    
    import qualified Data.Map               as M
    import           Data.Maybe             (fromJust)
    import           Data.String
    
    import LogicIR.Backend.Z3.Z3
    
    import LogicIR.Expr          (LExpr, lnot, (.&&), (.<==>))
    
    import Model
    
    data Z3Response = Satisfiable Model | Unsatisfiable | Undecidable
                      deriving Show
    
    
    -- | Determine the equality of two method's pre/post conditions.
    
    equivalentTo :: LExpr -> LExpr -> IO Response
    
    equivalentTo l l' = do
      equiv <- checkZ3 $ lnot (l .<==> l')
      case equiv of
        Satisfiable m -> do
    
          res <- sequence $ checkZ3 <$>
            [l .&& l', l .&& lnot l', lnot l .&& l', lnot l .&& lnot l']
          let feedback = mkFeedback $ toBool <$> res
          return $ NotEquivalent m Feedback {pre = feedback, post = defFeedback}
    
        Unsatisfiable -> return Equivalent
        Undecidable -> return Undefined
    
      where
        mkFeedback :: [Bool] -> SingleFeedback
        mkFeedback [b1, b2, b3, b4] = (b1, b2, b3, b4)
        mkFeedback _                = defFeedback
        toBool :: Z3Response -> Bool
        toBool (Satisfiable _) = True
        toBool _               = False
    
    
    -- | Checks the validity of a logical statement.
    checkZ3 :: LExpr -> IO Z3Response
    checkZ3 l = catchTimeout $ tryZ3 $ do
      -- solverReset
      fv <- freeVars l
      ast <- lExprToZ3Ast l
      -- Get model
      (r, model) <- local $ assert ast >> solverCheckAndGetModel
      response <- case r of
        Sat   -> do
          -- Construct counter-model
          ms <- local (sanitize . M.fromList <$>
             forM (M.toList fv) (evalAST fv (fromJust model)))
          return $ Satisfiable ms
        Unsat -> return Unsatisfiable
        _ -> return Undecidable
      solverReset
      return response
      where
        -- Construct model values
        evalAST :: FreeVars -> Z3.Model -> (String, AST) -> Z3 (String, ModelVal)
        evalAST fv m (k, ast) = do
          v <- fromJust <$> modelEval m ast True
          sortKind <- getSort v >>= getSortKind
          if sortKind == Z3_ARRAY_SORT then do
            -- Retrieve array's length
            lenName <- mkStringSymbol (k ++ "?length") >>= mkIntVar
            f <- snd <$> evalAST fv m (k ++ "?length", lenName)
            let len = case f of
                  (IntVal i) -> i
                  _          -> error "non-int length"
            -- Iterate array "points"
            modelVals <- forM [0..(len-1)] (\i -> do
              indexAST <- mkInteger $ toInteger i
              pointAST <- mkSelect v indexAST
              snd <$> evalAST fv m ("", pointAST)
              )
            return (k, ManyVal modelVals)
          else do
            v' <- astToString v
            return (k, fromString v' :: ModelVal)
    
    
    -- | Run a Z3 action in 'IO', but catch timeouts.
    catchTimeout :: IO Z3Response -> IO Z3Response
    catchTimeout prog = do
      res <- tryJust errorSelector prog
      return $ case res of
        Left () -> Undecidable
        Right r -> r
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
        where
          errorSelector :: Z3Error -> Maybe ()
          errorSelector err =
            case errCode err of
              InvalidUsage -> Just ()
              _            -> Nothing
    
    -- | Z3 try evaluation with timeout.
    
    tryZ3 :: Z3 a -> IO a
    
    tryZ3 prog = do
      env <- newEnv Nothing (  opt "timeout" (5000 :: Integer)
                            +? opt "model_validate" True
                            +? opt "well_sorted_check" True
                            +? opt "auto_config" True
                            -- +? opt "unsat_core" True
                            )
      evalZ3WithEnv prog env
    
    Orestis Melkonian's avatar
    Orestis Melkonian committed
    
    -- | Pretty-print Z3 AST.
    showZ3AST :: Z3 AST -> IO String
    showZ3AST ast' = tryZ3 $ ast' >>= astToString