Skip to content

Commit 8583a6d

Browse files
committed
Support ghost values in all language backends
This patch: * Factors out the machinery needed to track ghost state 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 40b9966 commit 8583a6d

34 files changed

+467
-351
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 language 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
@@ -641,8 +641,8 @@ substMethodSpec sc sm ms = do
641641
MS.SetupCond_Equal loc <$> goSetupValue sv1 <*> goSetupValue sv2
642642
goSetupCondition (MS.SetupCond_Pred loc tt) =
643643
MS.SetupCond_Pred loc <$> goTypedTerm tt
644-
goSetupCondition (MS.SetupCond_Ghost b loc gg tt) =
645-
MS.SetupCond_Ghost b loc gg <$> goTypedTerm tt
644+
goSetupCondition (MS.SetupCond_Ghost loc gg tt) =
645+
MS.SetupCond_Ghost loc gg <$> goTypedTerm tt
646646

647647
goSetupSlice (MirSetupSliceRaw ref len) =
648648
MirSetupSliceRaw <$> goSetupValue ref <*> goSetupValue len

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ condTerm sc (MS.SetupCond_Pred md tt) = do
581581
sub <- use MS.termSub
582582
t' <- liftIO $ SAW.scInstantiateExt sc sub $ SAW.ttTerm tt
583583
return (md, t')
584-
condTerm _ (MS.SetupCond_Ghost _ _ _ _) = do
584+
condTerm _ (MS.SetupCond_Ghost _ _ _) = do
585585
error $ "learnCond: SetupCond_Ghost is not supported"
586586

587587

doc/manual/manual.md

+6-5
Original file line numberDiff line numberDiff line change
@@ -3544,14 +3544,15 @@ with the following function:
35443544
35453545
Ghost state variables do not initially have any particluar type, and can
35463546
store data of any type. Given an existing ghost variable the following
3547-
function can be used to specify its value:
3547+
functions can be used to specify its value:
35483548
35493549
* `llvm_ghost_value : Ghost -> Term -> LLVMSetup ()`
3550+
* `jvm_ghost_value : Ghost -> Term -> JVMSetup ()`
3551+
* `mir_ghost_value : Ghost -> Term -> MIRSetup ()`
35503552
3551-
Currently, this function can only be used for LLVM verification, though
3552-
that will likely be generalized in the future. It can be used in either
3553-
the pre state or the post state, to specify the value of ghost state
3554-
either before or after the execution of the function, respectively.
3553+
These can be used in either the pre state or the post state, to specify the
3554+
value of ghost state either before or after the execution of the function,
3555+
respectively.
35553556
35563557
## An Extended Example
35573558

doc/manual/manual.pdf

-16 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
@@ -35,6 +35,8 @@
3535
* Add `"slice"` and `"slice range"` `setup value`s representing slices in MIR
3636
verification. Attempting to use these in LLVM or JVM verification will raise
3737
an error.
38+
* The `SAW/create ghost variable` command and the associated
39+
`ghost variable value` value are now supported with JVM and MIR verification.
3840

3941
## 1.0.0 -- 2023-06-26
4042

saw-remote-api/python/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
variable. This function is currently only supported with LLVM and MIR
3939
verification, and using this function with JVM verification will raise an
4040
error.
41+
* The `create_ghost_variable()` and `ghost_value()` functions are now supported
42+
with JVM and MIR verification.
4143

4244
## 1.0.1 -- YYYY-MM-DD
4345

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
@@ -31,6 +31,7 @@ import SAWScript.Crucible.MIR.Builtins
3131
mir_fresh_expanded_value,
3232
mir_fresh_var,
3333
mir_execute_func,
34+
mir_ghost_value,
3435
mir_load_module,
3536
mir_points_to,
3637
mir_postcond,
@@ -58,10 +59,11 @@ import SAWServer.CryptolExpression (CryptolModuleException(..), getTypedTermOfCE
5859
import SAWServer.Data.Contract
5960
( PointsTo(PointsTo),
6061
PointsToBitfield,
62+
GhostValue(GhostValue),
6163
Allocated(Allocated),
6264
ContractVar(ContractVar),
63-
Contract(preVars, preConds, preAllocated, prePointsTos, prePointsToBitfields,
64-
argumentVals, postVars, postConds, postAllocated, postPointsTos, postPointsToBitfields,
65+
Contract(preVars, preConds, preAllocated, preGhostValues, prePointsTos, prePointsToBitfields,
66+
argumentVals, postVars, postConds, postAllocated, postGhostValues, postPointsTos, postPointsToBitfields,
6567
returnVal) )
6668
import SAWServer.Data.MIRType (JSONMIRType, mirType)
6769
import SAWServer.Exceptions ( notAtTopLevel )
@@ -74,24 +76,25 @@ newtype ServerSetupVal = Val (MS.SetupValue MIR)
7476
compileMIRContract ::
7577
(FilePath -> IO ByteString) ->
7678
BuiltinContext ->
79+
Map ServerName MS.GhostGlobal ->
7780
CryptolEnv ->
7881
SAWEnv ->
7982
Contract JSONMIRType (P.Expr P.PName) ->
8083
MIRSetupM ()
81-
compileMIRContract fileReader bic cenv0 sawenv c =
84+
compileMIRContract fileReader bic ghostEnv cenv0 sawenv c =
8285
do allocsPre <- mapM setupAlloc (preAllocated c)
8386
(envPre, cenvPre) <- setupState allocsPre (Map.empty, cenv0) (preVars c)
8487
mapM_ (\p -> getTypedTerm cenvPre p >>= mir_precond) (preConds c)
8588
mapM_ (setupPointsTo (envPre, cenvPre)) (prePointsTos c)
8689
mapM_ setupPointsToBitfields (prePointsToBitfields c)
87-
--mapM_ (setupGhostValue ghostEnv cenvPre) (preGhostValues c)
90+
mapM_ (setupGhostValue ghostEnv cenvPre) (preGhostValues c)
8891
traverse (getSetupVal (envPre, cenvPre)) (argumentVals c) >>= mir_execute_func
8992
allocsPost <- mapM setupAlloc (postAllocated c)
9093
(envPost, cenvPost) <- setupState (allocsPre ++ allocsPost) (envPre, cenvPre) (postVars c)
9194
mapM_ (\p -> getTypedTerm cenvPost p >>= mir_postcond) (postConds c)
9295
mapM_ (setupPointsTo (envPost, cenvPost)) (postPointsTos c)
9396
mapM_ setupPointsToBitfields (postPointsToBitfields c)
94-
--mapM_ (setupGhostValue ghostEnv cenvPost) (postGhostValues c)
97+
mapM_ (setupGhostValue ghostEnv cenvPost) (postGhostValues c)
9598
case returnVal c of
9699
Just v -> getSetupVal (envPost, cenvPost) v >>= mir_return
97100
Nothing -> return ()
@@ -136,7 +139,10 @@ compileMIRContract fileReader bic cenv0 sawenv c =
136139
setupPointsToBitfields _ =
137140
MIRSetupM $ fail "Points-to-bitfield not supported in the MIR API."
138141

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

141147
resolve :: Map ServerName a -> ServerName -> MIRSetupM a
142148
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)