machine_check_exec/
framework.rs

1use std::collections::BTreeMap;
2use std::collections::BTreeSet;
3use std::collections::VecDeque;
4use std::ops::ControlFlow;
5use std::time::Instant;
6
7use log::debug;
8use log::log_enabled;
9use log::trace;
10use machine_check_common::check::Conclusion;
11use machine_check_common::check::Culprit;
12use machine_check_common::check::PreparedProperty;
13use machine_check_common::ExecError;
14use machine_check_common::ExecStats;
15use machine_check_common::NodeId;
16use machine_check_common::StateId;
17use machine_check_common::ThreeValued;
18use mck::concr::FullMachine;
19use mck::misc::Meta;
20use mck::refin::Manipulatable;
21use mck::refin::{self};
22use work_state::WorkState;
23
24use crate::model_check::{self};
25use crate::space::StateSpace;
26use crate::RefinInput;
27use crate::RefinPanicState;
28use crate::Strategy;
29use mck::abstr::Machine as AbstrMachine;
30use mck::refin::Machine as RefinMachine;
31use mck::refin::Refine;
32
33mod work_state;
34
35/// Three-valued abstraction refinement framework.
36pub struct Framework<M: FullMachine> {
37    /// Abstract system.
38    abstract_system: M::Abstr,
39
40    /// Default input precision.
41    default_input_precision: RefinInput<M>,
42
43    /// Default step precision.
44    default_step_precision: RefinPanicState<M>,
45
46    /// Work state containing the structures that change during verification.
47    work_state: WorkState<M>,
48}
49
50impl<M: FullMachine> Framework<M> {
51    /// Constructs the framework with a given system and strategy.
52    pub fn new(abstract_system: M::Abstr, strategy: Strategy) -> Self {
53        // default the input precision to clean (inputs will be refined)
54        let default_input_precision = if strategy.naive_inputs {
55            Refine::dirty()
56        } else {
57            Refine::clean()
58        };
59
60        // default the step precision to dirty (steps will remain non-decayed)
61        let default_step_precision = if strategy.use_decay {
62            Refine::clean()
63        } else {
64            Refine::dirty()
65        };
66
67        // return the framework with empty state space, before any construction
68        Framework {
69            abstract_system,
70            default_input_precision,
71            default_step_precision,
72            work_state: WorkState::new(),
73        }
74    }
75
76    pub fn verify(&mut self, property: &PreparedProperty) -> Result<bool, ExecError> {
77        // loop verification steps until some conclusion is reached
78        let result = loop {
79            match self.step_verification(property) {
80                ControlFlow::Continue(()) => {}
81                ControlFlow::Break(result) => break result,
82            }
83        };
84
85        // make compact after verification for nice state space information
86        self.work_state.make_compact();
87
88        if log_enabled!(log::Level::Trace) {
89            trace!("Verification final space: {:#?}", self.work_state.space);
90        }
91        result
92    }
93
94    pub fn step_verification(
95        &mut self,
96        property: &PreparedProperty,
97    ) -> ControlFlow<Result<bool, ExecError>> {
98        // if the space is invalid (just after construction), regenerate it
99        if !self.work_state.space.is_valid() {
100            self.regenerate(NodeId::ROOT);
101        } else if let Some(culprit) = self.work_state.culprit.take() {
102            // we have a culprit, refine on it
103            if let Err(err) = self.refine(&culprit) {
104                // the refinement is incomplete
105                return ControlFlow::Break(Err(err));
106            }
107            // run garbage collection
108            self.work_state.garbage_collect();
109        }
110
111        if log_enabled!(log::Level::Trace) {
112            trace!("Model-checking state space: {:#?}", self.work_state.space);
113        }
114
115        // perform model-checking
116        match model_check::check_property::<M>(&self.work_state.space, property) {
117            Ok(Conclusion::Known(conclusion)) => {
118                // conclude the result
119                ControlFlow::Break(Ok(conclusion))
120            }
121            Ok(Conclusion::Unknown(culprit)) => {
122                // we have a new culprit, continue the control flow
123                self.work_state.culprit = Some(culprit);
124                ControlFlow::Continue(())
125            }
126            Ok(Conclusion::NotCheckable) => {
127                // should never happen, the state space should be valid
128                panic!("The state space should be valid after stepping verification");
129            }
130            Err(err) => {
131                // propagate the error
132                ControlFlow::Break(Err(err))
133            }
134        }
135    }
136
137    /// Refines the precision and the state space given a culprit of unknown verification result.
138    fn refine(&mut self, culprit: &Culprit) -> Result<(), ExecError> {
139        // subrefine bits until the state space changes.
140        while !self.subrefine(culprit)? {}
141        self.work_state.num_refinements += 1;
142        Ok(())
143    }
144
145    /// Refines a single bit. OK result contains whether the state space changed.
146    fn subrefine(&mut self, culprit: &Culprit) -> Result<bool, ExecError> {
147        let start_instant = if log_enabled!(log::Level::Debug) {
148            Some(Instant::now())
149        } else {
150            None
151        };
152        // compute marking
153        let mut current_state_mark =
154            mck::refin::PanicResult::<<M::Refin as refin::Machine<M>>::State>::clean();
155
156        // TODO: rework panic name kludge
157        if culprit.atomic_property.left().name() == "__panic" {
158            current_state_mark.panic = refin::PanicBitvector::dirty();
159        } else {
160            // TODO: mark more adequately
161            let manip_mark = current_state_mark
162                .result
163                .get_mut(culprit.atomic_property.left().name())
164                .expect("Culprit mark should be manipulatable");
165
166            let manip_mark = if let Some(index) = culprit.atomic_property.left().index() {
167                let Some(indexed_manip_mark) = manip_mark.index_mut(index) else {
168                    panic!("Indexed culprit mark should be indexable");
169                };
170                indexed_manip_mark
171            } else {
172                manip_mark
173            };
174            manip_mark.mark();
175        }
176
177        // try increasing precision of the state preceding current mark
178        let mut iter = culprit.path.iter().cloned().rev().peekable();
179        // store the input precision refinements so that the oldest input can be refined first
180        let mut input_precision_refinement: Option<(
181            NodeId,
182            <<M as FullMachine>::Refin as mck::refin::Machine<M>>::Input,
183        )> = None;
184
185        while let Some(current_state_id) = iter.next() {
186            let previous_node_id = match iter.peek() {
187                Some(previous_state_id) => (*previous_state_id).into(),
188                None => NodeId::ROOT,
189            };
190
191            // decay is applied last in forward direction, so we will apply it first
192            let mut step_precision = self.work_state.step_precision.get(
193                &self.work_state.space,
194                previous_node_id,
195                &self.default_step_precision,
196            );
197
198            if step_precision.apply_refin(&current_state_mark) {
199                // single mark applied to decay, insert it back and regenerate
200                self.work_state.step_precision.insert(
201                    &mut self.work_state.space,
202                    previous_node_id,
203                    step_precision,
204                    &self.default_step_precision,
205                );
206
207                return Ok(self.regenerate(previous_node_id));
208            }
209
210            let input = self
211                .work_state
212                .space
213                .representative_input(previous_node_id, current_state_id);
214
215            let (input_mark, new_state_mark) = match TryInto::<StateId>::try_into(previous_node_id)
216            {
217                Ok(previous_state_id) => {
218                    trace!(
219                        "Finding refinement where original step function was from {:?} to {:?}",
220                        previous_state_id,
221                        current_state_id
222                    );
223                    // use step function
224                    let previous_state = self.work_state.space.state_data(previous_state_id);
225
226                    if log_enabled!(log::Level::Trace) {
227                        trace!("Earlier state: {:?}", previous_state);
228                        let current_state = self.work_state.space.state_data(current_state_id);
229                        trace!("Later state: {:?}", current_state);
230                        trace!("Later mark: {:?}", current_state_mark);
231                    }
232
233                    // the previous state must definitely be non-panicking
234                    let previous_state = &previous_state.result;
235
236                    let (_refinement_machine, new_state_mark, input_mark) = M::Refin::next(
237                        (&self.abstract_system, previous_state, input),
238                        current_state_mark,
239                    );
240
241                    (input_mark, Some(new_state_mark))
242                }
243                Err(_) => {
244                    trace!(
245                        "Finding refinement where original init function was to {:?}",
246                        current_state_id
247                    );
248
249                    if log_enabled!(log::Level::Trace) {
250                        let current_state = self.work_state.space.state_data(current_state_id);
251                        trace!("Later state: {:?}", current_state);
252                        trace!("Later mark: {:?}", current_state_mark);
253                    }
254                    // the current state was generated by the init function
255                    let (_refinement_machine, input_mark) =
256                        M::Refin::init((&self.abstract_system, input), current_state_mark);
257                    (input_mark, None)
258                }
259            };
260
261            let mut input_precision = self.work_state.input_precision.get(
262                &self.work_state.space,
263                previous_node_id,
264                &self.default_input_precision,
265            );
266
267            trace!("Input mark: {:?}", input_mark);
268
269            if input_precision.apply_refin(&input_mark) {
270                // refinement can be applied to input precision, store it
271                if log_enabled!(log::Level::Trace) {
272                    if let Ok(previous_state_id) = previous_node_id.try_into() {
273                        trace!(
274                            "Step candidate id: {:?} node: {:?}, input mark: {:?}",
275                            previous_state_id,
276                            self.work_state.space.state_data(previous_state_id),
277                            input_mark
278                        );
279                    } else {
280                        trace!("Init candidate input mark: {:?}", input_mark);
281                    }
282                }
283
284                // decide if we should replace refinement
285                let replace_refinement =
286                    if let Some(ref input_precision_refinement) = input_precision_refinement {
287                        trace!(
288                            "Candidate importance: {}, refinement importance: {}",
289                            input_precision.importance(),
290                            input_precision_refinement.1.importance()
291                        );
292                        input_precision.importance() >= input_precision_refinement.1.importance()
293                    } else {
294                        true
295                    };
296
297                if replace_refinement {
298                    trace!(
299                        "Replacing refinement with candidate with importance {}: {:?}",
300                        input_precision.importance(),
301                        input_precision
302                    );
303                    input_precision_refinement = Some((previous_node_id, input_precision));
304                }
305            }
306            // mark not applied, continue iteration
307            if let Some(new_state_mark) = new_state_mark {
308                // update current state mark
309                // note that the preceding state could not have panicked
310                current_state_mark = mck::refin::PanicResult {
311                    panic: refin::PanicBitvector::new_unmarked(),
312                    result: new_state_mark,
313                };
314            } else {
315                // we already know the iterator will end
316                // break early as current_state_mark is moved from
317                break;
318            }
319        }
320
321        // if there is an input precision refinement candidate, apply it
322        let result = match input_precision_refinement {
323            Some((node_id, refined_input_precision)) => {
324                // single mark applied, insert it back and regenerate
325                self.work_state.input_precision.insert(
326                    &mut self.work_state.space,
327                    node_id,
328                    refined_input_precision,
329                    &self.default_input_precision,
330                );
331
332                Ok(self.regenerate(node_id))
333            }
334            None => {
335                // cannot apply any refinement, verification incomplete
336                Err(ExecError::Incomplete)
337            }
338        };
339
340        if let Some(start_instant) = start_instant {
341            debug!(
342                "Refinement #{} took {:?}.",
343                self.work_state.num_refinements,
344                start_instant.elapsed()
345            );
346        }
347
348        result
349    }
350
351    /// Regenerates the state space from a given node, keeping its other parts. Returns whether the state space changed.
352    pub fn regenerate(&mut self, from_node_id: NodeId) -> bool {
353        let default_input_precision = &self.default_input_precision;
354        let default_step_precision = &self.default_step_precision;
355
356        let mut queue = VecDeque::new();
357
358        // clear the step from the initial node so it is processed
359        self.work_state.space.clear_step(from_node_id);
360        queue.push_back(from_node_id);
361
362        let mut changed = false;
363        // construct state space by breadth-first search
364        while let Some(node_id) = queue.pop_front() {
365            // if it has already been processed, continue
366            let current_has_direct_successor = self
367                .work_state
368                .space
369                .direct_successor_iter(node_id)
370                .next()
371                .is_some();
372            if current_has_direct_successor {
373                continue;
374            }
375
376            self.work_state.num_generated_states += 1;
377            // remove outgoing edges
378            let removed_direct_successors = self.work_state.space.clear_step(node_id);
379
380            // prepare precision
381            let input_precision: RefinInput<M> = self.work_state.input_precision.get(
382                &self.work_state.space,
383                node_id,
384                default_input_precision,
385            );
386            let step_precision = self.work_state.step_precision.get(
387                &self.work_state.space,
388                node_id,
389                default_step_precision,
390            );
391
392            // get current state, none if we are at start node
393            let current_state = if let Ok(state_id) = StateId::try_from(node_id) {
394                Some(self.work_state.space.state_data(state_id).clone())
395            } else {
396                None
397            };
398
399            // generate direct successors
400            for input in input_precision.into_proto_iter() {
401                // compute the next state
402                let mut next_state = {
403                    if let Some(current_state) = &current_state {
404                        M::Abstr::next(&self.abstract_system, &current_state.result, &input)
405                    } else {
406                        M::Abstr::init(&self.abstract_system, &input)
407                    }
408                };
409
410                // apply decay
411                step_precision.force_decay(&mut next_state);
412
413                // add the step to the state space
414                self.work_state.num_generated_transitions += 1;
415                let next_state_index = self.work_state.space.add_step(node_id, next_state, &input);
416
417                // add the tail to the queue if it has no direct successors yet
418                let next_has_direct_successor = self
419                    .work_state
420                    .space
421                    .direct_successor_iter(next_state_index.into())
422                    .next()
423                    .is_some();
424
425                if !next_has_direct_successor {
426                    // add to queue
427                    queue.push_back(next_state_index.into());
428                }
429            }
430
431            // make sure changed is true if the target nodes are different from the removed ones
432            // ignore the edges changing, currently only used for representative inputs
433            // which has no impact on verification
434            if !changed {
435                // compare sets of node ids
436                let direct_successors: BTreeSet<StateId> = self
437                    .work_state
438                    .space
439                    .direct_successor_iter(node_id)
440                    .collect();
441                changed = direct_successors != removed_direct_successors;
442            }
443        }
444
445        // Each node now should have at least one direct successor.
446        // Assert it to be sure.
447        self.space().assert_left_total();
448
449        changed
450    }
451
452    pub fn check_property_with_labelling(
453        &self,
454        property: &PreparedProperty,
455    ) -> Result<(Conclusion, BTreeMap<StateId, ThreeValued>), ExecError> {
456        model_check::check_property_with_labelling(&self.work_state.space, property)
457    }
458
459    pub fn find_panic_string(&mut self) -> Option<&'static str> {
460        self.work_state.find_panic_string()
461    }
462
463    pub fn reset(&mut self) {
464        // reset the work state
465        self.work_state = WorkState::new()
466    }
467
468    pub fn info(&mut self) -> ExecStats {
469        self.work_state.info()
470    }
471
472    pub fn space(&self) -> &StateSpace<M> {
473        &self.work_state.space
474    }
475
476    pub fn make_compact(&mut self) {
477        self.work_state.make_compact();
478    }
479}