machine_check/
lib.rs

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