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