afv 0.0.4 → 0.1.0
raw patch · 6 files changed
+318/−234 lines, 6 files
Files
- RELEASE-NOTES +7/−0
- afv.cabal +1/−1
- src/AFV.hs +13/−9
- src/Compile.hs +160/−173
- src/Model.hs +38/−3
- src/Verify.hs +99/−48
RELEASE-NOTES view
@@ -1,3 +1,10 @@+afv 0.1.0 01/24/10++- Moved functon calls and assignments to statement position only.+- Added checks for static top-level declarations, which are not supported.+- Checks start from 'main' function. Automatically detects infinite loop.+- Disallows state modifying expressions.+ afv 0.0.4 01/21/10 - Moved branches into model.
afv.cabal view
@@ -1,5 +1,5 @@ name: afv-version: 0.0.4+version: 0.1.0 category: Formal Methods
src/AFV.hs view
@@ -8,7 +8,7 @@ import Parse import Verify -version = "0.0.4"+version = "0.1.0" main :: IO () main = do@@ -19,10 +19,10 @@ | elem a ["version", "-v", "--version"] -> putStrLn $ "afv " ++ version ["example"] -> example ["header"] -> header- ("verify":function:args) -> do+ ("verify":args) -> do let a = parseArgs Args { yices = "yices", gcc = "gcc", k = 20, defines = [("AFV", "")], includes = ["."], inputs = [] } args units <- sequence [ putStrLn ("parsing " ++ f ++ " ...") >> hFlush stdout >> parse (gcc a) (includes a) (defines a) f | f <- inputs a ]- model <- compile function units+ model <- compile units verify (yices a) (k a) model _ -> help @@ -70,7 +70,7 @@ example = do header putStrLn "writing example design (example.c) ..."- putStrLn "verify with: afv verify example -k 15 example.c"+ putStrLn "verify with: afv verify -k 15 example.c" writeFile "example.c" $ unlines [ "// Provides assert and assume functions." , "#include \"afv.h\""@@ -114,6 +114,10 @@ , "" , "}" , ""+ , "void main() {"+ , " while (1) example();"+ , "}"+ , "" ] @@ -128,17 +132,17 @@ , " " ++ version , "" , "SYNOPSIS"- , " afv verify function-name [-k max-k] [--yices=path-to-yices] [--gcc=path-to-gcc] {-Idir} {-Dmacro[=def]} c-file {c-file}"+ , " afv verify [-k max-k] [--yices=path-to-yices] [--gcc=path-to-gcc] {-Idir} {-Dmacro[=def]} c-file {c-file}" , " afv header" , " afv example" , "" , "DESCRIPTION"- , " Afv performs bounded model checking and k-induction on a repetitively called C function."+ , " Afv performs bounded model checking and k-induction on C code with a signle infinite loop." , " Requires GCC for C preprocessing and the Yices SMT solver." , "" , "COMMANDS"- , " verify function-name [options] c-files ..."- , " Runs verification on the specified periodic function."+ , " verify [options] c-files ..."+ , " Runs verification." , "" , " -k n : Max k-induction depth." , " --yices=path : Path to Yices solver."@@ -152,6 +156,6 @@ , "" , " example" , " Writes an example design (example.c). Verify with:"- , " afv verify example -k 15 example.c"+ , " afv verify -k 15 example.c" , "" ]
src/Compile.hs view
@@ -11,31 +11,35 @@ import Utils -- | Compiles a program to a model for analysis.-compile :: String -> [CTranslUnit] -> IO Model-compile function units = do- m <- execStateT (evalStat initEnv $ rewrite function units) initMDB- return (model m) { actions = reverse $ actions (model m) }+compile :: [CTranslUnit] -> IO Model+compile units = do+ m <- execStateT (evalStat initEnv $ rewrite units) initMDB+ return (model m) { initActions = reverse $ initActions (model m), loopActions = reverse $ loopActions (model m) } none :: NodeInfo none = internalNode -- | Rewrites a program to a single statement. Requires no recursive functions and unique declarations for all top level declarations.-rewrite :: String -> [CTranslUnit] -> CStat-rewrite name units = if not $ null asms+rewrite :: [CTranslUnit] -> CStat+rewrite units = if not $ null asms then notSupported (head asms) "inline assembly" else if not $ null duplicateNames- then error $ "duplicate top-level names found (hiding names with static is not supported): " ++ show duplicateNames --XXX Should not allow top level static decls. An improper name reference in one file could reference a static variable in another.- else CCompound [] (vars ++ funcs ++ [CBlockStmt call]) none+ then error $ "duplicate top-level names found: " ++ intercalate ", " duplicateNames+ else if not $ null staticFuncDefs+ then notSupported (head staticFuncDefs) "top-level static declarations"+ else if not $ null staticVarDefs+ then notSupported (head staticVarDefs) "top-level static declarations"+ else CCompound [] (varDefs ++ funcDefs ++ [CBlockStmt call]) none where items = [ a | CTranslUnit items _ <- units, a <- items ]- varDefs = [ a | CDeclExt a <- items ]- vars = [ CBlockDecl (CDecl (CStorageSpec (CStatic none) : specs) a b) | CDecl specs a b <- varDefs ] -- Make top level vars static.- funcDefs = [ a | CFDefExt a <- items ]- funcs = map CNestedFunDef $ sortFunctions funcDefs+ varDefs = [ CBlockDecl a | CDeclExt a@(CDecl specs _ _) <- items, not $ isExtern $ fst $ typeInfo specs ]+ funcDefs = map CNestedFunDef $ sortFunctions [ a | CFDefExt a@(CFunDef specs _ _ _ _) <- items, not $ isExtern $ fst $ typeInfo specs ] asms = [ a | CAsmExt a <- items ] call :: CStat- call = CExpr (Just $ CCall (CVar (Ident name 0 none) none) [] none) none- duplicateNames = duplicates $ [ name | f <- funcDefs, let (_, (Ident name _ _), _, _) = functionInfo f ] ++ concat [ [ name | (Just (CDeclr (Just (Ident name _ _)) _ _ _ _), _, _) <- a ] | CDecl _ a _ <- varDefs ]+ call = CExpr (Just $ CCall (CVar (Ident "main" 0 none) none) [] none) none+ duplicateNames = duplicates $ [ name | CNestedFunDef f <- funcDefs, let (_, (Ident name _ _), _, _) = functionInfo f ] ++ concat [ [ name | (Just (CDeclr (Just (Ident name _ _)) _ _ _ _), _, _) <- a ] | CBlockDecl (CDecl _ a _) <- varDefs ]+ staticFuncDefs = [ a | CFDefExt a@(CFunDef specs _ _ _ _) <- items, isStatic $ fst $ typeInfo specs ]+ staticVarDefs = [ a | CDeclExt a@(CDecl specs _ _) <- items, isStatic $ fst $ typeInfo specs ] duplicates :: Eq a => [a] -> [a] duplicates [] = []@@ -46,15 +50,19 @@ data MDB = MDB { nextId' :: Int+ , stage :: Stage , stack :: [Ident] , model :: Model } +data Stage = Init | Loop | Done deriving Eq+ initMDB :: MDB initMDB = MDB { nextId' = 0+ , stage = Init , stack = []- , model = Model { variables = [], actions = [] }+ , model = Model { initActions = [], loopActions = [] } } nextId :: M Int@@ -63,43 +71,59 @@ put m { nextId' = nextId' m + 1 } return $ nextId' m +setStage :: Stage -> M ()+setStage a = do+ m <- get+ put m { stage = a } +getStage :: M Stage+getStage = do+ m <- get+ return $ stage m+ -- | Environment for resolving identifiers.-data Env = Env- { values :: [Value]- }+type Env = [Value] -data Value- = EnvFunction String Function- | EnvVariable String V+data Value = EnvFunction String Function | EnvVariable String V -data Function = Function Int ([E] -> M E)+data Function = Function Int ([E] -> M ()) -- | Looks of a variable in the environment. variable :: Env -> Ident -> V variable env i@(Ident name _ _) = if null m then err i $ "variable \"" ++ name ++ "\" not found" else head m where- m = [ a | EnvVariable n a <- values env, n == name ]+ m = [ a | EnvVariable n a <- env, n == name ] -- | Looks of a function in the environment. function :: Env -> Ident -> Function function env i@(Ident name _ _) = if null m then err i $ "function \"" ++ name ++ "\" not found" else head m where- m = [ a | EnvFunction n a <- values env, n == name ]+ m = [ a | EnvFunction n a <- env, n == name ] -- | Creates a branch.-branch :: Position -> V -> M a -> M a -> M (a, a)-branch n a onTrue onFalse = do+branch :: Position -> V -> M () -> M () -> M ()+branch p a onTrue onFalse = do --XXX What happens if infinite loop is called within branch? m1 <- get- put m1 { model = (model m1) { actions = [] } }- r1 <- onTrue+ case stage m1 of+ Init -> put m1 { model = (model m1) { initActions = [] } }+ Loop -> put m1 { model = (model m1) { loopActions = [] } }+ Done -> unexpected p "statements after infinite loop"+ onTrue m2 <- get- put m1 { model = (model m2) { actions = [] } }- r2 <- onFalse+ case stage m2 of+ Init | stage m1 == Init -> put m1 { model = (model m2) { initActions = [] } }+ Loop | stage m1 == Loop -> put m1 { model = (model m2) { loopActions = [] } }+ _ -> unexpected p "infinite loop in branch"+ onFalse m3 <- get- put m3 { model = (model m3) { actions = actions $ model m1 } }- newAction $ Branch a (reverse $ actions $ model m2) (reverse $ actions $ model m3) n- return (r1, r2)+ case stage m3 of+ Init | stage m1 == Init && stage m2 == Init -> do+ put m3 { model = (model m3) { initActions = initActions $ model m1 } }+ newAction $ Branch a (reverse $ initActions $ model m2) (reverse $ initActions $ model m3) p+ Loop | stage m1 == Loop && stage m2 == Loop -> do+ put m3 { model = (model m3) { loopActions = loopActions $ model m1 } }+ newAction $ Branch a (reverse $ loopActions $ model m2) (reverse $ loopActions $ model m3) p+ _ -> unexpected p "infinite loop in branch" -- | Push an identifier onto the call stack, do something, then pop it off. callStack :: Ident -> M a -> M a@@ -119,41 +143,31 @@ -- | The initial environment defines the assert and assume functions. initEnv :: Env-initEnv = Env- { values =- [ EnvFunction "assert" $ Function 1 assert- , EnvFunction "assume" $ Function 1 assume- ]- }+initEnv = [EnvFunction "assert" $ Function 1 assert, EnvFunction "assume" $ Function 1 assume] where assert a' = do let a = head a' s <- callPath- x <- latch (posOf a) a+ x <- latchBool (posOf a) a newAction $ Assert x s (posOf a)- return $ Var x assume a' = do let a = head a' s <- callPath- x <- latch (posOf a) a+ x <- latchBool (posOf a) a newAction $ Assume x s (posOf a)- return $ Var x -- | Adds new action. newAction :: Action -> M () newAction a = do m <- get- put m { model = (model m) { actions = a : actions (model m) }}+ case stage m of+ Init -> put m { model = (model m) { initActions = a : initActions (model m) }}+ Loop -> put m { model = (model m) { loopActions = a : loopActions (model m) }}+ Done -> error "Compile.newAction" -- | Adds new variable. addVar :: Env -> V -> M Env-addVar env a = do- case a of- State a -> do- m <- get- put m { model = (model m) { variables = if elem a (variables $ model m) then variables $ model m else a : variables (model m) }}- _ -> return ()- return env { values = EnvVariable name a : values env }+addVar env a = return $ EnvVariable name a : env where name = case a of State (VS a _ _ _) -> a@@ -164,132 +178,109 @@ evalStat :: Env -> CStat -> M ()-evalStat env a = case a of- CLabel i a [] _ -> callStack i $ evalStat env a- CExpr Nothing _ -> return ()- CExpr (Just a) _ -> evalExpr env a >> return ()- CCompound ids items _ -> f ids- where- f :: [Ident] -> M ()- f [] = foldM evalBlockItem env items >> return ()- f (a:b) = callStack a $ f b- CIf a b Nothing n -> evalStat env $ CIf a b (Just $ CCompound [] [] n) n- CIf a b (Just c) n -> do- a <- evalExpr env a >>= latch (posOf n)- branch (posOf n) a (evalStat env b) (evalStat env c)- return ()- _ -> notSupported a "statement"+evalStat env a = do+ stage <- getStage+ when (stage == Done) $ unexpected a "statements after infinite loop"+ case a of+ CFor (Left Nothing) Nothing Nothing a _ | stage == Init -> do setStage Loop >> evalStat env a >> setStage Done+ CWhile (CConst (CIntConst (CInteger 1 _ _) _)) a _ _ | stage == Init -> do setStage Loop >> evalStat env a >> setStage Done+ CLabel i a [] _ -> callStack i $ evalStat env a+ CCompound ids items _ -> f ids+ where+ f :: [Ident] -> M ()+ f [] = foldM evalBlockItem env items >> return ()+ f (a:b) = callStack a $ f b+ CIf a b Nothing n -> evalStat env $ CIf a b (Just $ CCompound [] [] n) n+ CIf a b (Just c) n -> do+ a <- latchBool (posOf n) $ evalExpr env a+ branch (posOf n) a (evalStat env b) (evalStat env c)+ return () + CExpr Nothing _ -> return ()+ CExpr (Just (CAssign op a b n)) _ -> case op of+ CAssignOp -> case evalExpr env a of+ Var v -> assign (posOf n) v $ evalExpr env b+ _ -> unexpected a "non variable in left hand of assignment"+ CMulAssOp -> f CMulOp+ CDivAssOp -> f CDivOp+ CRmdAssOp -> f CRmdOp+ CAddAssOp -> f CAddOp+ CSubAssOp -> f CSubOp+ CShlAssOp -> f CShlOp+ CShrAssOp -> f CShrOp+ CAndAssOp -> f CAndOp+ CXorAssOp -> f CXorOp+ COrAssOp -> f COrOp+ where+ f :: CBinaryOp -> M ()+ f op = evalStat env (CExpr (Just (CAssign CAssignOp a (CBinary op a b n) n)) n)++ CExpr (Just (CCall (CVar f _) args _)) _ -> do+ when (arity /= length args) $ unexpected f $ "function called with " ++ show (length args) ++ " arguments, but defined with " ++ show arity ++ " arguments"+ callStack f $ func $ map (evalExpr env) args+ where+ Function arity func = function env f++ CExpr (Just (CCall _ _ _)) _ -> notSupported a "non named function references"++ CExpr (Just (CUnary op a n1)) n2 | elem op [CPreIncOp, CPostIncOp] -> evalStat env (CExpr (Just (CAssign CAddAssOp a one n1)) n2)+ | elem op [CPreDecOp, CPostDecOp] -> evalStat env (CExpr (Just (CAssign CSubAssOp a one n1)) n2)+ where+ one = CConst $ CIntConst (cInteger 1) n1++ _ -> notSupported a "statement"+ evalBlockItem :: Env -> CBlockItem -> M Env evalBlockItem env a = case a of CBlockStmt a -> evalStat env a >> return env CBlockDecl a -> evalDecl env a CNestedFunDef a -> evalFunc env a -evalExpr :: Env -> CExpr -> M E+-- No stateful operations for expressions.+evalExpr :: Env -> CExpr -> E evalExpr env a = case a of- CAssign op a b n -> case op of- CAssignOp -> do- a' <- evalExpr env a- b' <- evalExpr env b- case a' of- Var v -> assign (posOf n) v b' >> return a'- _ -> unexpected a "non variable in left hand of assignment"- CMulAssOp -> f CMulOp- CDivAssOp -> f CDivOp- CRmdAssOp -> f CRmdOp- CAddAssOp -> f CAddOp- CSubAssOp -> f CSubOp- CShlAssOp -> f CShlOp- CShrAssOp -> f CShrOp- CAndAssOp -> f CAndOp- CXorAssOp -> f CXorOp- COrAssOp -> f COrOp- where- f :: CBinaryOp -> M E- f op = evalExpr env (CAssign CAssignOp a (CBinary op a b n) n)- CCond a (Just b) c n -> do- a <- evalExpr env a >>= latch (posOf a)- (b, c) <- branch (posOf n) a (evalExpr env b) (evalExpr env c)- return $ Mux (Var a) b c $ posOf n- CCond a Nothing b n -> do- a <- evalExpr env a >>= latch (posOf a)- (a, b) <- branch (posOf n) a (return $ Var a) (evalExpr env b)- return $ Mux a a b $ posOf n- CBinary op a b n -> case op of- CMulOp -> f Mul- CDivOp -> f Div- CRmdOp -> f Mod- CAddOp -> f Add- CSubOp -> f Sub+ CCond a (Just b) c n -> Mux (evalExpr env a) (evalExpr env b) (evalExpr env c) $ posOf n+ CCond a Nothing b n -> Or (evalExpr env a) (evalExpr env b) $ posOf n+ CBinary op a' b' n -> case op of+ CMulOp -> Mul a b p+ CDivOp -> Div a b p+ CRmdOp -> Mod a b p+ CAddOp -> Add a b p+ CSubOp -> Sub a b p CShlOp -> notSupported a "(<<)" CShrOp -> notSupported a "(>>)"- CLeOp -> f Lt- CGrOp -> f $ \ a b n -> Lt b a n- CLeqOp -> f $ \ a b n -> Not (Lt b a n) n- CGeqOp -> f $ \ a b n -> Not (Lt a b n) n- CEqOp -> f Eq- CNeqOp -> f $ \ a b n -> Not (Eq a b n) n+ CLeOp -> Lt a b p+ CGrOp -> Lt b a p+ CLeqOp -> Not (Lt b a p) p+ CGeqOp -> Not (Lt a b p) p+ CEqOp -> Eq a b p+ CNeqOp -> Not (Eq a b p) p CAndOp -> notSupported a "(&)" CXorOp -> notSupported a "(^)" COrOp -> notSupported a "(|)"- CLndOp -> do- a <- evalExpr env a >>= latch (posOf a)- (b, a) <- branch n' a (evalExpr env b) (return $ Var a)- return $ Mux a b a n'- CLorOp -> do- a <- evalExpr env a >>= latch (posOf a)- (a, b) <- branch n' a (return $ Var a) (evalExpr env b)- return $ Mux a a b n'+ CLndOp -> And a b p+ CLorOp -> Or a b p where- n' = posOf n- f :: (E -> E -> Position -> E) -> M E- f op = do- a <- evalExpr env a- b <- evalExpr env b- return $ op a b n'+ a = evalExpr env a'+ b = evalExpr env b'+ p = posOf n - CUnary op a n -> do- a <- evalExpr env a- case (op, a) of- (CPreIncOp, Var a) -> do- assign p a $ Add (Var a) one p- return $ Var a- (CPreDecOp, Var a) -> do- assign p a $ Sub (Var a) one p- return $ Var a- (CPostIncOp, Var a) -> do- b <- latch p $ Var a- assign p a $ Add (Var a) one p- return $ Var b- (CPostDecOp, Var a) -> do- b <- latch p $ Var a- assign p a $ Sub (Var a) one p- return $ Var b- (CPlusOp, a) -> return a- (CMinOp, a) -> return $ Sub zero a p- (CNegOp, a) -> return $ Not a p- --(CAdrOp, a) -> return $ Ref a p- --(CIndOp, a) -> return $ Deref a p- _ -> notSupported n "unary operator"+ CUnary op a' n -> case op of+ CPlusOp -> a+ CMinOp -> Sub zero a p+ CNegOp -> Not a p+ --(CAdrOp, a) -> return $ Ref a p+ --(CIndOp, a) -> return $ Deref a p+ _ -> notSupported n "unary operator" where+ a = evalExpr env a' p = posOf n- one = Const $ M.CInteger 1 p zero = Const $ M.CInteger 0 p - CCall (CVar f _) args _ -> do- args <- mapM (evalExpr env) args- when (arity /= length args) $ unexpected f $ "function called with " ++ show (length args) ++ " arguments, but defined with " ++ show arity ++ " arguments"- callStack f $ func args- where- Function arity func = function env f-- CCall _ _ _ -> notSupported a "non named function references"-- CVar i _ -> return $ Var $ variable env i+ CVar i _ -> Var $ variable env i CConst a -> case a of- CIntConst (CInteger a _ _) n -> return $ Const $ M.CInteger a $ posOf n- CFloatConst (CFloat a) n -> return $ Const $ CRational (toRational (read a :: Double)) $ posOf n+ CIntConst (CInteger a _ _) n -> Const $ M.CInteger a $ posOf n+ CFloatConst (CFloat a) n -> Const $ CRational (toRational (read a :: Double)) $ posOf n _ -> notSupported a "char or string constant" _ -> notSupported a "expression" @@ -324,15 +315,16 @@ (Just (CInitExpr c _), Nothing) -> evalDecl' env (a, Nothing, Just c) - (Nothing, Just e') -> do- e <- evalExpr env e'+ (Nothing, Just e) -> do v <- if isVolatile typInfo then return $ Volatile name typ $ posOf n else do i <- nextId- return $ Local name typ i (posOf e')- declare (posOf e') v e+ return $ Local name typ i p+ declare p v $ evalExpr env e addVar env v+ where+ p = posOf e _ -> notSupported i "variable declaration" _ -> notSupported d "arrays, pointers, or functional pointers (So what good is this tool anyway?)" @@ -340,7 +332,7 @@ evalFunc :: Env -> CFunDef -> M Env evalFunc env f = do when (typ /= Void) $ notSupported f "non void function"- return env { values = EnvFunction name (Function (length args) func) : values env }+ return $ EnvFunction name (Function (length args) func) : env where (specs, (Ident name _ _), args', stat) = functionInfo f (_, typ) = typeInfo specs@@ -348,7 +340,6 @@ func args' = do env <- foldM bindArg env $ zip args args' evalStat env stat- return false --XXX No support for 'return'. bindArg :: Env -> ((Ident, (TypeInfo, Type)), E) -> M Env bindArg env ((Ident name _ n, (typInfo, typ)), a) = if isVolatile typInfo@@ -382,17 +373,12 @@ _ -> newAction $ Assign v a n -- | Latch a value at a point in time.-latch :: Position -> E -> M V -- XXX When is it appropriate to latch and when not?---latch _ (Var a) = return a-latch n a = do+latchBool :: Position -> E -> M V+latchBool n a = do i <- nextId- let v = Tmp (typeOf a) i n- case typeOf a of- Integer Nothing -> error "Compile.latch1"- Rational Nothing -> error "Compile.latch2"- _ -> do- declare n v a- return v+ let v = Tmp Bool i n+ declare n v a+ return v @@ -445,6 +431,7 @@ CIf a b (Just c) _ -> fe env a ++ fs env b ++ fs env c CSwitch a b _ -> fe env a ++ fs env b CWhile a b _ _ -> fe env a ++ fs env b+ CFor (Left Nothing) Nothing Nothing a _ -> fs env a CFor _ _ _ _ _ -> notSupported a "for loops" CGoto _ _ -> [] CGotoPtr a _ -> fe env a
src/Model.hs view
@@ -15,8 +15,11 @@ , isInteger , typeCheckModel , unify+ , eVars+ , variables ) where +import Data.List import Language.C.Data.Position import Error@@ -188,7 +191,7 @@ (a, b) -> err n $ "type violation: " ++ show a ++ " incompatiable with " ++ show b typeCheckModel :: Model -> Model-typeCheckModel m = m { actions = map typeCheckAction $ actions m }+typeCheckModel m = m { initActions = map typeCheckAction $ initActions m, loopActions = map typeCheckAction $ loopActions m } typeCheckAction :: Action -> Action typeCheckAction a = case a of@@ -204,6 +207,24 @@ false :: E false = Const $ CBool False nopos +eVars :: E -> [V]+eVars a = case a of+ Var a -> [a]+ Const _ -> []+ Not a _ -> eVars a+ And a b _ -> eVars a ++ eVars b+ Or a b _ -> eVars a ++ eVars b+ Mul a b _ -> eVars a ++ eVars b+ Div a b _ -> eVars a ++ eVars b+ Mod a b _ -> eVars a ++ eVars b+ Add a b _ -> eVars a ++ eVars b+ Sub a b _ -> eVars a ++ eVars b+ Lt a b _ -> eVars a ++ eVars b+ Eq a b _ -> eVars a ++ eVars b+ Mux a b c _ -> eVars a ++ eVars b ++ eVars c+ Ref a _ -> eVars a+ Deref a _ -> eVars a+ instance Pos Position where posOf = id data Action@@ -222,8 +243,22 @@ Assume _ _ a -> a Branch _ _ _ a -> a +variables :: Model -> [VS]+variables m = nub $ concatMap vars $ initActions m ++ loopActions m+ where+ vars :: Action -> [VS]+ vars a = case a of+ Declare v e _ -> var v ++ [ a | State a <- eVars e ]+ Assign v e _ -> var v ++ [ a | State a <- eVars e ]+ Assert v _ _ -> var v+ Assume v _ _ -> var v+ Branch v a b _ -> var v ++ concatMap vars a ++ concatMap vars b+ var :: V -> [VS]+ var (State a) = [a]+ var _ = []+ data Model = Model- { variables :: [VS]- , actions :: [Action]+ { initActions :: [Action]+ , loopActions :: [Action] } deriving Show
src/Verify.hs view
@@ -13,12 +13,13 @@ -- | Verify a model with k-induction. verify :: FilePath -> Int -> Model -> IO () verify _ maxK _ | maxK < 1 = error "max k can not be less than 1"-verify yices maxK m = do- mapM_ (verifyModel yices format maxK) models+verify yices maxK m' = mapM_ (verifyModel yices format maxK) models where- model = typeCheckModel m- models = [ (name, trimModel model { actions = actions }) | (name, actions) <- trimAssertions (actions model) ]- format = "verifying %-" ++ show (maximum [ length n | (n, _) <- models ]) ++ "s "+ m = typeCheckModel m'+ initPlain = removeAssertions $ initActions m+ models = [ (name, m { initActions = actions, loopActions = [] }) | (name, actions) <- trimAssertions $ initActions m ]+ ++ [ (name, m { initActions = initPlain, loopActions = actions }) | (name, actions) <- trimAssertions $ loopActions m ]+ format = "verifying %-" ++ show (maximum $ map length $ fst $ unzip models) ++ "s " -- | Remove all assertions except one. trimAssertions :: [Action] -> [(String, [Action])]@@ -40,29 +41,32 @@ -- | Trim all unneeded stuff from a model. trimModel :: Model -> Model-trimModel = id --XXX+trimModel = id --XXX + -- | Verify a trimmed model. verifyModel :: FilePath -> String -> Int -> (String, Model) -> IO ()-verifyModel y format maxK (name, m) = do+verifyModel y format maxK (name, m') = do printf format name hFlush stdout env0 <- initEnv y m- execStateT (check env0 1) env0+ execStateT (actions (initActions m) >> doLoop >> check env0 1) env0 return () where+ m = trimModel m' check :: Env -> Int -> Y () check env0 k = do- transition m+ actions $ loopActions m resultBasis <- checkBasis m env0 case resultBasis of- Fail a -> liftIO (printf "FAILED: disproved basis in k = %d (see trace)\n" k) >> writeTrace name a+ Fail a -> liftIO (printf "FAILED: disproved basis in k = %d (see trace)\n" k) >> writeTrace True name a Problem -> error "Verify.check1"- Pass -> do+ Pass | null $ loopActions m -> liftIO $ printf "passed: assertion in initialization\n"+ | otherwise -> do resultStep <- checkStep case resultStep of Fail a | k < maxK -> check env0 (k + 1)- | otherwise -> liftIO (printf "inconclusive: unable to proved step up to max k = %d (see trace)\n" k) >> writeTrace name a+ | otherwise -> liftIO (printf "inconclusive: unable to proved step up to max k = %d (see trace)\n" k) >> writeTrace False name a Problem -> error "Verify.check2" Pass -> liftIO $ printf "passed: proved step in k = %d\n" k @@ -72,16 +76,21 @@ checkStep :: Y Result checkStep = do env <- get- let cmds' = reverse $ [CHECK] ++ [ASSERT $ NOT $ head $ asserts env] ++ cmds env- r <- liftIO $ quickCheckY' (yices env) [] $ cmds'- return $ result r+ if null $ asserts env+ then return Pass+ else do+ let cmds = vars env ++ loopCmds env ++ [ASSERT $ NOT $ head $ asserts env] ++ [CHECK]+ r <- liftIO $ quickCheckY' (yices env) [] cmds+ return $ result r -- | Check induction basis. checkBasis :: Model -> Env -> Y Result checkBasis m env0 = do env <- get- let cmds' = reverse $ [CHECK] ++ [ASSERT $ NOT $ AND $ asserts env] ++ [ ASSERT $ VarE (getVar' env0 (State a)) := exprConst t c | a@(VS _ t c _) <- variables m ] ++ cmds env- r <- liftIO $ quickCheckY' (yices env) [] cmds'+ let cmds = vars env ++ initCmds env ++ loopCmds env ++ [ASSERT $ NOT $ AND $ asserts env]+ ++ [ ASSERT $ VarE (getVar' env0 (State a)) := exprConst t c | a@(VS _ t c _) <- variables m ]+ ++ [CHECK]+ r <- liftIO $ quickCheckY' (yices env) [] cmds return $ result r result :: ResY -> Result@@ -92,16 +101,14 @@ _ -> error $ "unexpected yices results: " ++ show a --- | Transition relation. Returns assertion.-transition :: Model -> Y ()-transition m = mapM_ (action $ LitB True) $ actions m+actions :: [Action] -> Y ()+actions a = mapM_ (action $ LitB True) a assign :: Position -> V -> String -> Y () assign p v n = do- env <- get case v of- State (VS name _ _ _) -> put env { trace = Assign' p name n : trace env }- Local name _ _ _ -> put env { trace = Assign' p name n : trace env }+ State (VS name _ _ _) -> addTrace $ Assign' p name n+ Local name _ _ _ -> addTrace $ Assign' p name n _ -> return () action :: ExpY -> Action -> Y ()@@ -109,34 +116,38 @@ Declare v e p -> do e <- expr (typeOf v) e v' <- addVar v- env <- get- put env { cmds = ASSERT (VarE v' := e) : cmds env }+ addCmd $ ASSERT (VarE v' := e) assign p v v' Assign v e p -> do e <- expr (typeOf v) e vLast <- getVar v v' <- addVar v- env <- get- put env { cmds = ASSERT (VarE v' := IF enabled e (VarE vLast)) : cmds env }+ addCmd $ ASSERT (VarE v' := IF enabled e (VarE vLast)) assign p v v' Assert a n p -> do a <- getVar a env <- get- put env { asserts = (enabled :=> VarE a) : asserts env, trace = Assert' p n a : trace env }+ put env { asserts = (enabled :=> VarE a) : asserts env }+ addTrace $ Assert' p n a Assume a _ _ -> do a <- getVar a- env <- get- put env { cmds = ASSERT (enabled :=> VarE a) : cmds env }+ addCmd $ ASSERT (enabled :=> VarE a) Branch a onTrue onFalse p -> do a' <- getVar a env0 <- get- put env0 { trace = [] }+ if inLoop env0+ then put env0 { loopTrace = [] }+ else put env0 { initTrace = [] } mapM_ (action $ AND [enabled, VarE a']) onTrue env1 <- get- put env1 { trace = [] }+ if inLoop env1+ then put env1 { loopTrace = [] }+ else put env1 { initTrace = [] } mapM_ (action $ AND [enabled, NOT $ VarE a']) onFalse env2 <- get- put env2 { trace = Branch' p a' (reverse $ trace env1) (reverse $ trace env2) : trace env0 }+ if inLoop env2+ then put env2 { loopTrace = Branch' p a' (reverse $ loopTrace env1) (reverse $ loopTrace env2) : loopTrace env0 }+ else put env2 { initTrace = Branch' p a' (reverse $ initTrace env1) (reverse $ initTrace env2) : initTrace env0 } @@ -223,19 +234,38 @@ type Y = StateT Env IO data Env = Env- { yices :: FilePath- , nextId :: Int- , vars :: [(V, String)]- , cmds :: [CmdY]- , asserts :: [ExpY]- , trace :: [Trace]+ { yices :: FilePath+ , nextId :: Int+ , inLoop :: Bool+ , names :: [(V, String)]+ , vars :: [CmdY]+ , initCmds :: [CmdY]+ , loopCmds :: [CmdY]+ , asserts :: [ExpY]+ , initTrace :: [Trace]+ , loopTrace :: [Trace] } -data Trace = Assign' Position String String | Assert' Position String String | Branch' Position String [Trace] [Trace] +data Trace+ = Assign' Position String String+ | Assert' Position String String+ | Branch' Position String [Trace] [Trace] initEnv :: FilePath -> Model -> IO Env-initEnv y m = execStateT (mapM_ f $ variables m) (Env y 0 [] [] [] [])+initEnv y m = execStateT (mapM_ f $ variables m) env where+ env = Env+ { yices = y+ , nextId = 0+ , inLoop = False+ , names = []+ , vars = []+ , initCmds = []+ , loopCmds = []+ , asserts = []+ , initTrace = []+ , loopTrace = []+ } f :: VS -> Y () f a = do n <- addVar v@@ -243,22 +273,41 @@ where v = State a +doLoop :: Y ()+doLoop = do+ env <- get+ put env { inLoop = True }+ addVar :: V -> Y String addVar v = do env <- get let name = printf "n%d" $ nextId env- put env { nextId = nextId env + 1, vars = (v, name) : vars env, cmds = DEFINE (name, VarT $ typeY v) Nothing : cmds env }+ put env { nextId = nextId env + 1, names = (v, name) : names env, vars = DEFINE (name, VarT $ typeY v) Nothing : vars env } return name +addCmd :: CmdY -> Y ()+addCmd cmd = do+ env <- get+ if inLoop env+ then put env { loopCmds = cmd : loopCmds env }+ else put env { initCmds = cmd : initCmds env }++addTrace :: Trace -> Y ()+addTrace t = do+ env <- get+ if inLoop env+ then put env { loopTrace = t : loopTrace env }+ else put env { initTrace = t : initTrace env }+ getVar :: V -> Y String getVar v = do env <- get return $ getVar' env v getVar' :: Env -> V -> String-getVar' env v = case lookup v $ vars env of+getVar' env v = case lookup v $ names env of Just a -> a- Nothing -> error $ "Verify.getVar: variable not found: " ++ show v ++ " in " ++ show (vars env)+ Nothing -> error $ "Verify.getVar: variable not found: " ++ show v ++ " in " ++ show (names env) typeY :: TypeOf a => a -> String typeY a = case typeOf a of@@ -268,12 +317,14 @@ Integer _ -> "int" Rational _ -> "real" -writeTrace :: String -> [ExpY] -> Y ()-writeTrace name table' = do+writeTrace :: Bool -> String -> [ExpY] -> Y ()+writeTrace withInit name table' = do env <- get- let format p = printf ("%-" ++ show (maximum $ map maxPosWidth $ trace env) ++ "s ") $ position p- liftIO $ writeFile (name ++ ".trace") $ concatMap (f format) $ reverse $ trace env+ let format p = printf ("%-" ++ show (maximum $ map maxPosWidth $ (if withInit then initTrace env else []) ++ loopTrace env) ++ "s ") $ position p+ liftIO $ writeFile (name ++ ".trace") $ (if withInit then trace format "init" (initTrace env) else "") ++ trace format "loop" (loopTrace env) where+ trace :: (Position -> String) -> String -> [Trace] -> String+ trace format n t = n ++ ":\n" ++ concatMap (f format) (reverse t) table = [ (n, if v then "true" else "false") | VarE n := LitB v <- table' ] ++ [ (n, show v) | VarE n := LitI v <- table' ] ++ [ (n, show v) | VarE n := LitR v <- table' ]