Skip to content

Commit c6ca304

Browse files
committed
Support ghost values in all language backends
This patch: * Factors out the machinery needed to track ghost state into into the `SAWScript.Crucible.Common.Override` and `SAWScript.Builtins` modules. Nothing about ghost state is specific to any language backend, so it deserves to live in a non-LLVM–specific location. * Adds `jvm_ghost_value` and `mir_ghost_value` commands, which behave exactly like the LLVM backend's `llvm_ghost_value` command does, but for the JVM and MIR backends, respectively. * Adds a `test_mir_ghost` test case in SAWScript and the remote API to ensure that everything works as expected. Fixes #1929.
1 parent 310b39f commit c6ca304

34 files changed

+468
-352
lines changed

CHANGES.md

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@
1717
generating LLVM setup scripts for Cryptol FFI functions with the
1818
`llvm_ffi_setup` command. For more information, see the [manual](https://github.com/GaloisInc/saw-script/blob/master/doc/manual/manual.md#verifying-cryptol-ffi-functions).
1919

20+
* Ghost state is now supported with the JVM and MIR languaage backends:
21+
* The `llvm_declare_ghost_state` command is now deprecated in favor of the
22+
new `declare_ghost_state` command, as nothing about this command is
23+
LLVM-specific.
24+
* Add `jvm_ghost_value` and `mir_ghost_value` commands in addition to the
25+
existing `llvm_ghost_value` command.
26+
2027
# Version 1.0 -- 2023-06-26
2128

2229
## New Features

crucible-mir-comp/src/Mir/Compositional/Builder.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -640,8 +640,8 @@ substMethodSpec sc sm ms = do
640640
MS.SetupCond_Equal loc <$> goSetupValue sv1 <*> goSetupValue sv2
641641
goSetupCondition (MS.SetupCond_Pred loc tt) =
642642
MS.SetupCond_Pred loc <$> goTypedTerm tt
643-
goSetupCondition (MS.SetupCond_Ghost b loc gg tt) =
644-
MS.SetupCond_Ghost b loc gg <$> goTypedTerm tt
643+
goSetupCondition (MS.SetupCond_Ghost loc gg tt) =
644+
MS.SetupCond_Ghost loc gg <$> goTypedTerm tt
645645

646646
goTypedTerm tt = do
647647
term' <- goTerm $ SAW.ttTerm tt

crucible-mir-comp/src/Mir/Compositional/Override.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ condTerm sc (MS.SetupCond_Pred md tt) = do
571571
sub <- use MS.termSub
572572
t' <- liftIO $ SAW.scInstantiateExt sc sub $ SAW.ttTerm tt
573573
return (md, t')
574-
condTerm _ (MS.SetupCond_Ghost _ _ _ _) = do
574+
condTerm _ (MS.SetupCond_Ghost _ _ _) = do
575575
error $ "learnCond: SetupCond_Ghost is not supported"
576576

577577

doc/manual/manual.md

+6-5
Original file line numberDiff line numberDiff line change
@@ -3142,14 +3142,15 @@ with the following function:
31423142
31433143
Ghost state variables do not initially have any particluar type, and can
31443144
store data of any type. Given an existing ghost variable the following
3145-
function can be used to specify its value:
3145+
functions can be used to specify its value:
31463146
31473147
* `llvm_ghost_value : Ghost -> Term -> LLVMSetup ()`
3148+
* `jvm_ghost_value : Ghost -> Term -> JVMSetup ()`
3149+
* `mir_ghost_value : Ghost -> Term -> MIRSetup ()`
31483150
3149-
Currently, this function can only be used for LLVM verification, though
3150-
that will likely be generalized in the future. It can be used in either
3151-
the pre state or the post state, to specify the value of ghost state
3152-
either before or after the execution of the function, respectively.
3151+
These can be used in either the pre state or the post state, to specify the
3152+
value of ghost state either before or after the execution of the function,
3153+
respectively.
31533154
31543155
## An Extended Example
31553156

doc/manual/manual.pdf

-65 Bytes
Binary file not shown.

intTests/test_mir_ghost/Makefile

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
all: test.linked-mir.json
2+
3+
test.linked-mir.json: test.rs
4+
saw-rustc $<
5+
$(MAKE) remove-unused-build-artifacts
6+
7+
.PHONY: remove-unused-build-artifacts
8+
remove-unused-build-artifacts:
9+
rm -f test libtest.mir libtest.rlib
10+
11+
.PHONY: clean
12+
clean: remove-unused-build-artifacts
13+
rm -f test.linked-mir.json

intTests/test_mir_ghost/test.linked-mir.json

+1
Large diffs are not rendered by default.

intTests/test_mir_ghost/test.rs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
pub fn next() -> u32 {
2+
unimplemented!("This should be overridden")
3+
}
4+
5+
pub fn example() -> u32 {
6+
next();
7+
next();
8+
next()
9+
}

intTests/test_mir_ghost/test.saw

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
enable_experimental;
2+
3+
let next_spec counter = do {
4+
n <- mir_fresh_var "n" mir_u32;
5+
mir_ghost_value counter n;
6+
7+
mir_execute_func [];
8+
9+
mir_ghost_value counter {{n+1}};
10+
mir_return (mir_term {{n}});
11+
};
12+
13+
let example_spec counter = do {
14+
n <- mir_fresh_var "nm" mir_u32;
15+
mir_precond {{n < 2}};
16+
mir_ghost_value counter n;
17+
18+
mir_execute_func [];
19+
20+
mir_ghost_value counter {{n+3}};
21+
mir_return (mir_term {{n+2}});
22+
};
23+
24+
counter <- declare_ghost_state "ctr";
25+
m <- mir_load_module "test.linked-mir.json";
26+
27+
next <- mir_unsafe_assume_spec m "test::next" (next_spec counter);
28+
mir_verify m "test::example" [next] false (example_spec counter) z3;

intTests/test_mir_ghost/test.sh

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
set -e
2+
3+
$SAW test.saw

saw-remote-api/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
* The API for `"struct"` `setup value`s now has a `"MIR ADT"` field. For
2828
MIR verification, this field is required. For LLVM and JVM verification,
2929
this field must be `null`, or else an error will be raised.
30+
* The `SAW/create ghost variable` command and the associated
31+
`ghost variable value` value are now supported with JVM and MIR verification.
3032

3133
## 1.0.0 -- 2023-06-26
3234

saw-remote-api/python/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
* Add a `proclaim_f` function. This behaves like the `proclaim` function, except
3131
that it takes a `cry_f`-style format string as an argument. That is,
3232
`proclaim_f(...)` is equivalent to `proclaim(cry_f(...))`.
33+
* The `create_ghost_variable()` and `ghost_value()` functions are now supported
34+
with JVM and MIR verification.
3335

3436
## 1.0.1 -- YYYY-MM-DD
3537

saw-remote-api/python/tests/saw/test-files/mir_ghost.linked-mir.json

+1
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
pub fn next() -> u32 {
2+
unimplemented!("This should be overridden")
3+
}
4+
5+
pub fn example() -> u32 {
6+
next();
7+
next();
8+
next()
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import unittest
2+
from pathlib import Path
3+
4+
from saw_client import *
5+
from saw_client.crucible import cry_f
6+
from saw_client.mir import Contract, GhostVariable, u32
7+
8+
9+
class NextContract(Contract):
10+
def __init__(self, counter: GhostVariable) -> None:
11+
super().__init__()
12+
self.counter = counter
13+
14+
def specification(self) -> None:
15+
n = self.fresh_var(u32, 'n')
16+
self.ghost_value(self.counter, n)
17+
18+
self.execute_func()
19+
20+
self.ghost_value(self.counter, cry_f('{n} + 1'))
21+
self.returns(n)
22+
23+
24+
class ExampleContract(Contract):
25+
def __init__(self, counter: GhostVariable) -> None:
26+
super().__init__()
27+
self.counter = counter
28+
29+
def specification(self) -> None:
30+
n = self.fresh_var(u32, 'n')
31+
self.precondition_f('{n} < 2')
32+
self.ghost_value(self.counter, n)
33+
34+
self.execute_func()
35+
self.ghost_value(self.counter, cry_f('{n} + 3'))
36+
self.returns_f('{n} + 2')
37+
38+
39+
class MIRGhostTest(unittest.TestCase):
40+
def test_mir_ghost(self):
41+
connect(reset_server=True)
42+
if __name__ == "__main__": view(LogResults())
43+
json_name = str(Path('tests', 'saw', 'test-files', 'mir_ghost.linked-mir.json'))
44+
mod = mir_load_module(json_name)
45+
46+
counter = create_ghost_variable('ctr');
47+
next_ov = mir_assume(mod, 'mir_ghost::next', NextContract(counter))
48+
example_result = mir_verify(mod, 'mir_ghost::example', ExampleContract(counter), lemmas=[next_ov])
49+
self.assertIs(example_result.is_success(), True)
50+
51+
52+
if __name__ == "__main__":
53+
unittest.main()

saw-remote-api/src/SAWServer/JVMCrucibleSetup.hs

+14-8
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import qualified Data.Map as Map
2626
import qualified Cryptol.Parser.AST as P
2727
import Cryptol.Utils.Ident (mkIdent)
2828
import qualified Lang.Crucible.JVM as CJ
29-
import SAWScript.Crucible.Common.MethodSpec as MS (SetupValue(..))
29+
import qualified SAWScript.Crucible.Common.MethodSpec as MS
3030
import SAWScript.Crucible.JVM.Builtins
3131
( jvm_alloc_array,
3232
jvm_alloc_object,
@@ -35,6 +35,7 @@ import SAWScript.Crucible.JVM.Builtins
3535
jvm_static_field_is,
3636
jvm_execute_func,
3737
jvm_fresh_var,
38+
jvm_ghost_value,
3839
jvm_postcond,
3940
jvm_precond,
4041
jvm_return )
@@ -56,10 +57,11 @@ import SAWServer
5657
import SAWServer.Data.Contract
5758
( PointsTo(PointsTo),
5859
PointsToBitfield,
60+
GhostValue(GhostValue),
5961
Allocated(Allocated),
6062
ContractVar(ContractVar),
61-
Contract(preVars, preConds, preAllocated, prePointsTos, prePointsToBitfields,
62-
argumentVals, postVars, postConds, postAllocated, postPointsTos, postPointsToBitfields,
63+
Contract(preVars, preConds, preAllocated, preGhostValues, prePointsTos, prePointsToBitfields,
64+
argumentVals, postVars, postConds, postAllocated, postGhostValues, postPointsTos, postPointsToBitfields,
6365
returnVal) )
6466
import SAWServer.Data.SetupValue ()
6567
import SAWServer.CryptolExpression (CryptolModuleException(..), getTypedTermOfCExp)
@@ -82,28 +84,29 @@ instance Doc.DescribedMethod StartJVMSetupParams OK where
8284
]
8385
resultFieldDescription = []
8486

85-
newtype ServerSetupVal = Val (SetupValue CJ.JVM)
87+
newtype ServerSetupVal = Val (MS.SetupValue CJ.JVM)
8688

8789
compileJVMContract ::
8890
(FilePath -> IO ByteString) ->
8991
BuiltinContext ->
92+
Map ServerName MS.GhostGlobal ->
9093
CryptolEnv ->
9194
Contract JavaType (P.Expr P.PName) ->
9295
JVMSetupM ()
93-
compileJVMContract fileReader bic cenv0 c =
96+
compileJVMContract fileReader bic ghostEnv cenv0 c =
9497
do allocsPre <- mapM setupAlloc (preAllocated c)
9598
(envPre, cenvPre) <- setupState allocsPre (Map.empty, cenv0) (preVars c)
9699
mapM_ (\p -> getTypedTerm cenvPre p >>= jvm_precond) (preConds c)
97100
mapM_ (setupPointsTo (envPre, cenvPre)) (prePointsTos c)
98101
mapM_ setupPointsToBitfields (prePointsToBitfields c)
99-
--mapM_ (setupGhostValue ghostEnv cenvPre) (preGhostValues c)
102+
mapM_ (setupGhostValue ghostEnv cenvPre) (preGhostValues c)
100103
traverse (getSetupVal (envPre, cenvPre)) (argumentVals c) >>= jvm_execute_func
101104
allocsPost <- mapM setupAlloc (postAllocated c)
102105
(envPost, cenvPost) <- setupState (allocsPre ++ allocsPost) (envPre, cenvPre) (postVars c)
103106
mapM_ (\p -> getTypedTerm cenvPost p >>= jvm_postcond) (postConds c)
104107
mapM_ (setupPointsTo (envPost, cenvPost)) (postPointsTos c)
105108
mapM_ setupPointsToBitfields (postPointsToBitfields c)
106-
--mapM_ (setupGhostValue ghostEnv cenvPost) (postGhostValues c)
109+
mapM_ (setupGhostValue ghostEnv cenvPost) (postGhostValues c)
107110
case returnVal c of
108111
Just v -> getSetupVal (envPost, cenvPost) v >>= jvm_return
109112
Nothing -> return ()
@@ -149,7 +152,10 @@ compileJVMContract fileReader bic cenv0 c =
149152
setupPointsToBitfields _ =
150153
JVMSetupM $ fail "Points-to-bitfield not supported in JVM API."
151154

152-
--setupGhostValue _ _ _ = fail "Ghost values not supported yet in JVM API."
155+
setupGhostValue genv cenv (GhostValue serverName e) =
156+
do g <- resolve genv serverName
157+
t <- getTypedTerm cenv e
158+
jvm_ghost_value g t
153159

154160
resolve :: Map ServerName a -> ServerName -> JVMSetupM a
155161
resolve env name =

saw-remote-api/src/SAWServer/JVMVerify.hs

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module SAWServer.JVMVerify
99

1010
import Prelude hiding (mod)
1111
import Control.Lens
12+
import qualified Data.Map as Map
1213

1314
import SAWScript.Crucible.JVM.Builtins
1415
( jvm_unsafe_assume_spec, jvm_verify )
@@ -26,6 +27,7 @@ import SAWServer
2627
pushTask,
2728
dropTask,
2829
setServerVal,
30+
getGhosts,
2931
getJVMClass,
3032
getJVMMethodSpecIR )
3133
import SAWServer.CryptolExpression (getCryptolExpr)
@@ -51,7 +53,8 @@ jvmVerifyAssume mode (VerifyParams className fun lemmaNames checkSat contract sc
5153
let bic = view sawBIC state
5254
cenv = rwCryptol (view sawTopLevelRW state)
5355
fileReader <- Argo.getFileReader
54-
setup <- compileJVMContract fileReader bic cenv <$> traverse getCryptolExpr contract
56+
ghostEnv <- Map.fromList <$> getGhosts
57+
setup <- compileJVMContract fileReader bic ghostEnv cenv <$> traverse getCryptolExpr contract
5558
res <- case mode of
5659
VerifyContract -> do
5760
lemmas <- mapM getJVMMethodSpecIR lemmaNames

saw-remote-api/src/SAWServer/MIRCrucibleSetup.hs

+12-6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import SAWScript.Crucible.MIR.Builtins
3030
mir_alloc_mut,
3131
mir_fresh_var,
3232
mir_execute_func,
33+
mir_ghost_value,
3334
mir_load_module,
3435
mir_points_to,
3536
mir_postcond,
@@ -55,10 +56,11 @@ import SAWServer.CryptolExpression (CryptolModuleException(..), getTypedTermOfCE
5556
import SAWServer.Data.Contract
5657
( PointsTo(PointsTo),
5758
PointsToBitfield,
59+
GhostValue(GhostValue),
5860
Allocated(Allocated),
5961
ContractVar(ContractVar),
60-
Contract(preVars, preConds, preAllocated, prePointsTos, prePointsToBitfields,
61-
argumentVals, postVars, postConds, postAllocated, postPointsTos, postPointsToBitfields,
62+
Contract(preVars, preConds, preAllocated, preGhostValues, prePointsTos, prePointsToBitfields,
63+
argumentVals, postVars, postConds, postAllocated, postGhostValues, postPointsTos, postPointsToBitfields,
6264
returnVal) )
6365
import SAWServer.Data.MIRType (JSONMIRType, mirType)
6466
import SAWServer.Exceptions ( notAtTopLevel )
@@ -71,24 +73,25 @@ newtype ServerSetupVal = Val (MS.SetupValue MIR)
7173
compileMIRContract ::
7274
(FilePath -> IO ByteString) ->
7375
BuiltinContext ->
76+
Map ServerName MS.GhostGlobal ->
7477
CryptolEnv ->
7578
SAWEnv ->
7679
Contract JSONMIRType (P.Expr P.PName) ->
7780
MIRSetupM ()
78-
compileMIRContract fileReader bic cenv0 sawenv c =
81+
compileMIRContract fileReader bic ghostEnv cenv0 sawenv c =
7982
do allocsPre <- mapM setupAlloc (preAllocated c)
8083
(envPre, cenvPre) <- setupState allocsPre (Map.empty, cenv0) (preVars c)
8184
mapM_ (\p -> getTypedTerm cenvPre p >>= mir_precond) (preConds c)
8285
mapM_ (setupPointsTo (envPre, cenvPre)) (prePointsTos c)
8386
mapM_ setupPointsToBitfields (prePointsToBitfields c)
84-
--mapM_ (setupGhostValue ghostEnv cenvPre) (preGhostValues c)
87+
mapM_ (setupGhostValue ghostEnv cenvPre) (preGhostValues c)
8588
traverse (getSetupVal (envPre, cenvPre)) (argumentVals c) >>= mir_execute_func
8689
allocsPost <- mapM setupAlloc (postAllocated c)
8790
(envPost, cenvPost) <- setupState (allocsPre ++ allocsPost) (envPre, cenvPre) (postVars c)
8891
mapM_ (\p -> getTypedTerm cenvPost p >>= mir_postcond) (postConds c)
8992
mapM_ (setupPointsTo (envPost, cenvPost)) (postPointsTos c)
9093
mapM_ setupPointsToBitfields (postPointsToBitfields c)
91-
--mapM_ (setupGhostValue ghostEnv cenvPost) (postGhostValues c)
94+
mapM_ (setupGhostValue ghostEnv cenvPost) (postGhostValues c)
9295
case returnVal c of
9396
Just v -> getSetupVal (envPost, cenvPost) v >>= mir_return
9497
Nothing -> return ()
@@ -133,7 +136,10 @@ compileMIRContract fileReader bic cenv0 sawenv c =
133136
setupPointsToBitfields _ =
134137
MIRSetupM $ fail "Points-to-bitfield not supported in the MIR API."
135138

136-
--setupGhostValue _ _ _ = fail "Ghost values not supported yet in the MIR API."
139+
setupGhostValue genv cenv (GhostValue serverName e) =
140+
do g <- resolve genv serverName
141+
t <- getTypedTerm cenv e
142+
mir_ghost_value g t
137143

138144
resolve :: Map ServerName a -> ServerName -> MIRSetupM a
139145
resolve env name =

saw-remote-api/src/SAWServer/MIRVerify.hs

+4-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module SAWServer.MIRVerify
99

1010
import Prelude hiding (mod)
1111
import Control.Lens
12+
import qualified Data.Map as Map
1213

1314
import SAWScript.Crucible.MIR.Builtins
1415
( mir_unsafe_assume_spec, mir_verify )
@@ -26,6 +27,7 @@ import SAWServer
2627
pushTask,
2728
dropTask,
2829
setServerVal,
30+
getGhosts,
2931
getMIRModule,
3032
getMIRMethodSpecIR )
3133
import SAWServer.CryptolExpression (getCryptolExpr)
@@ -53,7 +55,8 @@ mirVerifyAssume mode (VerifyParams modName fun lemmaNames checkSat contract scri
5355
cenv = rwCryptol (view sawTopLevelRW state)
5456
sawenv = view sawEnv state
5557
fileReader <- Argo.getFileReader
56-
setup <- compileMIRContract fileReader bic cenv sawenv <$>
58+
ghostEnv <- Map.fromList <$> getGhosts
59+
setup <- compileMIRContract fileReader bic ghostEnv cenv sawenv <$>
5760
traverse getCryptolExpr contract
5861
res <- case mode of
5962
VerifyContract -> do

0 commit comments

Comments
 (0)