SimpleFormulaChecker.hs 3.74 KiB
module Javawlp.SimpleFormulaChecker where
import Language.Java.Syntax
import Language.Java.Parser
import Language.Java.Pretty
import Z3.Monad
import Data.Maybe
import Javawlp.Engine.Types
import Javawlp.Engine.HelperFunctions
import Javawlp.Engine.Verifier
import Debug.Trace
{-
Hi Duncan, I have written this simple Haskell program that shows you how to use some parts of this
Javawlp library for your purpose. The function "checkformula" below can be used to parse
a Java class of the form:
class C {
boolean f1(x,y,z) { return e1 ; }
boolean f2(... ) { return e2 ; }
...
}
There is an example of such a class in "examples/DuncanExample.java". For e demo, call:
checkformula "../examples/DuncanExample.java" "f1"
checkformula "../examples/DuncanExample.java" "f2"
checkformula "../examples/DuncanExample.java" "f3"
Each time, it will grab the formula in "return e" of the specified method, and will pass it to
Z3 to be checked if the formula is satisfiable. If it is, satisfying instances of the formula's free
variables will be printed.
The starting point to see of to ask Z3 to check Java's formula is the fuunction "unsafeIsSatisfiable"
in the module Verifier. This in turn calls "foldExp expAssertAlgebra expr" to translate expr to
an representation for Z3.
Some notes on the demo:
** To simplify the demo, the method-names must uniquely identify them.
** The used Java-parser library is a 3rd party. Unfortunately it is not that sophisticated.
It seems to be blind to Java's operators' priorities.
To parse e.g. x<0 || x>0 we have to explicityly brackets the sub-expressions: (x<0) || (x>0).
Dependencies:
* Haskell package: language-java
* Haskell package: z3, which then also need z3 binary/lib
-}
checkformula :: FilePath -> String -> IO ()
checkformula javasrc methodname = do
-- parse the Java source file:
compilationnUnit <- parseJava javasrc
-- get all the class declarations in the Java source file; usually a single file defines only
-- one class, but it could theoretically have more:
let decls = getDecls compilationnUnit
-- get the method's body ; to make it simple, the method's name is assumed to uniquely identify its body
let mbody = fromJust $ getMethod decls (Ident methodname)
-- get the method's formal parameters:
let env = getMethodTypeEnv decls (Ident methodname)
putStrLn $ show mbody
-- In this example, we expect the method's body to be of the form { return e ; }
-- Let's grab e:
let StmtBlock (Block [BlockStmt (Return (Just e))]) = mbody -- trace (show mbody) $ mbody
let formula = e
putStrLn ("\n** Formula to check: " ++ prettyPrint formula ++ "\n")
let formula = e
putStrLn ("** Formula AST: " ++ show formula ++ "\n")
-- Check if the formula is satisfiable. If it is, print the instantiation of its free
-- variables that would make it true:
let (result,model) = unsafeIsSatisfiable env decls formula
case result of
Unsat -> putStrLn "** UNSATISFIABLE."
Undef -> putStrLn "** Unable to decide the satisfiablity."
_ -> do
putStrLn "** SATISFIABLE."
case model of
Just m -> do { putStrLn "** Model:" ; s <- evalZ3 (modelToString m) ; putStrLn s }
_ -> return ()
where
-- | Parse a Java source file, and extracts the necessary information from the compilation unit
parseJava :: FilePath -> IO CompilationUnit
parseJava s = do
-- Get the source code
source <- readFile s
-- Parse the source code
case parser compilationUnit source of
Left parseError -> error (show parseError)
Right compUnit -> return compUnit