afv (empty) → 0.0.0
raw patch · 11 files changed
+1299/−0 lines, 11 filesdep +basedep +bytestringdep +directorysetup-changed
Dependencies added: base, bytestring, directory, language-c, mtl, process, yices
Files
- LICENSE +27/−0
- RELEASE-NOTES +4/−0
- Setup.lhs +8/−0
- afv.cabal +52/−0
- src/AFV.hs +150/−0
- src/Compile.hs +457/−0
- src/Error.hs +27/−0
- src/Model.hs +202/−0
- src/Parse.hs +28/−0
- src/Utils.hs +85/−0
- src/Verify.hs +259/−0
+ LICENSE view
@@ -0,0 +1,27 @@+Copyright (c) Tom Hawkins 2009, 2010++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.+3. Neither the name of the author nor the names of his contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+SUCH DAMAGE.
+ RELEASE-NOTES view
@@ -0,0 +1,4 @@+afv 0.0.0 01/12/10++- Initial release.+
+ Setup.lhs view
@@ -0,0 +1,8 @@+#! /usr/bin/env runhaskell++> module Main (main) where+>+> import Distribution.Simple (defaultMain)+>+> main :: IO ()+> main = defaultMain
+ afv.cabal view
@@ -0,0 +1,52 @@+name: afv+version: 0.0.0++category: Formal Methods++synopsis: Model checking Atom generated C.++description: A model checker for Atom generated, or similar, C code.++author: Tom Hawkins <tomahawkins@gmail.com>+maintainer: Tom Hawkins <tomahawkins@gmail.com>++stability: experimental++license: BSD3+license-file: LICENSE++homepage: http://tomahawkins.org++build-type: Simple+cabal-version: >= 1.6++extra-source-files:+ RELEASE-NOTES++executable afv+ hs-source-dirs: src++ main-is: AFV.hs++ other-modules: Compile,+ Error,+ Model,+ Parse,+ Utils,+ Verify++ build-depends:+ base >= 4 && < 5,+ bytestring >= 0.9.1 && < 0.9.2,+ mtl >= 1.1 && < 1.2,+ directory >= 1.0 && < 1.1,+ process >= 1.0 && < 1.1,+ language-c >= 0.3 && < 0.4,+ yices >= 0.0.0.4 && < 0.1++ ghc-options: -W+ extensions:++source-repository head+ type: darcs+ location: http://patch-tag.com/r/tomahawkins/afv/pullrepo
+ src/AFV.hs view
@@ -0,0 +1,150 @@+module Main (main) where++import Data.List+import System.Environment+import System.IO++import Compile+import Parse+import Verify++main :: IO ()+main = do+ args <- getArgs+ case args of+ [] -> help+ (a:_) | elem a ["help", "-h", "--help", "-help"] -> help+ ["example"] -> example+ ["header"] -> header+ ("verify":function: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+ verify (yices a) (k a) model+ _ -> help++data Args = Args+ { yices :: FilePath+ , k :: Int+ , gcc :: FilePath+ , defines :: [(String, String)]+ , includes :: [FilePath]+ , inputs :: [FilePath]+ } deriving Show++parseArgs :: Args -> [String] -> Args+parseArgs a b = case b of+ [] -> a+ arg : args | isYices -> parseArgs a { gcc = pathYices } args+ | isGCC -> parseArgs a { gcc = pathGCC } args+ | isInclude -> parseArgs a { includes = includes a ++ [drop 2 arg] } args+ | isDefine -> parseArgs a { defines = defines a ++ [(name, value)] } args+ where+ isYices = isPrefixOf "--yices=" arg+ pathYices = drop 8 arg+ isGCC = isPrefixOf "--gcc=" arg+ pathGCC = drop 6 arg+ isInclude = length arg > 2 && take 2 arg == "-I"+ isDefine = length arg > 2 && take 2 arg == "-D"+ (name, value') = span (/= '=') $ drop 2 arg+ value = if length value' <= 1 then "1" else tail value'+ "-k" : k : args -> parseArgs a { k = read k } args+ files -> a { inputs = files }+ ++header :: IO ()+header = do+ putStrLn "writing AFV header file (afv.h) ..."+ writeFile "afv.h" $ unlines+ [ "#ifndef AFV"+ , "#include <assert.h>"+ , "#define assume assert"+ , "#endif"+ ]++example :: IO ()+example = do+ header+ putStrLn "writing example design (example.c) ..."+ putStrLn "verify with: afv verify example -k 15 example.c"+ writeFile "example.c" $ unlines+ [ "// Provides assert and assume functions."+ , "#include \"afv.h\""+ , ""+ , "// Periodic function for verification. Implements a simple rolling counter."+ , "void example () {"+ , ""+ , " // The rolling counter."+ , " static int counter = 0;"+ , ""+ , " // Assertions."+ , " GreaterThanOrEqualTo0: assert(counter >= 0); // The 'counter' must always be greater than or equal to 0."+ , " LessThan10: assert(counter < 10); // The 'counter' must always be less than 10."+ , ""+ , " // Implementation 1."+ , " if (counter == 10)"+ , " counter = 0;"+ , " else"+ , " counter++;"+ , ""+ , " // Implementation 2."+ , " // if (counter == 9)"+ , " // counter = 0;"+ , " // else"+ , " // counter = counter + 1;"+ , ""+ , " // Implementation 3."+ , " // if (counter >= 9)"+ , " // counter = 0;"+ , " // else"+ , " // counter++;"+ , ""+ , " // Implementation 4."+ , " // if (counter >= 9 || counter < 0)"+ , " // counter = 0;"+ , " // else"+ , " // counter++;"+ , ""+ , " // Implementation 5."+ , " // counter = (counter + 1) % 10;"+ , ""+ , "}"+ , ""+ ]++++help :: IO ()+help = putStrLn $ unlines+ [ ""+ , "NAME"+ , " afv - Atom's Formal Verifier"+ , ""+ , "SYNOPSIS"+ , " afv verify function-name [-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."+ , " Requires GCC for C preprocessing and the Yices SMT solver."+ , ""+ , "COMMANDS"+ , " verify function-name [options] c-files ..."+ , " Runs verification on the specified periodic function."+ , ""+ , " -k n : Max k-induction depth."+ , " --yices=path : Path to Yices solver."+ , " --gcc=path : Path to GCC."+ , " -Idir : Add directory to includes search path."+ , " -Dmacro : Define a macro."+ , " -Dmacro=def : Define a macro with a value."+ , ""+ , " header"+ , " Writes AFV's header file (afv.h), which provides the 'assert' and 'assume' functions."+ , ""+ , " example"+ , " Writes an example design (example.c). Verify with:"+ , " afv verify example -k 15 example.c"+ , ""+ ]
+ src/Compile.hs view
@@ -0,0 +1,457 @@+module Compile (compile) where++import Control.Monad.State hiding (State)+import Data.List+import Language.C+import Language.C.Data.Ident++import Error+import Model hiding (CInteger)+import qualified Model as M+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) }++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 then notSupported (head asms) "inline assembly" else CCompound [] (vars ++ funcs ++ [CBlockStmt call]) none+ where+ items = [ a | CTranslUnit items _ <- units, a <- items ]+ vars = [ CBlockDecl (CDecl (CStorageSpec (CStatic none) : specs) a b) | CDeclExt (CDecl specs a b) <- items ] -- Make top level vars static.+ funcs = map CNestedFunDef $ sortFunctions [ a | CFDefExt a <- items ]+ asms = [ a | CAsmExt a <- items ]+ call :: CStat+ call = CExpr (Just $ CCall (CVar (Ident name 0 none) none) [] none) none+ -- XXX Need to check for top level name conflicts.++type M = StateT MDB IO++data MDB = MDB+ { nextId :: Int+ , stack :: [Ident]+ , enabled :: E+ , model :: Model+ }++initMDB :: MDB+initMDB = MDB+ { nextId = 0+ , stack = []+ , enabled = true+ , model = Model { variables = [], actions = [] }+ }++++-- | Environment for resolving identifiers.+data Env = Env+ { values :: [Value]+ }++data Value+ = EnvFunction String Function+ | EnvVariable String V++data Function = Function Int ([E] -> M E)++-- | 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 ]++-- | 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 ]++-- | Creates a branch.+branch :: Position -> E -> M a -> M a -> M (a, a)+branch n a onTrue onFalse = do+ m1 <- get+ put m1 { enabled = And (enabled m1) a n }+ r1 <- onTrue+ m2 <- get+ put m2 { enabled = And (enabled m1) (Not a n) n }+ r2 <- onFalse+ m3 <- get+ put m3 { enabled = enabled m1 }+ return (r1, r2)++-- | Push an identifier onto the call stack, do something, then pop it off.+callStack :: Ident -> M a -> M a+callStack id a = do+ m <- get+ put m { stack = id : stack m }+ a <- a+ m <- get+ put m { stack = tail $ stack m }+ return a++callPath :: M ([String], Position)+callPath = do+ m <- get+ let s = stack m+ return ([ n | Ident n _ _ <- reverse s ], posOf $ head s)++-- | The initial environment defines the assert and assume functions.+initEnv :: Env+initEnv = Env+ { values =+ [ EnvFunction "assert" $ Function 1 assert+ , EnvFunction "assume" $ Function 1 assume+ ]+ }+ where+ assert a = do+ (s, n) <- callPath+ m <- get+ let x = imply (enabled m) (head a) n+ newAction $ Assert x s n+ return x+ assume a = do+ (s, n) <- callPath+ m <- get+ let x = imply (enabled m) (head a) n+ newAction $ Assume x s n+ return x++-- | Adds new action.+newAction :: Action -> M ()+newAction a = do+ m <- get+ put m { model = (model m) { actions = a : actions (model m) }}++-- | 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 }+ where+ name = case a of+ State (VS a _ _ _) -> a+ Volatile a _ _ -> a+ Local a _ _ _ -> a+ Tmp _ _ _ -> error "Compile.addVar: should not call addVar with Tmp"++++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+ branch (posOf n) a (evalStat env b) (evalStat env c)+ return ()+ _ -> 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+evalExpr env a = latch (posOf 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 False (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+ (b, c) <- branch (posOf n) a (evalExpr env b) (evalExpr env c)+ return $ Mux a b c $ posOf n+ CCond a Nothing b n -> do+ a <- evalExpr env a+ (a, b) <- branch (posOf n) a (return 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+ 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+ CAndOp -> f And+ CXorOp -> f $ \ a b n -> Or (And a (Not b n) n) (And (Not a n) b n) n+ COrOp -> f Or+ CLndOp -> do+ a <- evalExpr env a+ (b, a) <- branch n' a (evalExpr env b) (return a)+ return $ Mux a b a n'+ CLorOp -> do+ a <- evalExpr env a+ (a, b) <- branch n' a (return a) (evalExpr env b)+ return $ Mux a a b n'+ 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'++ CUnary op a n -> do+ a <- evalExpr env a+ case (op, a) of+ (CPreIncOp, Var a) -> do+ assign False p a $ Add (Var a) one p+ return $ Var a+ (CPreDecOp, Var a) -> do+ assign False p a $ Sub (Var a) one p+ return $ Var a+ (CPostIncOp, Var a) -> do+ b <- latch p $ Var a+ assign False p a $ Add (Var a) one p+ return b+ (CPostDecOp, Var a) -> do+ b <- latch p $ Var a+ assign False p a $ Sub (Var a) one p+ return b+ (CPlusOp, a) -> return $ Add zero a p+ (CMinOp, a) -> return $ Sub zero a p+ (CNegOp, a) -> return $ Not a p+ _ -> notSupported n "unary operator"+ where+ 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+ 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+ _ -> notSupported a "char or string constant"+ _ -> notSupported a "expression"+ +evalDecl :: Env -> CDecl -> M Env+evalDecl env d@(CDecl specs decls _) = if isExtern typInfo then return env else foldM evalDecl' env decls+ where+ (typInfo, typ) = typeInfo specs+ evalDecl' :: Env -> (Maybe CDeclr, Maybe CInit, Maybe CExpr) -> M Env+ evalDecl' env (a, b, c) = case a of+ Just (CDeclr (Just i@(Ident name _ n)) [] Nothing [] _) -> case (b, c) of+ (Nothing, Nothing) -> evalDecl' env (a, Just $ CInitExpr (CConst (CIntConst (cInteger 0) n)) n, Nothing)+ (Just (CInitExpr c@(CConst const) n'), Nothing) | isStatic typInfo && not (isVolatile typInfo) -> addVar env v+ | otherwise -> evalDecl' env (a, Nothing, Just c)+ where+ v = State $ VS name typ init $ posOf n+ init = case typ of+ Void -> unexpected d "void type for variable declaration"+ Bool -> CBool (cInt /= 0) $ posOf n'+ Integer _ -> M.CInteger cInt $ posOf n'+ Rational _ -> CRational cRat $ posOf n'++ cInt :: Integer+ cInt = case const of+ CIntConst (CInteger a _ _) _ -> a+ _ -> unexpected const "non integer initialization"++ cRat :: Rational+ cRat = case const of+ CIntConst (CInteger a _ _) _ -> fromIntegral a+ CFloatConst (CFloat a) _ -> fromIntegral (read a :: Integer)+ _ -> unexpected const "non numeric initialization"++ (Nothing, Just e') -> do+ e <- evalExpr env e'+ v <- if isVolatile typInfo+ then return $ Volatile name typ $ posOf n+ else do+ m <- get+ put m { nextId = nextId m + 1 }+ return $ Local name typ (nextId m) (posOf e')+ assign True (posOf e') v e+ addVar env v+ _ -> notSupported i "variable declaration"+ _ -> notSupported d "arrays, pointers, or functional pointers (So what good is this tool anyway?)"+ ++evalFunc :: Env -> CFunDef -> M Env+evalFunc env f = do+ when (typ /= Void || not (null args)) $ notSupported f "non void f(void) function (How lame is this?)"+ return env { values = EnvFunction name (Function (length args) func) : values env }+ where+ (specs, (Ident name _ _), args, stat) = functionInfo f+ (_, typ) = typeInfo specs+ func _ = do --XXX+ evalStat env stat+ return false+++++++-- | Assgins a value to a variable.+assign :: Bool -> Position -> V -> E -> M ()+assign decl n v a = case v of+ Volatile _ _ _ -> return ()+ _ -> do+ m <- get+ newAction $ Assign v $ if decl then a else Mux (enabled m) a (Var v) n --XXX Is this correct for local and tmp variables?++-- | Latch a value at a point in time and return the expression of the latch variable.+latch :: Position -> E -> M E+latch _ (Var a) = return (Var a)+latch _ (Const a) = return (Const a)+latch n a = do+ m <- get+ let v = Tmp (typeOf a) (nextId m) n+ put m { nextId = nextId m + 1 }+ assign True n v a+ return $ Var v++-- | Logical implication. Assumes Bool inputs.+imply :: E -> E -> Position -> E+imply a b n = Or (Not a n) b n+++++++-- | Extract relavent info from a function declaration.+functionInfo :: CFunDef -> ([CDeclSpec], Ident, [CDecl], CStat)+functionInfo (CFunDef specs (CDeclr (Just ident) [(CFunDeclr (Right (args, False)) _ _)] Nothing [] _) [] stmt _) = (specs, ident, args, stmt)+functionInfo f = notSupported f "function"+++++-- | Topologically sorts functions based on dependencies.+sortFunctions :: [CFunDef] -> [CFunDef]+sortFunctions fs = case topo $ map functionDeps fs of+ Left a -> notSupported none $ "recursive functions somewhere among: " ++ show a+ Right a -> [ f | a <- a, f <- fs, functionName f == a ]++topo :: Eq a => [(a, [a])] -> Either [a] [a]+topo a = topo' [] a+ where+ topo' a [] = Right a+ topo' done waiting = if null next then Left $ fst $ unzip waiting else topo' (done ++ next) stillWaiting+ where+ next = [ a | (a, deps) <- waiting, all (flip elem done) deps ]+ stillWaiting = [ (a, deps) | (a, deps) <- waiting, notElem a next ]+++functionName :: CFunDef -> String+functionName f = name where (_, Ident name _ _, _, _) = functionInfo f++afvFunctionNames :: [String]+afvFunctionNames = ["assert", "assume"]++-- | Analyzes a function for dependencies.+functionDeps :: CFunDef -> (String, [String])+functionDeps f = (functionName f, filter (flip notElem afvFunctionNames) $ nub $ fb [] [CNestedFunDef f])+ where+ rewrite :: CFunDef -> CStat+ rewrite f = CCompound [] (map CBlockDecl args ++ [CBlockStmt stat]) none where (_, _, args, stat) = functionInfo f+ fs :: [String] -> CStat -> [String]+ fs env a = case a of+ CLabel _ a _ _ -> fs env a+ CCase a b _ -> fe env a ++ fs env b+ CCases a b c _ -> fe env a ++ fe env b ++ fs env c+ CDefault a _ -> fs env a+ CExpr (Just a ) _ -> fe env a+ CExpr Nothing _ -> []+ CCompound _ a _ -> fb env a+ CIf a b Nothing _ -> fe env a ++ fs env b+ 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 _ _ _ _ _ -> notSupported a "for loops"+ CGoto _ _ -> []+ CGotoPtr a _ -> fe env a+ CCont _ -> []+ CBreak _ -> []+ CReturn Nothing _ -> []+ CReturn (Just a) _ -> fe env a+ CAsm _ _ -> []+ fe :: [String] -> CExpr -> [String]+ fe env a = case a of+ CComma a _ -> fe' a+ CAssign _ a b _ -> fe' [a, b]+ CCond a (Just b) c _ -> fe' [a, b, c]+ CCond a Nothing b _ -> fe' [a, b]+ CBinary _ a b _ -> fe' [a, b]+ CCast _ a _ -> fe env a --XXX does not check cast declaration.+ CUnary _ a _ -> fe env a+ --CSizeofExpr CExpr NodeInfo+ --CSizeofType CDecl NodeInfo+ --CAlignofExpr CExpr NodeInfo+ --CAlignofType CDecl NodeInfo+ --CComplexReal CExpr NodeInfo+ --CComplexImag CExpr NodeInfo+ CIndex a b _ -> fe' [a, b]+ CCall (CVar (Ident n _ _) _) args _ -> (if elem n env then [] else [n]) ++ fe' args+ CCall _ _ _ -> notSupported a "non-named function references"+ CMember a _ _ _ -> fe env a+ CVar _ _ -> []+ CConst _ -> []+ --CCompoundLit CDecl CInitList NodeInfo+ CStatExpr a _ -> fs env a+ CLabAddrExpr _ _ -> []+ --CBuiltinExpr CBuiltin+ _ -> notSupported a "expression"+ where+ fe' = concatMap $ fe env+ fb :: [String] -> [CBlockItem] -> [String]+ fb _ [] = []+ fb env (a:b) = case a of+ CBlockStmt a -> fs env a ++ fb env b+ CBlockDecl _ -> fb env b+ CNestedFunDef f -> fs env' (rewrite f) ++ fb env' b where env' = functionName f : env+
+ src/Error.hs view
@@ -0,0 +1,27 @@+module Error+ ( err+ , notSupported+ , unexpected+ , debug+ , debug'+ ) where++import Language.C+import System.IO.Unsafe++err :: Pos a => a -> String -> b+err a m = error $ format (posOf a) ++ ": " ++ m+ where+ format (Position name line col) = name ++ ":" ++ show line ++ ":" ++ show col++notSupported :: Pos a => a -> String -> b+notSupported a m = err a $ " not supported: " ++ m++unexpected :: Pos a => a -> String -> b+unexpected a m = err a $ "unexpected: " ++ m++debug :: Show a => String -> a -> a+debug m a = debug' m a a++debug' :: Show a => String -> a -> b -> b+debug' m a b = unsafePerformIO (putStrLn (m ++ ": " ++ show a) >> return b)
+ src/Model.hs view
@@ -0,0 +1,202 @@+module Model+ ( Type (..)+ , IntegerType (..)+ , TypeOf (..)+ , Const (..)+ , VS (..)+ , V (..)+ , E (..)+ , Action (..)+ , Model (..)+ , true+ , false+ , isRational+ , isInteger+ , unify+ ) where++import Language.C.Data.Position++import Error++data IntegerType = U8 | U16 | U32 | U64 | S8 | S16 | S32 | S64 deriving (Show, Eq)++data Type+ = Void+ | Bool+ | Integer (Maybe IntegerType)+ | Rational (Maybe Bool) -- isDouble+ deriving Eq++isInteger :: Type -> Bool+isInteger (Integer _) = True+isInteger _ = False++isRational :: Type -> Bool+isRational (Rational _) = True+isRational _ = False++instance Show Type where+ show a = case a of+ Void -> "void"+ Bool -> "bool"+ Integer Nothing -> "unspecified integer type"+ Integer (Just a) -> case a of+ U8 -> "unsigned char"+ U16 -> "unsigned short"+ U32 -> "unsigned long"+ U64 -> "unsigned long long"+ S8 -> "signed char"+ S16 -> "signed short"+ S32 -> "signed long"+ S64 -> "signed long long"+ Rational Nothing -> "unspecified floating type"+ Rational (Just True) -> "double"+ Rational (Just False) -> "float"++class TypeOf a where typeOf :: a -> Type++instance TypeOf Type where typeOf = id++data Const+ = CBool Bool Position+ | CInteger Integer Position+ | CRational Rational Position+ deriving (Show, Eq)++instance TypeOf Const where+ typeOf a = case a of+ CBool _ _ -> Bool+ CInteger _ _ -> Integer Nothing+ CRational _ _ -> Rational Nothing++instance Pos Const where+ posOf a = case a of+ CBool _ a -> a+ CInteger _ a -> a+ CRational _ a -> a++-- | User defined state variable (non-volatile top level variables or non-volatile static variables in functions).+data VS = VS String Type Const Position deriving (Show, Eq)++instance TypeOf VS where typeOf (VS _ t _ _) = t+instance Pos VS where posOf (VS _ _ _ n) = n++-- | Variables.+data V+ = State VS+ | Volatile String Type Position+ | Local String Type Int Position+ | Tmp Type Int Position+ deriving (Show, Eq)+ +instance TypeOf V where+ typeOf a = case a of+ State a -> typeOf a+ Volatile _ a _ -> a+ Local _ a _ _ -> a+ Tmp a _ _ -> a++instance Pos V where+ posOf a = case a of+ State a -> posOf a+ Volatile _ _ n -> n+ Local _ _ _ n -> n+ Tmp _ _ n -> n++{-+instance Pos V where+ posOf a = case a of+ V _ _ a -> a+ Volatile _ _ a -> a+ Local _ _ a _ -> a+-} ++-- | Expressions.+data E+ = Var V+ | Const Const+ | Not E Position+ | And E E Position+ | Or E E Position+ | Mul E E Position+ | Div E E Position+ | Mod E E Position+ | Add E E Position+ | Sub E E Position+ | Lt E E Position+ | Eq E E Position+ | Mux E E E Position+ deriving (Show, Eq)++instance Pos E where+ posOf a = case a of+ Var a -> posOf a+ Const a -> posOf a+ Not _ a -> a+ And _ _ a -> a+ Or _ _ a -> a+ Mul _ _ a -> a+ Div _ _ a -> a+ Mod _ _ a -> a+ Add _ _ a -> a+ Sub _ _ a -> a+ Lt _ _ a -> a+ Eq _ _ a -> a+ Mux _ _ _ a -> a++instance TypeOf E where+ typeOf a = case a of+ Var a -> typeOf a+ Const a -> typeOf a+ Not a _ -> unify' Bool (typeOf a)+ And a b _ -> unify' Bool $ unify' (typeOf a) (typeOf b)+ Or a b _ -> unify' (typeOf a) (typeOf b)+ Mul a b _ -> unify' (typeOf a) (typeOf b)+ Div a b _ -> unify' (typeOf a) (typeOf b)+ Mod a b _ -> unify' (typeOf a) (typeOf b)+ Add a b _ -> unify' (typeOf a) (typeOf b)+ Sub a b _ -> unify' (typeOf a) (typeOf b)+ Lt a b _ -> seq (unify' (typeOf a) (typeOf b)) Bool+ Eq a b _ -> seq (unify' (typeOf a) (typeOf b)) Bool+ Mux a b c _ -> seq (unify' (typeOf b) (typeOf c)) $ unify' Bool (typeOf a)+ where+ unify' = unify a++unify :: Pos a => a -> Type -> Type -> Type+unify n a b = case (a, b) of+ (a, b) | a == b -> a++ (Bool, Integer Nothing) -> Bool+ (Integer Nothing, Bool) -> Bool++ (Integer Nothing, a@(Integer _)) -> a+ (a@(Integer _), Integer Nothing) -> a++ (Integer Nothing, a@(Rational _)) -> a+ (a@(Rational _), Integer Nothing) -> a++ (Rational Nothing, a@(Rational _)) -> a+ (a@(Rational _), Rational Nothing) -> a++ (a, b) -> unexpected n $ "type violation: " ++ show a ++ " incompatiable with " ++ show b++true :: E+true = Const $ CBool True nopos++false :: E+false = Const $ CBool False nopos++instance Pos Position where posOf = id++data Action+ = Assign V E+ | Assert E [String] Position+ | Assume E [String] Position+ deriving (Show, Eq)++data Model = Model+ { variables :: [VS]+ , actions :: [Action]+ } deriving Show+
+ src/Parse.hs view
@@ -0,0 +1,28 @@+module Parse (parse) where++import Language.C+import Language.C.System.GCC+import Language.C.System.Preprocess+import System.Exit+import System.IO++parse :: FilePath -> [FilePath] -> [(String,String)] -> FilePath -> IO CTranslUnit+parse gcc includes defines file = do+ a <- preprocess gcc includes defines file+ case parseC a (initPos file) of+ Left e -> putStrLn ("parsing error: " ++ show e) >> exitFailure+ Right a -> return a++preprocess :: FilePath -> [FilePath] -> [(String,String)] -> FilePath -> IO InputStream+preprocess gcc includes defines file = do+ a <- runPreprocessor (newGCC gcc) CppArgs+ { cppOptions = map IncludeDir includes ++ map (\ (a,b) -> Define a b) defines+ , extraOptions = []+ , cppTmpDir = Nothing+ , inputFile = file+ , outputFile = Nothing+ }+ case a of+ Left e -> putStrLn "preprocessing error" >> exitWith e+ Right a -> return a+
+ src/Utils.hs view
@@ -0,0 +1,85 @@+module Utils+ ( TypeInfo (..)+ , typeInfo+ , isVisable+ ) where++import Data.List+import Language.C++import Error+import Model++data TypeInfo = TypeInfo+ { isConst+ , isVolatile+ , isStatic+ , isExtern :: Bool+ }++isVisable :: TypeInfo -> Bool+isVisable t = not (isStatic t) && not (isExtern t)++typeInfo :: [CDeclSpec] -> (TypeInfo, Type)+typeInfo specs = (info, typ)+ where+ info = foldl f TypeInfo { isConst = False, isVolatile = False, isStatic = False, isExtern = False } specs+ f :: TypeInfo -> CDeclSpec -> TypeInfo+ f t a = case a of+ CStorageSpec a -> case a of+ CStatic _ -> t { isStatic = True }+ CExtern _ -> t { isExtern = True }+ _ -> notSupported a "storage class"+ CTypeQual a -> case a of+ CConstQual _ -> t { isConst = True }+ CVolatQual _ -> t { isVolatile = True }+ _ -> notSupported a "type qualifier"+ CTypeSpec _ -> t+ voids = [ a | CTypeSpec a@(CVoidType _) <- specs ]+ signed = [ a | CTypeSpec a@(CSignedType _) <- specs ]+ unsigned = [ a | CTypeSpec a@(CUnsigType _) <- specs ]+ bools = [ a | CTypeSpec a@(CBoolType _) <- specs ]+ chars = [ a | CTypeSpec a@(CCharType _) <- specs ]+ shorts = [ a | CTypeSpec a@(CShortType _) <- specs ]+ longs = [ a | CTypeSpec a@(CLongType _) <- specs ]+ ints = [ a | CTypeSpec a@(CIntType _) <- specs ]+ floats = [ a | CTypeSpec a@(CFloatType _) <- specs ]+ doubles = [ a | CTypeSpec a@(CDoubleType _) <- specs ]+ typ = case (length voids, length signed, length unsigned, length bools, length chars, length shorts, length longs, length ints, length floats, length doubles) of+ (1, 0, 0, 0, 0, 0, 0, 0, 0, 0) -> Void++ (0, 0, 0, 1, 0, 0, 0, 0, 0, 0) -> Bool++ (0, 0, 0, 0, 1, 0, 0, 0, 0, 0) -> Integer $ Just S8+ (0, 1, 0, 0, 1, 0, 0, 0, 0, 0) -> Integer $ Just S8+ (0, 0, 1, 0, 1, 0, 0, 0, 0, 0) -> Integer $ Just U8++ (0, 0, 0, 0, 0, 1, 0, 0, 0, 0) -> Integer $ Just S16+ (0, 1, 0, 0, 0, 1, 0, 0, 0, 0) -> Integer $ Just S16+ (0, 0, 1, 0, 0, 1, 0, 0, 0, 0) -> Integer $ Just U16+ (0, 0, 0, 0, 0, 1, 0, 1, 0, 0) -> Integer $ Just S16+ (0, 1, 0, 0, 0, 1, 0, 1, 0, 0) -> Integer $ Just S16+ (0, 0, 1, 0, 0, 1, 0, 1, 0, 0) -> Integer $ Just U16++ (0, 0, 0, 0, 0, 0, 0, 1, 0, 0) -> Integer $ Just S32+ (0, 1, 0, 0, 0, 0, 0, 1, 0, 0) -> Integer $ Just S32+ (0, 0, 1, 0, 0, 0, 0, 1, 0, 0) -> Integer $ Just U32+ (0, 0, 0, 0, 0, 0, 1, 0, 0, 0) -> Integer $ Just S32+ (0, 1, 0, 0, 0, 0, 1, 0, 0, 0) -> Integer $ Just S32+ (0, 0, 1, 0, 0, 0, 1, 0, 0, 0) -> Integer $ Just U32+ (0, 0, 0, 0, 0, 0, 1, 1, 0, 0) -> Integer $ Just S32+ (0, 1, 0, 0, 0, 0, 1, 1, 0, 0) -> Integer $ Just S32+ (0, 0, 1, 0, 0, 0, 1, 1, 0, 0) -> Integer $ Just U32++ (0, 0, 0, 0, 0, 0, 2, 0, 0, 0) -> Integer $ Just S64+ (0, 1, 0, 0, 0, 0, 2, 0, 0, 0) -> Integer $ Just S64+ (0, 0, 1, 0, 0, 0, 2, 0, 0, 0) -> Integer $ Just U64+ (0, 0, 0, 0, 0, 0, 2, 1, 0, 0) -> Integer $ Just S64+ (0, 1, 0, 0, 0, 0, 2, 1, 0, 0) -> Integer $ Just S64+ (0, 0, 1, 0, 0, 0, 2, 1, 0, 0) -> Integer $ Just U64++ (0, 0, 0, 0, 0, 0, 0, 0, 1, 0) -> Rational $ Just False+ (0, 0, 0, 0, 0, 0, 0, 0, 0, 1) -> Rational $ Just True++ a -> notSupported (head specs) $ "type signature: " ++ show a+
+ src/Verify.hs view
@@ -0,0 +1,259 @@+module Verify (verify) where++import Control.Monad+import Data.IORef+import Data.List+import Data.Maybe+import Math.SMT.Yices.Pipe+import Math.SMT.Yices.Syntax+import System.IO+import Text.Printf++import Model++-- | 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+ y <- createYicesPipe yices []+ mapM_ (verifyModel y maxK) models+ exitY y+ where+ assertions = [ a | a@(Assert _ _ _) <- actions m ]+ models = map (trimModel . trimAssertions m) assertions++-- | Remove all assertions except one.+trimAssertions :: Model -> Action -> Model+trimAssertions m a = m { actions = filter keep $ actions m }+ where+ keep b@(Assert _ _ _) = a == b+ keep _ = True++-- | Trim all unneeded stuff from a model.+trimModel :: Model -> Model+trimModel = id --XXX++-- | Verify a trimmed model.+verifyModel :: YicesIPC -> Int -> Model -> IO ()+verifyModel y maxK m = do+ printf "verifying assertion %s ...\n" (intercalate "." path) >> hFlush stdout+ withLogic y $ do+ initEnv <- initEnv y m+ check initEnv initEnv [] 1+ where+ [path] = [ path | Assert _ path _ <- actions m ]+ check :: Env -> Env -> [ExpY] -> Int -> IO ()+ check _ _ _ k | k > maxK = printf "inconclusive: unable to proved step up to max k = %d\n" maxK+ check initEnv env asserts k = do+ (env, assert) <- transition y m env+ resultBasis <- checkBasis y m initEnv (assert : asserts)+ case resultBasis of+ Fail -> printf "FAILED: disproved basis in k = %d\n" k+ Pass -> do+ resultStep <- checkStep y assert+ case resultStep of+ Fail -> withLogic y $ check initEnv env (assert : asserts) (k + 1)+ Pass -> printf "passed: proved step in k = %d\n" k+ +data Result = Pass | Fail++-- | Check induction step.+checkStep :: YicesIPC -> ExpY -> IO Result+checkStep y assert = do+ r <- withLogic y $ do+ runCmds y [ASSERT $ NOT assert]+ checkLogic y+ return $ result r++-- | Check induction basis.+checkBasis :: YicesIPC -> Model -> Env -> [ExpY] -> IO Result+checkBasis y m env asserts = do+ r <- withLogic y $ do+ runCmds y [ ASSERT $ VarE (getVar env (State a)) := exprConst t c | a@(VS _ t c _) <- variables m ]+ runCmds y [ASSERT $ NOT $ AND asserts]+ checkLogic y+ return $ result r++result :: ResY -> Result+result a = case a of+ Sat _ -> Fail+ UnSat _ -> Pass+ InCon _ -> Pass++-- | Does operation in a new stack.+withLogic :: YicesIPC -> IO a -> IO a+withLogic y a = do+ runCmds y [PUSH]+ a <- a+ runCmds y [POP]+ return a++-- | Transition relation. Returns assertion.+transition :: YicesIPC -> Model -> Env -> IO (Env, ExpY) -- (new env, assertion)+transition y m env = do+ (env', assert) <- foldM (action y) (env, LitB True) (actions m)+ --runCmds y [ ASSERT (VarE (getVar env $ State a) := VarE (getVar env' $ State a)) | a <- variables m ]+ return (env', assert)++action :: YicesIPC -> (Env, ExpY) -> Action -> IO (Env, ExpY)+action y (env, assert) a = case a of+ Assign v e -> do+ e <- expr y env (typeOf v) e+ env <- addVar y env v+ runCmds y [ASSERT $ VarE (getVar env v) := e]+ return (env, assert)+ Assert a _ _ -> do+ a <- expr y env Bool a+ return (env, AND [assert, a])+ Assume a _ _ -> do+ a <- expr y env Bool a+ runCmds y [ASSERT a]+ return (env, assert)++expr :: YicesIPC -> Env -> Type -> E -> IO ExpY+expr y env t a = case a of+ Var a@(Volatile _ _ _) -> do+ env <- addVar y env a+ return $ VarE $ getVar env a+ Var a -> return $ VarE $ getVar env a++ Const a -> return $ exprConst t a++ Not a _ -> do+ a <- expr y env Bool a+ return $ NOT a++ And a b _ -> do+ a <- expr y env Bool a+ b <- expr y env Bool b+ return $ AND [a, b]++ Or a b _ -> do+ a <- expr y env Bool a+ b <- expr y env Bool b+ return $ OR [a, b]++ Mul a b _ -> do+ a <- expr y env t a+ b <- expr y env t b+ return $ a :*: b+ + Div a b _ -> do+ a <- expr y env t a+ b <- expr y env t b+ return $ op a b+ where+ op = case t of+ Integer _ -> DIV+ _ -> (:/:)++ Mod a b _ -> do+ a <- expr y env t a+ b <- expr y env t b+ return $ MOD a b++ Add a b _ -> do+ a <- expr y env t a+ b <- expr y env t b+ return $ a :+: b++ Sub a b _ -> do+ a <- expr y env t a+ b <- expr y env t b+ return $ a :-: b++ Lt a b n -> do+ let t' = unify n (typeOf a) (typeOf b)+ a <- expr y env t' a+ b <- expr y env t' b+ return $ a :< b++ Eq a b n -> do+ let t' = unify n (typeOf a) (typeOf b)+ a <- expr y env t' a+ b <- expr y env t' b+ return $ a := b++ Mux a b c n -> do+ let t' = unify n (typeOf b) (typeOf c)+ a <- expr y env Bool a+ b <- expr y env t' b+ c <- expr y env t' c+ return $ IF a b c++exprConst :: Type -> Const -> ExpY+exprConst t a = case a of + CBool a _ -> LitB a+ CInteger a _ | t == Bool -> LitB $ a /= 0+ | otherwise -> LitI a+ CRational a _ -> LitR a+++++data Env = Env NextId [(V, String)] -- Env nextId table++initEnv :: YicesIPC -> Model -> IO Env+initEnv y m = do+ nid <- newNextId+ foldM (addVar y) (Env nid []) $ map State $ variables m++addVar :: YicesIPC -> Env -> V -> IO Env+addVar y (Env nid table) v = do+ i <- nextId nid+ let name = printf "n%d" i+ runCmds y [DEFINE (name, VarT $ typeY v) Nothing]+ return $ Env nid $ replace (v, name) table++getVar :: Env -> V -> String+getVar (Env _ table) v = case lookup v table of+ Just a -> a+ Nothing -> error $ "Verify.getVar: variable not found: " ++ show v ++ " in " ++ show (fst $ unzip table)++replace :: Eq a => (a, b) -> [(a, b)] -> [(a, b)]+replace a [] = [a]+replace (a, b) ((a', b') : c) | a == a' = (a, b) : c+ | otherwise = (a', b') : replace (a, b) c+++++++typeY :: TypeOf a => a -> String+typeY a = case typeOf a of+ Void -> error "Verify.typeY: void time"+ Bool -> "bool"+ Integer _ -> "int"+ Rational _ -> "real"++++newtype NextId = NextId (IORef Int)++newNextId :: IO NextId+newNextId = newIORef 0 >>= (return . NextId)++nextId :: NextId -> IO Int+nextId (NextId a) = do+ i <- readIORef a+ writeIORef a $ i + 1+ return i+++verbose :: Bool+verbose = False++runCmds :: YicesIPC -> [CmdY] -> IO ()+runCmds y cmds = do+ when verbose $ mapM_ (putStrLn . show) cmds+ runCmdsY' y cmds++checkLogic :: YicesIPC -> IO ResY+checkLogic y = do+ when verbose $ do+ putStrLn "(check)"+ hFlush stdout+ checkY y++