Skip to content

Commit 3aed2b5

Browse files
authored
Add 'real code' section to tutorial, plus command line args docs (#525)
* add 'real code' section to tutorial, plus command line arguments documentation * fix typos
1 parent a6d7cda commit 3aed2b5

File tree

5 files changed

+166
-3
lines changed

5 files changed

+166
-3
lines changed

rmc-docs/src/SUMMARY.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
- [Getting started with RMC](./getting-started.md)
44
- [Installation](./install-guide.md)
55
- [Comparison with other tools](./tool-comparison.md)
6-
- [RMC on a single file]()
6+
- [RMC on a single file](./rmc-single-file.md)
77
- [RMC on a crate]()
88
- [Debugging failures]()
99
- [Debugging non-termination]()
10+
- [Debugging coverage]()
1011

1112
- [RMC tutorial](./rmc-tutorial.md)
1213
- [First steps with RMC](./tutorial-first-steps.md)
1314
- [Failures that RMC can spot](./tutorial-kinds-of-failure.md)
1415
- [Loops, unwinding, and bounds](./tutorial-loops-unwinding.md)
16+
- [Where to start on real code](./tutorial-real-code.md)
1517

1618
- [RMC developer documentation]()
1719

rmc-docs/src/getting-started.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
# Getting started with RMC
22

33
RMC is a Rust verification tool based on _model checking_.
4-
With RMC, you can ensure that broad classes of problems are absent from your Rust code by writing _proof harnesses_, which are broadly similar to tests (especially property tests.)
4+
With RMC, you can ensure that broad classes of problems are absent from your Rust code by writing _proof harnesses_, which are broadly similar to tests (especially property tests).
55
RMC is especially useful for verifying unsafe code in Rust, where many of the language's usual guarantees can no longer be checked by the compiler.
66
But RMC is also useful for finding panics in safe Rust, and it can check user-defined assertions.
77

88
## Project Status
99

1010
RMC is currently in the initial development phase, and has not yet made an official release.
1111
It is under active development, but it does not yet support all Rust language features.
12+
(The [RMC dashboard](./dashboard.md) can help you understand our current progress.)
1213
If you encounter issues when using RMC we encourage you to [report them to us](https://github.com/model-checking/rmc/issues/new/choose).
1314

15+
RMC usually syncs with the main branch of Rust every week, and so is generally up-to-date with the latest Rust language features.
16+
1417
## Getting started
1518

1619
1. [Begin with the RMC installation guide.](./install-guide.md) Currently, this means checking out and building RMC.

rmc-docs/src/rmc-single-file.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# RMC on a single file
2+
3+
For small examples, or initial learning, it's very common to run RMC on just one source file.
4+
The command line format for invoking RMC directly has a few common formats:
5+
6+
```
7+
rmc filename.rs
8+
# or
9+
rmc filename.rs [--rmc-flags]
10+
# or
11+
rmc filename.rs [--rmc-flags] --cbmc-args [--cbmc-flags]
12+
```
13+
14+
For example,
15+
16+
```
17+
rmc filenames.rs --visualize --cbmc-args --object-bits 11 --unwind 15
18+
```
19+
20+
## Common RMC arguments
21+
22+
**`--visualize`** will generate a report in the local directory accessible through `report/html/index.html`.
23+
This report will shows coverage information, as well as give traces for each failure RMC finds.
24+
25+
**`--function <name>`** RMC defaults to assuming the starting function is called `main`.
26+
You can change it to a different function with this argument.
27+
Note that to "find" the function given, it needs to be given the `#[no_mangle]` annotation.
28+
29+
**`--gen-c`** will generate a C file that roughly corresponds to the input Rust file.
30+
This can sometimes be helpful when trying to debug a problem with RMC.
31+
32+
**`--keep-temps`** will preserve generated files that RMC generates.
33+
In particular, this will include a `.json` file which is the "CBMC symbol table".
34+
This can be helpful in trying to diagnose bugs in RMC, and may sometimes be requested in RMC bug reports.
35+
36+
## Common CBMC arguments
37+
38+
RMC invokes CBMC to do the underlying solving.
39+
(CBMC is the "C Bounded Model Checker" but is actually a framework that supports model checking multiple languages.)
40+
CBMC arguments are sometimes necessary to get good results.
41+
42+
To give arguments to CBMC, you pass `--cbmc-args` to RMC.
43+
This "switches modes" from RMC arguments to CBMC arguments.
44+
Everything else given on the command line will be assumed to be a CBMC argument, and so all RMC arguments should be provided before this flag.
45+
46+
**`--unwind <n>`** Give a global upper bound on all loops.
47+
This can force termination when CBMC tries to unwind loops indefinitely.
48+
49+
**`--object-bits <n>`** CBMC, by default, assumes there are only going to be 256 objects allocated on the heap in a single trace.
50+
This corresponds to a default of `--object-bits 8`.
51+
Rust programs often will use more than this, and so need to raise this limit.
52+
However, very large traces with many allocations often prove intractable to solve.
53+
If you run into this issue, a good first start is to raise the limit to 2048, i.e. `--object-bits 11`.
54+
55+
**`--unwindset label_1:bound_1,label_2:bound_2,...`** Give specific unwinding bounds on specific loops.
56+
The labels for each loop can be discovered by running with the following CBMC flag:
57+
58+
**`--show-loops`** Print the labels of each loop in the program.
59+
Useful for `--unwindset`.
60+

rmc-docs/src/tutorial-loops-unwinding.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ Then we can specify the bound for specific loops by name, from the command line:
122122
rmc src/lib.rs --cbmc-args --unwindset _RNvCs6JP7pnlEvdt_3lib17initialize_prefix.0:12
123123
```
124124

125-
The general format of the `--unwindset` option is: `label_1:bound_1,label_2:bound_1,...`.
125+
The general format of the `--unwindset` option is: `label_1:bound_1,label_2:bound_2,...`.
126126
The label is revealed by the output of `--show-loops` as we saw above.
127127

128128
## Summary

rmc-docs/src/tutorial-real-code.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Where to start on real code
2+
3+
It can be daunting to find the right place to start writing proofs for a real-world project.
4+
This section will try to help you get over that hurdle.
5+
6+
In general, you're trying to do three things:
7+
8+
1. Find a place it'd be valuable to have a proof.
9+
2. Find a place it won't be too difficult to prove something, just to start.
10+
3. Figure out what a feasible longer-term goal might be.
11+
12+
**By far the best strategy is to follow your testing.**
13+
Places where proof will be valuable are often places where you've written a lot of tests, because they're valuable there for the same reasons.
14+
Likewise, code structure changes to make functions more unit-testable will also make functions more amenable to proof.
15+
Often, by examining existing unit tests (and especially property tests), you can easily find a relatively self-contained function that's a good place to start.
16+
17+
## Where is proof valuable?
18+
19+
1. Where complicated things happen with untrusted user input.
20+
These are often the critical "entry points" into the code.
21+
These are also places where you probably want to try fuzz testing.
22+
23+
2. Where `unsafe` is used extensively.
24+
These are often places where you'll already have concentrated a lot of tests.
25+
26+
3. Where you have a complicated implementation that accomplishes a much simpler abstract problem.
27+
Ideal places for property testing, if you haven't tried that already.
28+
But the usual style of property tests you often write here (generate large random lists of operations, then compare between concrete and abstract model) won't be practical to directly port to model checking.
29+
30+
4. Where normal testing "smells" intractable.
31+
32+
## Where is it easier to start?
33+
34+
1. Find crates or files with smaller lists of dependencies.
35+
Dependencies can sometimes blow up the tractability of proofs.
36+
This can usually be handled, but requires a lot more investment to make it happen, and so isn't a good place to start.
37+
38+
2. Don't forget to consider starting with your dependencies.
39+
Sometimes the best place to start won't be your code, but in code you depend on.
40+
If it's used by more projects that just yours, it will be valuable to more people, too!
41+
42+
3. Find well-tested code.
43+
Code structure changes to make code more unit-testable will make it more provable, too.
44+
45+
Here are some things to avoid, when starting out:
46+
47+
1. Lots of loops, or at least nested loops.
48+
As we saw in the last section, right now we often need to put upper bounds on these to make more limited claims.
49+
50+
2. "Inductive data structures."
51+
These are data of unbounded size.
52+
(For example, linked lists and trees.)
53+
Much like needing to put bounds on loops, these can be hard to model since you needs to put bounds on their size, too.
54+
55+
3. I/O code.
56+
RMC doesn't model I/O, so if you're depending on behavior like reading/writing to a file, you won't be able to prove anything.
57+
This is one obvious area where testability helps provability: often we separate I/O and "pure" computation into different functions, so we can unit test the latter.
58+
59+
4. Deeper call graphs.
60+
Functions that call a lot of other functions can require more investment to make tractable.
61+
They may not be a good starting point.
62+
63+
5. Significant global state.
64+
Rust tends to discourage this, but it still exists in some forms.
65+
66+
67+
## Your first proof
68+
69+
A first proof will likely start in the following form:
70+
71+
1. Nondeterministically initialize variables that will correspond to function inputs, with as few constraints as possible
72+
2. Call the function in question with these inputs.
73+
3. Don't (yet) assert any post-conditions.
74+
75+
Running RMC on this simple starting point will help figure out:
76+
77+
1. What unexpected constraints might be needed on your inputs to avoid "expected" failures.
78+
2. Whether you're over-constrained. Check the coverage report using `--visualize`. Ideally you'd see 100% coverage, and if not, it's usually because now you've over-constrained the inputs.
79+
3. Whether RMC will support all the Rust features involved.
80+
4. Whether you've started with a tractable problem.
81+
(If the problem is initially intractable, try `--unwind 1` and see if you can follow the techniques in the previous section to put a bound on the problem.)
82+
83+
Once you've got something working, the next step is to prove more interesting properties than what RMC covers by default.
84+
You accomplish this by adding new assertions.
85+
These are not necessarily assertions just in your proof harness: consider also adding new assertions to the code being run.
86+
These count too!
87+
Even if a proof harness has no post-conditions being asserted directly, the assertions encountered along the way can be meaningful proof results by themselves.
88+
89+
90+
## Summary
91+
92+
In this section:
93+
94+
1. We got some advice on how to choose a higher-value starting point for a first proof.
95+
2. We got some advice on how to choose an easier starting point for a first proof.
96+
3. We got some advice on how to structure our first proof, at least initially.
97+
98+
> TODO: This section is incomplete. We should probably add an example session of finding a proof to write for, perhaps, Firecracker.

0 commit comments

Comments
 (0)