Skip to content

Commit 37d57b3

Browse files
authored
Merge pull request #7600 from NlightNFotis/rust_doc_enhancements
Enhance documentation for functions and modules of the Rust API
2 parents 4af9c8a + c60a175 commit 37d57b3

File tree

6 files changed

+195
-50
lines changed

6 files changed

+195
-50
lines changed

src/libcprover-rust/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ cxx-build = "1.0"
1818

1919
[lib]
2020
crate-type = ["rlib"]
21+
doctest = false

src/libcprover-rust/build.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ fn get_current_working_dir() -> std::io::Result<PathBuf> {
88
env::current_dir()
99
}
1010

11-
// Passed by the top-level CMakeLists.txt to control which version of the
12-
// static library of CBMC we're linking against. A user can also change the
13-
// environment variable to link against different versions of CBMC.
11+
// Initially passed by the top-level CMakeLists.txt to control which version
12+
// of the static library of CBMC we were linking against. Staying in order to
13+
// allow users to be able to easily change the version of the CBMC static
14+
// library that's being looked up.
1415
fn get_cbmc_version() -> Result<String, VarError> {
1516
env::var("CBMC_VERSION")
1617
}
1718

18-
// Passed by the top-level CMakeLists.txt to control where the static library we
19-
// link against is located. A user can also change the location of the library
20-
// on their system by supplying the environment variable themselves.
19+
// Initially passed by the top-level CMakeLists.txt to control where the static
20+
// library we link against was located. Now staying for backward compatibility of
21+
// the build system, and to allow fine grained control for a user as to where the
22+
// static library is located (be it in a build folder or a system `/lib` folder).
2123
fn get_lib_directory() -> Result<String, VarError> {
2224
env::var("CBMC_LIB_DIR")
2325
}

src/libcprover-rust/include/c_api.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ struct api_sessiont;
1313

1414
// Helper function
1515
std::vector<std::string> const &
16-
translate_vector_of_string(rust::Vec<rust::String> elements);
16+
_translate_vector_of_string(rust::Vec<rust::String> elements);
1717

1818
// Exposure of the C++ object oriented API through free-standing functions.
1919
std::unique_ptr<api_sessiont> new_api_session();

src/libcprover-rust/src/c_api.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ std::vector<std::string> output;
2020
extern bool cbmc_invariants_should_throw;
2121

