|
| 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