Skip to content

Commit 91d933c

Browse files
committed
[DOCS] Add a tutorial file and inline it into the documentation
1 parent 32d5223 commit 91d933c

File tree

2 files changed

+120
-14
lines changed

2 files changed

+120
-14
lines changed

src/libcprover-rust/src/lib.rs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
1+
#![doc = include_str!("../tutorial.md")]
12
#![warn(missing_docs)]
23

3-
//! # Libcprover_rust
4-
//!
5-
//! A Rust interface for convenient interaction with the CProver tools.
6-
7-
/// The main API module for interfacing with CProver tools (`cbmc`,
8-
/// `goto-analyzer`, etc).
4+
/// The main API module for interfacing with CProver tools (`cbmc`, `goto-analyzer`, etc).
95
#[cxx::bridge]
106
pub mod cprover_api {
117

@@ -67,12 +63,8 @@ pub mod ffi_util {
6763
}
6864

6965
/// This function aims to simplify direct printing of the messages that we get
70-
/// from CBMC's C++ API. Underneath, it's using [translate_cpp_vector_to_rust]
71-
/// to translate the C++ types into Rust types and then prints out the strings
72-
/// contained in the resultant Rust vector.
73-
pub fn print_response(vec: &CxxVector<CxxString>) {
74-
let vec: Vec<String> = translate_response_buffer(vec);
75-
66+
/// from CBMC's C++ API.
67+
pub fn print_response(vec: &Vec<String>) {
7668
for s in vec {
7769
println!("{}", s);
7870
}
@@ -87,7 +79,7 @@ pub mod ffi_util {
8779

8880
// To test run "CBMC_LIB_DIR=<path_to_build/libs> CBMC_VERSION=<version> cargo test -- --test-threads=1 --nocapture"
8981
#[cfg(test)]
90-
mod tests
82+
mod tests {
9183
use super::*;
9284
use cxx::let_cxx_string;
9385
use std::process;
@@ -145,7 +137,7 @@ mod tests
145137

146138
assert!(msgs_assert.contains(&String::from(validation_msg)));
147139

148-
// print_response(msgs);
140+
// ffi_util::print_response(msgs_assert);
149141
}
150142

151143
#[test]

src/libcprover-rust/tutorial.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Libcprover-rust
2+
3+
A Rust interface for convenient interaction with the CProver tools.
4+
5+
## Basic Usage
6+
7+
This file will guide through a sample interaction with the API, under a basic
8+
scenario: *loading a file and verifying the model contained within*.
9+
10+
To begin, we will assume that you have a file under `/tmp/api_example.c`,
11+
with the following contents:
12+
13+
```c
14+
int main(int argc, char *argv[])
15+
{
16+
int arr[] = {0, 1, 2, 3};
17+
__CPROVER_assert(arr[3] != 3, "expected failure: arr[3] == 3");
18+
}
19+
```
20+
21+
The first thing we need to do to initiate any interaction with the API
22+
itself is to create a new `api_sessiont` handle by using the function
23+
`new_api_session`:
24+
25+
```rust
26+
let client = cprover_api::new_api_session();
27+
```
28+
29+
Then, we need to add the file to a vector with filenames that indicate
30+
which files we want the verification engine to load the models of:
31+
32+
```rust
33+
let vec: Vec<String> = vec!["/tmp/api_example.c".to_owned()];
34+
35+
let vect = ffi_util::translate_rust_vector_to_cpp(vec);
36+
```
37+
38+
In the above code example, we created a Rust language Vector of Strings
39+
(`vec`). In the next line, we called a utility function from the module
40+
`ffi_util` to translate the Rust `Vec<String>` into the C++ equivalent
41+
`std::vector<std::string>` - this step is essential, as we need to translate
42+
the type into something that the C++ end understands.
43+
44+
These operations are *explicit*: we have opted to force users to translate
45+
between types at the FFI level in order to reduce the "magic" and instill
46+
mental models more compatible with the nature of the language-border (FFI)
47+
work. If we didn't, and we assumed the labour of translating these types
48+
transparently at the API level, we risked mistakes from our end or from the
49+
user end frustrating debugging efforts.
50+
51+
At this point, we have a handle of a C++ vector containing the filenames
52+
of the files we want the CProver verification engine to load. To do so,
53+
we're going to use the following piece of code:
54+
55+
```rust
56+
// Invoke load_model_from_files and see if the model has been loaded.
57+
if let Err(_) = client.load_model_from_files(vect) {
58+
eprintln!("Failed to load model from files: {:?}", vect);
59+
process::exit(1);
60+
}
61+
```
62+
63+
The above is an example of a Rust idiom known as a `if let` - it's effectively
64+
a pattern match with just one pattern - we don't match any other case.
65+
66+
What we we do above is two-fold:
67+
68+
* We call the function `load_model_from_files` with the C++ vector (`vect`)
69+
we prepared before. It's worth noting that this function is being called
70+
with `client.` - what this does is that it passes the `api_session` handle
71+
we initialised at the beginning as the first argument to the `load_model_from_files`
72+
on the C++ API's end.
73+
* We handled the case where the model loading failed for whatever reason from
74+
the C++ end by catching the error on the Rust side and printing a suitable error
75+
message and exiting the process gracefully.
76+
77+
---
78+
79+
*Interlude*: **Error Handling**
80+
81+
`cxx.rs` (the FFI bridge we're using to build the Rust API) allows for a mechanism
82+
wherein exceptions from the C++ program can be translated into Rust `Result<>` types
83+
provided suitable infrastructure has been built.
84+
85+
Our Rust API contains a C++ shim which (among other things) intercepts CProver
86+
exceptions (from `cbmc`, etc.) and translates them into a form that the bridge
87+
can then translate to appropriate `Result` types that the Rust clients can use.
88+
89+
This means that, as above, we can use the same Rust idioms and types as we would
90+
use on a purely Rust based codebase to interact with the API.
91+
92+
*All of the API calls* are returning `Result` types such as above.
93+
94+
---
95+
96+
After we have loaded the model, we can proceed to then engage the verification
97+
engine for an analysis run:
98+
99+
```rust
100+
if let Err(_) = client.verify_model() {
101+
eprintln!("Failed to verify model from files: {:?}", vect);
102+
process::exit(1);
103+
}
104+
```
105+
106+
While all this is happening, we are collecting the output of the various
107+
phases into a message buffer. We can go forward and print any messages from
108+
that buffer into `stdout`:
109+
110+
```rust
111+
let msgs_cpp = cprover_api::get_messages();
112+
let msgs_rust = ffi_util::translate_cpp_vector_to_rust(msgs_cpp);
113+
ffi_util::print_response(msgs_rust);
114+
```

0 commit comments

Comments
 (0)