2222
std::vector<std::string> const &
23-
translate_vector_of_string(rust::Vec<rust::String> elements)
23+
_translate_vector_of_string(rust::Vec<rust::String> elements)
2424
{
2525
std::vector<std::string> *stdv = new std::vector<std::string>{};
2626
std::transform(

src/libcprover-rust/src/lib.rs

Lines changed: 70 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,83 @@
1-
use cxx::{CxxString, CxxVector};
1+
#![doc = include_str!("../tutorial.md")]
2+
#![warn(missing_docs)]
23

4+
/// The main API module for interfacing with CProver tools (`cbmc`, `goto-analyzer`, etc).
35
#[cxx::bridge]
4-
pub mod ffi {
6+
pub mod cprover_api {
57

68
unsafe extern "C++" {
79
include!("libcprover-cpp/api.h");
810
include!("include/c_api.h");
911

12+
/// Central organisational handle of the API. This directly corresponds to the
13+
/// C++-API type `api_sessiont`. To initiate a session interaction, call [new_api_session].
1014
type api_sessiont;
1115

12-
// API Functions
16+
/// Provide a unique pointer to the API handle. This will be required to interact
17+
/// with the API calls, and thus, is expected to be the first call before any other
18+
/// interaction with the API.
1319
fn new_api_session() -> UniquePtr<api_sessiont>;
20+
21+
/// Return the API version - note that this is coming from the C++ API, which
22+
/// returns the API version of CBMC (which should map to the version of `libcprover.a`)
23+
/// the Rust API has mapped against.
1424
fn get_api_version(&self) -> UniquePtr<CxxString>;
25+
/// Provided a C++ Vector of Strings (use [translate_vector_of_string] to translate
26+
/// a Rust `Vec<String` into a `CxxVector<CxxString>` before passing it to the function),
27+
/// load the models from the files in the vector and link them together.
1528
fn load_model_from_files(&self, files: &CxxVector<CxxString>) -> Result<()>;
29+
/// Execute a verification engine run against the loaded model.
30+
/// *ATTENTION*: A model must be loaded before this function is run.
1631
fn verify_model(&self) -> Result<()>;
32+
/// Run a validation check on the goto-model that has been loaded.
33+
/// Corresponds to the CProver CLI option `--validate-goto-model`.
1734
fn validate_goto_model(&self) -> Result<()>;
35+
/// Drop functions that aren't used from the model. Corresponds to
36+
/// the CProver CLI option `--drop-unused-functions`
1837
fn drop_unused_functions(&self) -> Result<()>;
1938

20-
// Helper/Utility functions
21-
fn translate_vector_of_string(elements: Vec<String>) -> &'static CxxVector<CxxString>;
39+
// WARNING: Please don't use this function - use its public interface in [ffi_util::translate_rust_vector_to_cpp].
40+
// The reason this is here is that it's implemented on the C++ shim, and to link this function against
41+
// its implementation it needs to be declared within the `unsafe extern "C++"` block of the FFI bridge.
42+
#[doc(hidden)]
43+
fn _translate_vector_of_string(elements: Vec<String>) -> &'static CxxVector<CxxString>;
44+
/// Print messages accumulated into the message buffer from CProver's end.
2245
fn get_messages() -> &'static CxxVector<CxxString>;
2346
}
24-
25-
extern "Rust" {
26-
fn print_response(vec: &CxxVector<CxxString>);
27-
fn translate_response_buffer(vec: &CxxVector<CxxString>) -> Vec<String>;
28-
}
2947
}
3048

31-
/// This is a utility function, whose job is to translate the responses from the C++
32-
/// API (which we get in the form of a C++ std::vector<std:string>) into a form that
33-
/// can be easily consumed by other Rust programs.
34-
pub fn translate_response_buffer(vec: &CxxVector<CxxString>) -> Vec<String> {
35-
vec.iter()
36-
.map(|s| s.to_string_lossy().into_owned())
37-
.collect()
38-
}
49+
/// Module containing utility functions for translating between types across
50+
/// the FFI boundary.
51+
pub mod ffi_util {
52+
use crate::cprover_api::_translate_vector_of_string;
53+
use cxx::CxxString;
54+
use cxx::CxxVector;
55+
56+
/// This function translates the responses from the C++ API (which we get in the
57+
/// form of a C++ std::vector<std:string>) into the equivalent Rust type `Vec<String>`.
58+
/// Dual to [translate_rust_vector_to_cpp].
59+
pub fn translate_cpp_vector_to_rust(vec: &CxxVector<CxxString>) -> Vec<String> {
60+
vec.iter()
61+
.map(|s| s.to_string_lossy().into_owned())
62+
.collect()
63+
}
3964

40-
/// This is a utility function, whose aim is to simplify direct printing of the messages
41-
/// that we get from CBMC's C++ API. Underneath, it's using translate_response_buffer
42-
/// to translate the C++ types into Rust types and then prints out the strings contained
43-
/// in the resultant rust vector.
44-
pub fn print_response(vec: &CxxVector<CxxString>) {
45-
let vec: Vec<String> = translate_response_buffer(vec);
65+
/// This function aims to simplify direct printing of the messages that we get
66+
/// from CBMC's C++ API.
67+
pub fn print_response(vec: &Vec<String>) {
68+
for s in vec {
69+
println!("{}", s);
70+
}
71+
}
4672

47-
for s in vec {
48-
println!("{}", s);
73+
/// Translate a Rust `Vec<String>` into a C++ acceptable `std::vector<std::string>`.
74+
/// Dual to [translate_cpp_vector_to_rust].
75+
pub fn translate_rust_vector_to_cpp(elements: Vec<String>) -> &'static CxxVector<CxxString> {
76+
_translate_vector_of_string(elements)
4977
}
5078
}
5179

52-
// To test run "CBMC_LIB_DIR=<path_to_build/libs> SAT_IMPL=minisat2 cargo test -- --test-threads=1 --nocapture"
80+
// To test run "CBMC_LIB_DIR=<path_to_build/libs> CBMC_VERSION=<version> cargo test -- --test-threads=1 --nocapture"
5381
#[cfg(test)]
5482
mod tests {
5583
use super::*;
@@ -58,7 +86,7 @@ mod tests {
5886

5987
#[test]
6088
fn it_works() {
61-
let client = ffi::new_api_session();
89+
let client = cprover_api::new_api_session();
6290
let result = client.get_api_version();
6391

6492
let_cxx_string!(expected_version = "0.1");
@@ -69,21 +97,21 @@ mod tests {
6997
fn translate_vector_of_rust_string_to_cpp() {
7098
let vec: Vec<String> = vec!["other/example.c".to_owned(), "/tmp/example2.c".to_owned()];
7199

72-
let vect = ffi::translate_vector_of_string(vec);
100+
let vect = ffi_util::translate_rust_vector_to_cpp(vec);
73101
assert_eq!(vect.len(), 2);
74102
}
75103

76104
#[test]
77105
fn it_can_load_model_from_file() {
78-
let binding = ffi::new_api_session();
106+
let binding = cprover_api::new_api_session();
79107
let client = match binding.as_ref() {
80108
Some(api_ref) => api_ref,
81109
None => panic!("Failed to acquire API session handle"),
82110
};
83111

84112
let vec: Vec<String> = vec!["other/example.c".to_owned()];
85113

86-
let vect = ffi::translate_vector_of_string(vec);
114+
let vect = ffi_util::translate_rust_vector_to_cpp(vec);
87115
assert_eq!(vect.len(), 1);
88116

89117
// Invoke load_model_from_files and see if the model
@@ -104,21 +132,21 @@ mod tests {
104132
// This is also why a print instruction is commented out (as a guide for someone
105133
// else in case they want to inspect the output).
106134
let validation_msg = "Validating consistency of goto-model supplied to API session";
107-
let msgs = ffi::get_messages();
108-
let msgs_assert = translate_response_buffer(msgs).clone();
135+
let msgs = cprover_api::get_messages();
136+
let msgs_assert = ffi_util::translate_cpp_vector_to_rust(msgs).clone();
109137

110138
assert!(msgs_assert.contains(&String::from(validation_msg)));
111139

112-
// print_response(msgs);
140+
// ffi_util::print_response(msgs_assert);
113141
}
114142

115143
#[test]
116144
fn it_can_verify_the_loaded_model() {
117-
let client = ffi::new_api_session();
145+
let client = cprover_api::new_api_session();
118146

119147
let vec: Vec<String> = vec!["other/example.c".to_owned()];
120148

121-
let vect = ffi::translate_vector_of_string(vec);
149+
let vect = ffi_util::translate_rust_vector_to_cpp(vec);
122150

123151
if let Err(_) = client.load_model_from_files(vect) {
124152
eprintln!("Failed to load model from files: {:?}", vect);
@@ -138,23 +166,23 @@ mod tests {
138166

139167
let verification_msg = "VERIFICATION FAILED";
140168

141-
let msgs = ffi::get_messages();
142-
let msgs_assert = translate_response_buffer(msgs).clone();
169+
let msgs = cprover_api::get_messages();
170+
let msgs_assert = ffi_util::translate_cpp_vector_to_rust(msgs).clone();
143171

144172
assert!(msgs_assert.contains(&String::from(verification_msg)));
145173
}
146174

147175
#[test]
148176
fn it_can_drop_unused_functions_from_model() {
149-
let binding = ffi::new_api_session();
177+
let binding = cprover_api::new_api_session();
150178
let client = match binding.as_ref() {
151179
Some(api_ref) => api_ref,
152180
None => panic!("Failed to acquire API session handle"),
153181
};
154182

155183
let vec: Vec<String> = vec!["other/example.c".to_owned()];
156184

157-
let vect = ffi::translate_vector_of_string(vec);
185+
let vect = ffi_util::translate_rust_vector_to_cpp(vec);
158186
assert_eq!(vect.len(), 1);
159187

160188
if let Err(_) = client.load_model_from_files(vect) {
@@ -171,8 +199,8 @@ mod tests {
171199
let instrumentation_msg = "Performing instrumentation pass: dropping unused functions";
172200
let instrumentation_msg2 = "Dropping 8 of 11 functions (3 used)";
173201

174-
let msgs = ffi::get_messages();
175-
let msgs_assert = translate_response_buffer(msgs).clone();
202+
let msgs = cprover_api::get_messages();
203+
let msgs_assert = ffi_util::translate_cpp_vector_to_rust(msgs).clone();
176204

177205
assert!(msgs_assert.contains(&String::from(instrumentation_msg)));
178206
assert!(msgs_assert.contains(&String::from(instrumentation_msg2)));

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)