Skip to content

Commit 6583923

Browse files
committed
Implement mir_load_module
This implements an experimental `mir_load_module` command, the first SAW command dedicated to MIR (Rust) code. I have added a basic test case to kick the tires and ensure that everything works as you would expect. You can't do much with the resulting MIR module yet (besides printing it in the SAW REPL), but more functionality for verifying the MIR code will come in future patches. Note that in order to produce a MIR JSON file suitable for SAW's needs, you must build your Rust code with one of the two `mir-json` tools added in GaloisInc/mir-json#41. This checks off one box in #1859.
1 parent 5df5615 commit 6583923

File tree

18 files changed

+268
-11
lines changed

18 files changed

+268
-11
lines changed

CHANGES.md

+4
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@
9595
on Windows at this time due to a downstream issue with CVC5 1.0.4 and
9696
earlier.)
9797

98+
* Add experimental support for verifying Rust programs. For more information,
99+
see the `mir_*` commands documented in the
100+
[SAW manual](https://github.com/GaloisInc/saw-script/blob/master/doc/manual/manual.md).
101+
98102
# Version 0.9
99103

100104
## New Features

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ This repository contains the code for SAWScript, the scripting
66
language that forms the primary user interface to the Software
77
Analysis Workbench (SAW). It provides the ability to reason about
88
formal models describing the denotation of programs written in
9-
languages such as C, Java, and Cryptol.
9+
languages such as C, Java, and Cryptol. It also provides experimental,
10+
incomplete support for the Rust language.
1011

1112
## Documentation
1213

doc/manual/manual.md

+71-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ mathematical models of the computational behavior of software,
55
transforming these models, and proving properties about them.
66

77
SAW can currently construct models of a subset of programs written in
8-
Cryptol, LLVM (and therefore C), and JVM (and therefore Java). The
8+
Cryptol, LLVM (and therefore C), and JVM (and therefore Java). SAW also has
9+
experimental, incomplete support for MIR (and therefore Rust). The
910
models take the form of typed functional programs, so in a sense SAW can
1011
be considered a translator from imperative programs to their functional
1112
equivalents. Various external proof tools, including a variety of SAT
@@ -176,7 +177,7 @@ Cryptol, Haskell and ML. In particular, functions are applied by
176177
writing them next to their arguments rather than by using parentheses
177178
and commas. Rather than writing `f(x, y)`, write `f x y`.
178179

179-
Comments are written as in C and Java (among many other languages). All
180+
Comments are written as in C, Java, and Rust (among many other languages). All
180181
text from `//` until the end of a line is ignored. Additionally, all
181182
text between `/*` and `*/` is ignored, regardless of whether the line
182183
ends.
@@ -1568,6 +1569,8 @@ analyze JVM and LLVM programs.
15681569

15691570
The first step in analyzing any code is to load it into the system.
15701571

1572+
## Loading LLVM
1573+
15711574
To load LLVM code, simply provide the location of a valid bitcode file
15721575
to the `llvm_load_module` function.
15731576

@@ -1583,6 +1586,8 @@ most likely case of incompleteness. We aim to support every version
15831586
after 3.5, however, so report any parsing failures as [on
15841587
GitHub](https://github.com/GaloisInc/saw-script/issues).
15851588

1589+
## Loading Java
1590+
15861591
Loading Java code is slightly more complex, because of the more
15871592
structured nature of Java packages. First, when running `saw`, three flags
15881593
control where to look for classes:
@@ -1623,12 +1628,28 @@ unresolved issues in verifying code involving classes such as `String`. For
16231628
more information on these issues, refer to
16241629
[this GitHub issue](https://github.com/GaloisInc/crucible/issues/641).
16251630

1631+
## Loading MIR
1632+
1633+
To load a piece of Rust code, first compile it to a MIR JSON file, as described
1634+
in [this section](#compiling-mir), and then provide the location of the JSON
1635+
file to the `mir_load_module` function:
1636+
1637+
* `mir_load_module : String -> TopLevel MIRModule`
1638+
1639+
SAW currently supports Rust code that can be built with a [March 22, 2020 Rust
1640+
nightly](https://static.rust-lang.org/dist/2020-03-22/). If you encounter a
1641+
Rust feature that SAW does not support, please report it [on
1642+
GitHub](https://github.com/GaloisInc/saw-script/issues).
1643+
16261644
## Notes on Compiling Code for SAW
16271645

1628-
SAW will generally be able to load arbitrary LLVM bitcode and JVM
1629-
bytecode files, but several guidelines can help make verification
1630-
easier or more likely to succeed. For generating LLVM with `clang`, it
1631-
can be helpful to:
1646+
SAW will generally be able to load arbitrary LLVM bitcode, JVM bytecode, and
1647+
MIR JSON files, but several guidelines can help make verification easier or
1648+
more likely to succeed.
1649+
1650+
### Compiling LLVM
1651+
1652+
For generating LLVM with `clang`, it can be helpful to:
16321653

16331654
* Turn on debugging symbols with `-g` so that SAW can find source
16341655
locations of functions, names of variables, etc.
@@ -1659,11 +1680,54 @@ behavior, and SAW currently does not have built in support for these
16591680
functions (though you could manually create overrides for them in a
16601681
verification script).
16611682

1683+
[^1]: https://clang.llvm.org/docs/UsersManual.html#controlling-code-generation
1684+
1685+
### Compiling Java
1686+
16621687
For Java, the only compilation flag that tends to be valuable is `-g` to
16631688
retain information about the names of function arguments and local
16641689
variables.
16651690

1666-
[^1]: https://clang.llvm.org/docs/UsersManual.html#controlling-code-generation
1691+
### Compiling MIR
1692+
1693+
In order to verify Rust code, SAW analyzes Rust's MIR (mid-level intermediate
1694+
representation) language. In particular, SAW analyzes a particular form of MIR
1695+
that the [`mir-json`](https://github.com/GaloisInc/mir-json) tool produces. You
1696+
will need to intall `mir-json` and run it on Rust code in order to produce MIR
1697+
JSON files that SAW can load (see [this section](#loading-mir)).
1698+
1699+
For `cargo`-based projects, `mir-json` provides a `cargo` subcommand called
1700+
`cargo saw-build` that builds a JSON file suitable for use with SAW. `cargo
1701+
saw-build` integrates directly with `cargo`, so you can pass flags to it like
1702+
any other `cargo` subcommand. For example:
1703+
1704+
```
1705+
$ export SAW_RUST_LIBRARY_PATH=<...>
1706+
$ cargo saw-build <other cargo flags>
1707+
<snip>
1708+
linking 11 mir files into <...>/example-364cf2df365c7055.linked-mir.json
1709+
<snip>
1710+
```
1711+
1712+
Note that:
1713+
1714+
* The full output of `cargo saw-build` here is omitted. The important part is
1715+
the `.linked-mir.json` file that appears after `linking X mir files into`, as
1716+
that is the JSON file that must be loaded with SAW.
1717+
* `SAW_RUST_LIBRARY_PATH` should point to the the MIR JSON files for the Rust
1718+
standard library.
1719+
1720+
`mir-json` also supports compiling individual `.rs` files through `mir-json`'s
1721+
`saw-rustc` command. As the name suggests, it accepts all of the flags that
1722+
`rustc` accepts. For example:
1723+
1724+
```
1725+
$ export SAW_RUST_LIBRARY_PATH=<...>
1726+
$ saw-rustc example.rs <other rustc flags>
1727+
<snip>
1728+
linking 11 mir files into <...>/example.linked-mir.json
1729+
<snip>
1730+
```
16671731

16681732
## Notes on C++ Analysis
16691733

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
all: test.json
2+
3+
test.linked-mir.json: test.rs
4+
saw-rustc $<
5+
6+
.PHONY: clean
7+
clean:
8+
rm -f test.linked-mir.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"fns":[{"abi":{"kind":"Rust"},"args":[],"body":{"blocks":[{"block":{"data":[{"kind":"Assign","lhs":{"data":[],"var":{"is_zst":false,"mut":{"kind":"Mut"},"name":"_0","ty":"ty::i32"}},"pos":"test.rs:1:23: 1:25","rhs":{"kind":"Use","usevar":{"data":{"rendered":{"kind":"int","size":4,"val":"42"},"ty":"ty::i32"},"kind":"Constant"}}}],"terminator":{"kind":"Return","pos":"test.rs:1:27: 1:27"}},"blockid":"bb0"}],"vars":[{"is_zst":false,"mut":{"kind":"Mut"},"name":"_0","ty":"ty::i32"}]},"name":"test/3a1fbbbh::foo[0]","return_ty":"ty::i32","spread_arg":null}],"adts":[],"statics":[],"vtables":[],"traits":[],"intrinsics":[{"inst":{"def_id":"test/3a1fbbbh::foo[0]","kind":"Item","substs":[]},"name":"test/3a1fbbbh::foo[0]"}],"tys":[{"name":"ty::i32","ty":{"intkind":{"kind":"I32"},"kind":"Int"}}],"roots":["test/3a1fbbbh::foo[0]"]}

intTests/test_mir_load_module/test.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub fn foo() -> i32 { 42 }
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
enable_experimental;
2+
3+
m <- mir_load_module "test.linked-mir.json";
4+
print m;

intTests/test_mir_load_module/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/docs/SAW.rst

+26
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,32 @@ No return fields
419419

420420

421421

422+
SAW/MIR/load module (command)
423+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
424+
425+
Load the specified MIR module.
426+
427+
Parameter fields
428+
++++++++++++++++
429+
430+
431+
``name``
432+
The name to refer to the loaded module by later.
433+
434+
435+
436+
``module name``
437+
The file containing the MIR JSON file to load.
438+
439+
440+
441+
Return fields
442+
+++++++++++++
443+
444+
No return fields
445+
446+
447+
422448
SAW/Yosys/import (command)
423449
~~~~~~~~~~~~~~~~~~~~~~~~~~
424450

saw-remote-api/saw-remote-api.cabal

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ common deps
4545
cryptol-saw-core,
4646
crucible,
4747
crucible-jvm,
48+
crucible-mir,
4849
cryptonite,
4950
cryptonite-conduit,
5051
directory,
@@ -81,6 +82,7 @@ library
8182
SAWServer.JVMVerify,
8283
SAWServer.LLVMCrucibleSetup,
8384
SAWServer.LLVMVerify,
85+
SAWServer.MIRCrucibleSetup,
8486
SAWServer.NoParams,
8587
SAWServer.OK,
8688
SAWServer.ProofScript,

saw-remote-api/saw-remote-api/Main.hs

+7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import SAWServer.LLVMVerify
3636
llvmAssume,
3737
llvmVerifyX86Descr,
3838
llvmVerifyX86 )
39+
import SAWServer.MIRCrucibleSetup
40+
( mirLoadModuleDescr, mirLoadModule )
3941
import SAWServer.ProofScript
4042
( makeSimpsetDescr, makeSimpset, proveDescr, prove )
4143
import SAWServer.SaveTerm ( saveTermDescr, saveTerm )
@@ -114,6 +116,11 @@ sawMethods =
114116
"SAW/LLVM/assume"
115117
llvmAssumeDescr
116118
llvmAssume
119+
-- MIR
120+
, Argo.command
121+
"SAW/MIR/load module"
122+
mirLoadModuleDescr
123+
mirLoadModule
117124
-- Yosys
118125
, Argo.command
119126
"SAW/Yosys/import"

saw-remote-api/src/SAWServer.hs

+16-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import qualified Data.AIG as AIG
3838
import qualified Lang.Crucible.FunctionHandle as Crucible (HandleAllocator, newHandleAllocator)
3939
import qualified Lang.Crucible.JVM as CJ
4040
import qualified Lang.JVM.Codebase as JSS
41+
import Mir.Generator (RustModule)
4142
--import qualified Verifier.SAW.CryptolEnv as CryptolEnv
4243
import Verifier.SAW.Module (emptyModule)
4344
import Verifier.SAW.SharedTerm (mkSharedContext, scLoadModule)
@@ -78,7 +79,8 @@ import SAWServer.Exceptions
7879
notAJVMClass,
7980
notAJVMMethodSpecIR,
8081
notAYosysImport,
81-
notAYosysTheorem, notAYosysSequential
82+
notAYosysTheorem, notAYosysSequential,
83+
notAMIRModule
8284
)
8385

8486
type SAWCont = (SAWEnv, SAWTask)
@@ -317,12 +319,13 @@ data ServerVal
317319
| VJVMCrucibleSetup (Pair CrucibleSetupTypeRepr JVMSetupM)
318320
| VLLVMCrucibleSetup (Pair CrucibleSetupTypeRepr LLVMCrucibleSetupM)
319321
| VLLVMModule (Some CMS.LLVMModule)
322+
| VMIRModule RustModule
320323
| VJVMMethodSpecIR (CMS.ProvedSpec CJ.JVM)
321324
| VLLVMMethodSpecIR (CMS.SomeLLVM CMS.ProvedSpec)
322325
| VGhostVar CMS.GhostGlobal
323326
| VYosysImport YosysImport
324327
| VYosysTheorem YosysTheorem
325-
| VYosysSequential YosysSequential
328+
| VYosysSequential YosysSequential
326329

327330
instance Show ServerVal where
328331
show (VTerm t) = "(VTerm " ++ show t ++ ")"
@@ -333,6 +336,7 @@ instance Show ServerVal where
333336
show (VJVMCrucibleSetup _) = "VJVMCrucibleSetup"
334337
show (VLLVMCrucibleSetup _) = "VLLVMCrucibleSetup"
335338
show (VLLVMModule (Some _)) = "VLLVMModule"
339+
show (VMIRModule _) = "VMIRModule"
336340
show (VLLVMMethodSpecIR _) = "VLLVMMethodSpecIR"
337341
show (VJVMMethodSpecIR _) = "VJVMMethodSpecIR"
338342
show (VGhostVar x) = "(VGhostVar " ++ show x ++ ")"
@@ -391,6 +395,9 @@ instance KnownCrucibleSetupType a => IsServerVal (LLVMCrucibleSetupM a) where
391395
instance IsServerVal (Some CMS.LLVMModule) where
392396
toServerVal = VLLVMModule
393397

398+
instance IsServerVal RustModule where
399+
toServerVal = VMIRModule
400+
394401
setServerVal :: IsServerVal val => ServerName -> val -> Argo.Command SAWState ()
395402
setServerVal name val =
396403
do Argo.debugLog $ "Saving " <> (T.pack (show name))
@@ -438,6 +445,13 @@ getLLVMModule n =
438445
VLLVMModule m -> return m
439446
_other -> Argo.raise (notAnLLVMModule n)
440447

448+
getMIRModule :: ServerName -> Argo.Command SAWState RustModule
449+
getMIRModule n =
450+
do v <- getServerVal n
451+
case v of
452+
VMIRModule m -> return m
453+
_other -> Argo.raise (notAMIRModule n)
454+
441455
getLLVMSetup :: ServerName -> Argo.Command SAWState (Pair CrucibleSetupTypeRepr LLVMCrucibleSetupM)
442456
getLLVMSetup n =
443457
do v <- getServerVal n

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

+12
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module SAWServer.Exceptions (
1414
, notAYosysTheorem
1515
, notAYosysImport
1616
, notAYosysSequential
17+
, notAMIRModule
1718
-- * Wrong monad errors
1819
, notSettingUpCryptol
1920
, notSettingUpLLVMCrucible
@@ -192,6 +193,17 @@ notAYosysSequential name =
192193
" is not a Yosys sequential module")
193194
(Just $ object ["name" .= name])
194195

196+
notAMIRModule ::
197+
(ToJSON name, Show name) =>
198+
name {- ^ the name that should have been mapped to a MIR module -}->
199+
JSONRPCException
200+
notAMIRModule name =
201+
makeJSONRPCException 10140
202+
("The server value with name " <>
203+
T.pack (show name) <>
204+
" is not a MIR module")
205+
(Just $ object ["name" .= name])
206+
195207
cantLoadLLVMModule :: String -> JSONRPCException
196208
cantLoadLLVMModule err =
197209
makeJSONRPCException
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{-# LANGUAGE MultiParamTypeClasses #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
-- | Support for interfacing with MIR-related commands in SAW.
4+
module SAWServer.MIRCrucibleSetup
5+
( mirLoadModule
6+
, mirLoadModuleDescr
7+
) where
8+
9+
import Control.Lens ( view )
10+
import Data.Aeson ( FromJSON(..), withObject, (.:) )
11+
12+
import SAWScript.Crucible.MIR.Builtins ( mir_load_module )
13+
14+
import qualified Argo
15+
import qualified Argo.Doc as Doc
16+
import SAWServer as Server
17+
( ServerName(..),
18+
SAWState,
19+
sawTask,
20+
setServerVal )
21+
import SAWServer.Exceptions ( notAtTopLevel )
22+
import SAWServer.OK ( OK, ok )
23+
import SAWServer.TopLevel ( tl )
24+
import SAWServer.TrackFile ( trackFile )
25+
26+
data MIRLoadModuleParams
27+
= MIRLoadModuleParams ServerName FilePath
28+
29+
instance FromJSON MIRLoadModuleParams where
30+
parseJSON =
31+
withObject "params for \"SAW/MIR/load module\"" $ \o ->
32+
MIRLoadModuleParams <$> o .: "name" <*> o .: "module name"
33+
34+
instance Doc.DescribedMethod MIRLoadModuleParams OK where
35+
parameterFieldDescription =
36+
[ ("name",
37+
Doc.Paragraph [Doc.Text "The name to refer to the loaded module by later."])
38+
, ("module name",
39+
Doc.Paragraph [Doc.Text "The file containing the MIR JSON file to load."])
40+
]
41+
resultFieldDescription = []
42+
43+
mirLoadModuleDescr :: Doc.Block
44+
mirLoadModuleDescr =
45+
Doc.Paragraph [Doc.Text "Load the specified MIR module."]
46+
47+
-- | The implementation of the @SAW/MIR/load module@ command.
48+
mirLoadModule :: MIRLoadModuleParams -> Argo.Command SAWState OK
49+
mirLoadModule (MIRLoadModuleParams serverName fileName) =
50+
do tasks <- view sawTask <$> Argo.getState
51+
case tasks of
52+
(_:_) -> Argo.raise $ notAtTopLevel $ map fst tasks
53+
[] ->
54+
do mirMod <- tl $ mir_load_module fileName
55+
setServerVal serverName mirMod
56+
trackFile fileName
57+
ok

0 commit comments

Comments
 (0)