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