machine_check/lib.rs
1#![doc = include_str!("../README.md")]
2
3mod args;
4mod traits;
5mod types;
6mod verify;
7
8use log::error;
9use log::info;
10use log::log_enabled;
11use log::trace;
12use log::warn;
13use machine_check_common::check::KnownConclusion;
14use machine_check_common::property::Property;
15use machine_check_exec::Strategy;
16
17use args::ProgramArgs;
18pub use args::{ExecArgs, ExecStrategy};
19pub use traits::Ext;
20pub use types::{Bitvector, BitvectorArray, Signed, Unsigned};
21
22/// Finite-state machine intended to be verifiable by **machine-check**.
23///
24/// To actually be verifiable by **machine-check**, further processing must be done by enclosing the structures
25/// and the [`Machine`] implementation within the [`machine_description`] macro.
26///
27pub use ::mck::concr::Machine;
28
29use ::mck::concr::FullMachine;
30
31/// Switch using a bitmask as scrutinee, useful for switching on processor instruction opcodes.
32///
33/// The switch is similar to a normal Rust match expression:
34/// ```
35/// use machine_check::{Bitvector, bitmask_switch};
36/// let opcode = Bitvector::<6>::new(0b10_1101);
37/// let mut foo = Bitvector::<2>::new(0);
38/// let mut bar = Bitvector::<2>::new(0);
39/// bitmask_switch!(opcode {
40/// "00_----" => {}
41/// "10_aabb" => {
42/// foo = a;
43/// bar = b;
44/// }
45/// "11_--cc" => {
46/// foo = c;
47/// }
48/// _ => {}
49/// });
50/// assert_eq!(foo, Bitvector::new(3));
51/// assert_eq!(bar, Bitvector::new(1));
52/// ```
53///
54/// Unlike Rust match, the scrutinee must be [`Bitvector`], and the non-default choices are string literals
55/// containing, for each bit of the bitvector, one of these:
56/// - '0' or '1': the bit must match,
57/// - dash ('-'): the bit can have any value (don't care),
58/// - ASCII letter: same as don't care, but a new variable is created containing the bits with given letter.
59/// - Underscore ('_') used to visually separate the bits.
60///
61/// Unlike Rust match, overlapping choices are not permitted, so that it is certain which arm is taken.
62/// In case there is no default arm, there must be full coverage.
63///
64/// Currently, the macro cannot return values and there is no syntactic disjunction guarantee, i.e. that
65/// exactly one of the arms is taken. This may change in the future.
66///
67///
68pub use ::machine_check_macros::bitmask_switch;
69
70/// Processes a module so that it can be used in **machine-check** verification.
71///
72/// To efficiently verify a system with **machine-check**, verification equivalents of the system that allow
73/// more advanced reasoning (e.g. not caring about the value of some variable unless found to be necessary)
74/// must be created, which is done by enclosing the system code in a module and applying this macro on it.
75///
76/// In practice, everything used in [`Machine`] must be processed by this macro. System construction,
77/// however, can (and should) be done outside.
78///
79/// Note that, due to [a Rust issue](https://github.com/rust-lang/rust/issues/54726), procedural macros
80/// currently cannot be used as inner attributes, so this is the currently recommended way of
81/// using the macro:
82/// ```
83/// #[machine_check::machine_description]
84/// mod machine_module {
85/// // ... structs including a struct implementing Machine ...
86/// }
87/// ```
88///
89/// The macro is currently rather limited in the subset of Rust code it can process, and errors may be cryptic.
90/// Improvements are planned in the future. For now, the examples in the crate show code processable without errors.
91///
92pub use ::machine_check_macros::machine_description;
93
94/// Executes machine-check with system environment arguments.
95///
96/// Is supposed to be used for simple systems that do not take arguments.
97///
98/// The system must implement [`Machine`]. The system structures and [`Input`], [`State`], and [`Machine`]
99/// implementations must be enclosed within the [`machine_description`] macro, which processes them to enable
100/// fast and efficient formal verification.
101pub fn run<M: FullMachine>(system: M) -> ExecResult {
102 let parsed_args = <ExecArgs as clap::Parser>::parse_from(std::env::args());
103 execute(system, parsed_args)
104}
105
106/// Parses machine-check and user-defined arguments.
107///
108/// Returns arguments parsed to `machine-check` and system-specific argument definitions.
109/// The arguments can be later used in [`execute`].
110pub fn parse_args<A: clap::Args>(args: impl Iterator<Item = String>) -> (ExecArgs, A) {
111 let parsed_args = <ProgramArgs<A> as clap::Parser>::parse_from(args);
112 (parsed_args.run_args, parsed_args.system_args)
113}
114
115pub use ::machine_check_common::ExecError;
116pub use ::machine_check_common::ExecResult;
117pub use ::machine_check_common::ExecStats;
118
119/// Runs **machine-check** with the given constructed system and parsed arguments.
120///
121/// Parsed arguments are used to run **machine-check**. Otherwise, this method behaves the same as [`run`].
122pub fn execute<M: FullMachine>(system: M, exec_args: ExecArgs) -> ExecResult {
123 // logging to stderr, stdout will contain the result in batch mode
124 let silent = exec_args.silent;
125 let batch = exec_args.batch;
126
127 let mut filter_level_num = exec_args.verbose;
128
129 let mut overriden_log_level = false;
130 if let Ok(Ok(log_level_override)) =
131 std::env::var("MACHINE_CHECK_LOG_LEVEL_OVERRIDE").map(|var| var.parse::<u8>())
132 {
133 filter_level_num = log_level_override;
134 overriden_log_level = true;
135 }
136
137 let mut filter_level = match filter_level_num {
138 0 => log::LevelFilter::Info,
139 1 => log::LevelFilter::Debug,
140 _ => log::LevelFilter::Trace,
141 };
142
143 if silent && !overriden_log_level {
144 filter_level = log::LevelFilter::Off;
145 }
146
147 // initialize logger, but do not panic if it was already initialized
148 let _ = env_logger::builder().filter_level(filter_level).try_init();
149
150 if overriden_log_level {
151 warn!(
152 "Overriden log level to {:?} ({})",
153 filter_level, filter_level_num
154 );
155 }
156
157 let strategy = Strategy {
158 naive_inputs: matches!(exec_args.strategy, ExecStrategy::Naive),
159 use_decay: matches!(exec_args.strategy, ExecStrategy::Decay),
160 };
161
162 // determine the property to verify
163 let prop = if let Some(property_str) = exec_args.property {
164 match Property::parse(&property_str) {
165 Ok(prop) => Some(prop),
166 Err(err) => {
167 error!("Cannot construct the property: {}", err);
168 return ExecResult {
169 result: Err(err),
170 stats: ExecStats::default(),
171 };
172 }
173 }
174 } else {
175 // check for inherent panics
176 None
177 };
178 if prop.is_none() && !exec_args.gui && !exec_args.inherent {
179 panic!("Expected either a property or inherent verification");
180 }
181
182 let result = if exec_args.gui {
183 // start the GUI instead of verifying
184 ExecResult {
185 result: Err(start_gui(system, prop, strategy)),
186 stats: ExecStats::default(),
187 }
188 } else {
189 info!("Starting verification.");
190
191 let result = verify::verify(system, prop, exec_args.assume_inherent, strategy);
192
193 if log_enabled!(log::Level::Trace) {
194 trace!("Verification result: {:?}", result);
195 }
196
197 if log_enabled!(log::Level::Info) {
198 // the result will be propagated, just inform that we ended somehow
199 match result.result {
200 Ok(_) => info!("Verification ended."),
201 Err(_) => error!("Verification returned an error."),
202 }
203 }
204 result
205 };
206
207 if !silent {
208 if batch {
209 // serialize the verification result to stdout
210 if let Err(err) = serde_json::to_writer(std::io::stdout(), &result) {
211 panic!("Could not serialize verification result: {:?}", err);
212 }
213 } else if !matches!(result.result, Err(ExecError::NoResult))
214 && log_enabled!(log::Level::Info)
215 {
216 // print the verification result nicely
217 let result_title = match &result.result {
218 Ok(KnownConclusion::False) => "Result: DOES NOT HOLD",
219 Ok(KnownConclusion::True) => "Result: HOLDS",
220 Ok(KnownConclusion::Dependent) => "Result: DEPENDS ON PARAMETERS",
221 Err(err) => &format!("Result: ERROR ({})", err),
222 };
223
224 let mut stats_cells: Vec<(&str, String)> = [
225 ("Refinements", result.stats.num_refinements),
226 ("Generated states", result.stats.num_generated_states),
227 ("Final states", result.stats.num_final_states),
228 (
229 "Generated transitions",
230 result.stats.num_generated_transitions,
231 ),
232 ("Final transitions", result.stats.num_final_transitions),
233 ]
234 .into_iter()
235 .map(|(name, value)| (name, value.to_string()))
236 .collect();
237
238 if let Some(inherent_panic_message) = &result.stats.inherent_panic_message {
239 stats_cells.push((
240 "Inherent panic message",
241 format!("{:?}", inherent_panic_message),
242 ));
243 }
244
245 let inner_table_width = stats_cells
246 .iter()
247 .map(|(name, value)| format!("{}: {}", name, value).len())
248 .max()
249 .unwrap()
250 .max(result_title.len());
251
252 let result_title = format!(
253 "| {:^width$} |",
254 result_title,
255 width = inner_table_width
256 );
257 let table_bar = format!("+{}+", "-".repeat(result_title.len().saturating_sub(2)));
258
259 // the log is printed to stderr, follow it
260 eprintln!("{}\n{}\n{}", table_bar, result_title, table_bar);
261 for (name, value) in stats_cells {
262 eprintln!(
263 "| {}: {:>width$} |",
264 name,
265 value,
266 width = inner_table_width - name.len()
267 )
268 }
269 eprintln!("{}", table_bar);
270 }
271 }
272 result
273}
274
275fn start_gui<M: FullMachine>(
276 system: M,
277 property: Option<Property>,
278 strategy: Strategy,
279) -> ExecError {
280 // the GUI will, at best, return no result
281 #[cfg(feature = "gui")]
282 match machine_check_gui::run(system, property, strategy) {
283 Ok(()) => ExecError::NoResult,
284 Err(err) => err,
285 }
286 #[cfg(not(feature = "gui"))]
287 {
288 // make sure there is no warning about unused variables
289 let _ = (system, property, strategy);
290 ExecError::GuiError(String::from("The GUI feature was not enabled during build"))
291 }
292}
293
294#[doc(hidden)]
295pub mod mck {
296 pub use mck::*;
297}