1use log::debug;
4use std::{
5 collections::{HashMap, HashSet},
6 ops::Deref,
7 rc::Rc,
8};
9use taco_display_utils::join_iterator;
10use taco_model_checker::ModelCheckerResult;
11use taco_model_checker::reachability_specification::DisjunctionTargetConfig;
12use taco_smt_encoder::SMTSolverContext;
13use taco_smt_encoder::expression_encoding::EncodeToSMT;
14use taco_smt_encoder::expression_encoding::ctx_mgr::SMTConfigMgr;
15use taco_smt_encoder::expression_encoding::step_ctx::LazyStepContext;
16use taco_smt_encoder::{
17 SMTSolver,
18 expression_encoding::{
19 DeclaresVariable,
20 config_ctx::ConfigCtx,
21 ta_encoding::{encode_initial_constraints, encode_resilience_condition},
22 },
23};
24use taco_threshold_automaton::ThresholdAutomaton;
25
26use crate::acs_threshold_automaton::configuration::ACSTAConfig;
27use crate::{
28 acs_threshold_automaton::ACSThresholdAutomaton,
29 error_graph::{ErrorGraph, NodeRef},
30};
31
32type StepCtx = LazyStepContext<ACSThresholdAutomaton, ConfigCtx>;
34
35pub struct SpuriousGraphChecker {
36 solver: SMTSolver,
37 spec: DisjunctionTargetConfig,
38 cs_ta: Rc<ACSThresholdAutomaton>,
39 ctx_mgr: SMTConfigMgr<StepCtx, ConfigCtx>,
40}
41
42impl SpuriousGraphChecker {
43 pub fn new(
44 ta: &ACSThresholdAutomaton,
45 spec: DisjunctionTargetConfig,
46 mut solver: SMTSolver,
47 ) -> Self {
48 let params = Rc::new(
49 ta.parameters()
50 .map(|p| {
51 let expr = p
52 .declare_variable(&mut solver, 0)
53 .expect("Failed to declare parameter SMT variables");
54 solver
55 .assert(solver.gte(expr, solver.numeral(0)))
56 .expect("Failed to assert param gt 0");
57
58 (p.clone(), expr)
59 })
60 .collect::<HashMap<_, _>>(),
61 );
62
63 let ctx = ConfigCtx::new(&mut solver, ta, params.clone(), 0);
64
65 let rc = encode_resilience_condition(ta, &solver, &ctx);
67 solver
68 .assert(rc)
69 .expect("Failed to assert resilience condition");
70
71 let init_cond = encode_initial_constraints(ta, &solver, &ctx);
73 solver
74 .assert(init_cond)
75 .expect("Failed to assert initial condition");
76
77 let mut ctx_mgr = SMTConfigMgr::new(params);
78 ctx_mgr.push_cfg(Rc::new(ctx));
79
80 Self {
81 solver,
82 ctx_mgr,
83 spec,
84 cs_ta: Rc::new(ta.clone()),
85 }
86 }
87
88 pub fn find_violation(&mut self, e_graph: &ErrorGraph) -> ModelCheckerResult {
89 debug!(
90 "Error graph exploration of ta '{}' and specification '{}' found the following initial configurations, that can potentially reach the error state: {}",
91 self.cs_ta,
92 self.spec,
93 join_iterator(
94 e_graph
95 .initial_leafs
96 .iter()
97 .map(|n| n.borrow().config().display_compact(&self.cs_ta)),
98 ","
99 )
100 );
101
102 for init_node in e_graph.initial_leafs.iter() {
103 let init_cfg = self.ctx_mgr.configs().next().unwrap().clone();
104
105 let res_check_node = self.explore_node_for_violation(init_node, init_cfg, 1);
106
107 if !res_check_node.is_safe() {
108 return res_check_node;
109 }
110 }
111
112 ModelCheckerResult::SAFE
113 }
114
115 fn explore_node_for_violation(
116 &mut self,
117 node: &NodeRef,
118 prev_cfg_ctx: Rc<ConfigCtx>,
119 index: usize,
120 ) -> ModelCheckerResult {
121 self.solver
124 .push()
125 .expect("Failed to push frame for configuration");
126
127 let cfg_constr = node.borrow().config().encode_config_constraints_to_smt(
128 &self.cs_ta,
129 &self.solver,
130 prev_cfg_ctx.deref(),
131 );
132 self.solver
133 .assert(cfg_constr)
134 .expect("Failed to assert configuration constraints");
135
136 debug!(
137 "Exploring configuration: {}",
138 node.borrow().config().display_compact(&self.cs_ta)
139 );
140
141 if node.borrow().error_level() > 0
143 && !self
144 .solver
145 .assert_and_check_expr(self.solver.true_())
146 .expect("Failed to check spurious")
147 .is_sat()
148 {
149 self.solver.pop().expect("Failed to pop frame");
150 debug!("Explored path was spurious");
151 return ModelCheckerResult::SAFE;
152 }
153
154 let cfg_primed_ctx = Rc::new(ConfigCtx::new(
156 &mut self.solver,
157 self.cs_ta.deref(),
158 self.ctx_mgr.params().clone(),
159 index,
160 ));
161 self.ctx_mgr.push_cfg_primed(cfg_primed_ctx.clone());
162
163 let mut visited_nodes_cache = HashSet::<ACSTAConfig>::new();
166 let (s, step_nodes, contains_err_state) =
167 Self::collect_enabled_rules_and_next_nodes(node, &mut visited_nodes_cache);
168 debug!(
169 "Steady step with rules : {}",
170 join_iterator(s.iter().map(|u| format!("'{u}'")), ",")
171 );
172
173 let steady_ctx: LazyStepContext<ACSThresholdAutomaton, ConfigCtx> = self
175 .encode_and_assert_steady_path_with_rules(
176 &s,
177 prev_cfg_ctx,
178 cfg_primed_ctx.clone(),
179 index,
180 );
181 self.ctx_mgr.push_steady(steady_ctx);
182
183 for node in step_nodes {
184 self.solver.push().expect("Failed to push frame");
186
187 let res = self.explore_next_interval_state_transition(
188 &node,
189 cfg_primed_ctx.clone(),
190 index + 1,
191 );
192 if !res.is_safe() {
193 return res;
194 }
195
196 self.solver.pop().expect("Failed to pop frame");
197 }
198
199 if node.borrow().error_level() == 0 || (contains_err_state) {
201 let res = self.check_spurious_error(&cfg_primed_ctx);
202 if !res.is_safe() {
203 return res;
204 }
205 }
206
207 self.solver
209 .pop()
210 .expect("Failed to pop frame for configuration");
211 self.ctx_mgr.pop_steady();
212 self.ctx_mgr.pop_cfg_primed();
213
214 ModelCheckerResult::SAFE
215 }
216
217 fn explore_next_interval_state_transition(
218 &mut self,
219 next_node: &NodeRef,
220 prev_cfg: Rc<ConfigCtx>,
221 index: usize,
222 ) -> ModelCheckerResult {
223 let node = next_node.borrow();
224 let is = node.config().interval_state().clone();
225
226 let nxt_cfg = Rc::new(ConfigCtx::new(
228 &mut self.solver,
229 self.cs_ta.deref(),
230 self.ctx_mgr.params().clone(),
231 index,
232 ));
233 self.ctx_mgr.push_cfg(nxt_cfg.clone());
234
235 for next_edge in node
237 .parents()
238 .chain(node.back_edges())
239 .filter(|e| e.node().borrow().config().interval_state() != &is)
240 {
241 self.solver.push().expect("Failed to push context");
242
243 debug!(
244 "Exploring step to configuration '{}' using '{}'",
245 next_edge
246 .node()
247 .borrow()
248 .config()
249 .display_compact(&self.cs_ta),
250 join_iterator(next_edge.get_ids_of_rules(), ",")
251 );
252
253 let cfg_constr = node.config().encode_config_constraints_to_smt(
254 &self.cs_ta,
255 &self.solver,
256 prev_cfg.deref(),
257 );
258 self.solver
259 .assert(cfg_constr)
260 .expect("Failed to assert configuration constraints");
261
262 let rules = next_edge
263 .get_ids_of_rules()
264 .map(|id| {
265 self.cs_ta
266 .get_rule_by_id(id)
267 .expect("Failed to find rule id")
268 })
269 .cloned();
270
271 let step_ctx = LazyStepContext::new(
272 rules,
273 self.cs_ta.clone(),
274 prev_cfg.clone(),
275 nxt_cfg.clone(),
276 index,
277 &mut self.solver,
278 );
279 self.ctx_mgr.push_step(step_ctx.clone());
280
281 let step_constr = step_ctx.encode_phi_step(&self.solver);
282 self.solver
283 .assert(step_constr)
284 .expect("Failed to assert step constraints");
285
286 let res = self.explore_node_for_violation(next_edge.node(), nxt_cfg.clone(), index + 1);
287 if !res.is_safe() {
288 return res;
289 }
290
291 self.solver
292 .pop()
293 .expect("Failed to pop solver context after exploring step");
294 self.ctx_mgr.pop_step();
295 }
296
297 self.ctx_mgr.pop_cfg();
298
299 ModelCheckerResult::SAFE
300 }
301
302 fn encode_and_assert_steady_path_with_rules(
303 &mut self,
304 appl_rules: &HashSet<u32>,
305 prev_cfg: Rc<ConfigCtx>,
306 next_cfg: Rc<ConfigCtx>,
307 index: usize,
308 ) -> StepCtx {
309 let rules_to_appl = appl_rules
310 .iter()
311 .map(|id| {
312 self.cs_ta
313 .get_rule_by_id(*id)
314 .expect("Failed to find rule id")
315 })
316 .cloned();
317
318 let step_ctx = LazyStepContext::new(
319 rules_to_appl,
320 self.cs_ta.clone(),
321 prev_cfg,
322 next_cfg,
323 index,
324 &mut self.solver,
325 );
326
327 let steady_path = step_ctx.encode_phi_steady(&self.solver);
328
329 self.solver
330 .assert(steady_path)
331 .expect("Failed to assert steady condition");
332
333 step_ctx
334 }
335
336 fn collect_enabled_rules_and_next_nodes(
345 org_node: &NodeRef,
346 visited_nodes: &mut HashSet<ACSTAConfig>,
347 ) -> (HashSet<u32>, Vec<NodeRef>, bool) {
348 let mut next_is = Vec::new();
349 let mut applicable_rules = HashSet::new();
350 let mut s_contains_err_state = false;
351
352 let is = org_node.borrow().config().interval_state().clone();
353 let node = org_node.borrow();
354
355 visited_nodes.insert(node.config().clone());
357
358 applicable_rules.extend(node.self_loops());
360
361 let mut exists_new_is = false;
362
363 for successor_edge in node.parents().chain(node.back_edges()) {
364 if successor_edge.node().borrow().config() == node.config() {
366 applicable_rules.extend(successor_edge.get_ids_of_rules());
367 continue;
368 }
369
370 if successor_edge.node().borrow().config().interval_state() == &is {
373 if successor_edge.node().borrow().error_level() == 0 {
375 s_contains_err_state = true;
376 }
377
378 applicable_rules.extend(successor_edge.get_ids_of_rules());
379
380 if !visited_nodes.contains(successor_edge.node().borrow().config()) {
381 let (s, nxt, suc_s_contains_err_state) =
383 Self::collect_enabled_rules_and_next_nodes(
384 successor_edge.node(),
385 visited_nodes,
386 );
387 s_contains_err_state |= suc_s_contains_err_state;
388 next_is.extend(nxt);
389 applicable_rules.extend(s);
390 }
391
392 continue;
393 }
394
395 exists_new_is = true;
396 }
397
398 if exists_new_is {
399 next_is.push(org_node.clone());
400 }
401
402 (applicable_rules, next_is, s_contains_err_state)
403 }
404
405 fn check_spurious_error(&mut self, ctx: &ConfigCtx) -> ModelCheckerResult {
408 self.solver
410 .push()
411 .expect("Failed to push frame for err cond");
412
413 let err_cond = self
414 .spec
415 .encode_to_smt_with_ctx(&self.solver, ctx)
416 .expect("Failed to encode error condition");
417
418 let res = self
419 .solver
420 .assert_and_check_expr(err_cond)
421 .expect("Failed to check spurious");
422
423 if res.is_sat() {
424 let path = self.ctx_mgr.extract_error_path(
425 res,
426 &mut self.solver,
427 self.cs_ta.get_interval_ta().get_ta().clone(),
428 );
429 return ModelCheckerResult::UNSAFE(vec![(
430 self.spec.name().to_string(),
431 Box::new(path),
432 )]);
433 }
434
435 self.solver
436 .pop()
437 .expect("Failed to pop frame for error condition");
438
439 debug!("Checked for error, but error has not been reached.");
440
441 ModelCheckerResult::SAFE
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use std::collections::HashMap;
448
449 use taco_interval_ta::IntervalThresholdAutomaton;
450 use taco_model_checker::{
451 ModelCheckerResult, TATrait, reachability_specification::TargetConfig,
452 };
453 use taco_parser::{ParseTA, bymc::ByMCParser};
454 use taco_smt_encoder::SMTSolverBuilder;
455 use taco_threshold_automaton::{
456 expressions::{
457 BooleanConnective, BooleanExpression, ComparisonOp, IntegerExpression, Location,
458 Parameter, Variable,
459 },
460 general_threshold_automaton::{Action, UpdateExpression, builder::RuleBuilder},
461 path::{Configuration, PathBuilder, Transition},
462 };
463
464 use crate::{acs_threshold_automaton::ACSThresholdAutomaton, error_graph::ErrorGraph};
465
466 #[test]
467 fn test_initial_error_condition_fullfilled() {
468 let test_spec = "
469 skel test_ta1 {
470 shared var1, var2, var3;
471 parameters n, f;
472
473 assumptions (1) {
474 n == 1;
475 }
476
477 locations (2) {
478 loc1 : [0];
479 loc2 : [1];
480 loc3 : [2];
481 }
482
483 inits (1) {
484 loc1 == 0;
485 loc2 == n;
486 loc3 == 0;
487 var1 == 0;
488 var2 == 0;
489 var3 == 0;
490 }
491
492 rules (4) {
493 0: loc1 -> loc2
494 when(true)
495 do {};
496 }
497
498 specifications (1) {
499 test1:
500 [](loc2 == 0)
501 }
502 }
503 ";
504
505 let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
506 let solver_builder = SMTSolverBuilder::default();
507
508 let spec = TargetConfig::new_cover([Location::new("loc2")])
509 .unwrap()
510 .into_disjunct_with_name("test");
511
512 let test_tas =
513 IntervalThresholdAutomaton::try_from_general_ta(ta.clone(), &solver_builder, &spec)
514 .unwrap();
515 assert_eq!(test_tas.len(), 1);
516
517 let test_ta = test_tas[0].clone();
518
519 let cs_ta = ACSThresholdAutomaton::from(test_ta);
520
521 let path = PathBuilder::new(ta)
522 .add_parameter_assignment(HashMap::from([
523 (Parameter::new("n"), 1),
524 (Parameter::new("f"), 0),
525 ]))
526 .unwrap()
527 .add_configuration(Configuration::new(
528 HashMap::from([
529 (Variable::new("var1"), 0),
530 (Variable::new("var2"), 0),
531 (Variable::new("var3"), 0),
532 ]),
533 HashMap::from([
534 (Location::new("loc1"), 0),
535 (Location::new("loc2"), 1),
536 (Location::new("loc3"), 0),
537 ]),
538 ))
539 .unwrap()
540 .build()
541 .unwrap();
542
543 let e_graph = ErrorGraph::compute_error_graph(spec, cs_ta);
544 assert!(!e_graph.is_empty());
545
546 let ctx = SMTSolverBuilder::default();
547 let res = e_graph.check_for_non_spurious_counter_example(ctx.new_solver());
548 assert!(!res.is_safe());
549
550 let res = match res {
551 ModelCheckerResult::SAFE => unreachable!("checked above"),
552 ModelCheckerResult::UNSAFE(v) => {
553 assert_eq!(v.len(), 1);
554 *v[0].1.clone()
555 }
556 ModelCheckerResult::UNKNOWN(_) => todo!(),
557 };
558
559 assert_eq!(
560 res,
561 path.clone(),
562 "Got:\n{}\n\nExpected:\n{}\n\n",
563 res,
564 path
565 );
566 }
567
568 #[test]
569 fn test_spec_reach_location_one_rule_one_process_no_guards() {
570 let test_spec = "
571 skel test_ta1 {
572 shared var1, var2, var3;
573 parameters n, f;
574
575 assumptions (1) {
576 n == 1;
577 }
578
579 locations (2) {
580 loc1 : [0];
581 loc2 : [1];
582 loc3 : [2];
583 }
584
585 inits (1) {
586 loc1 == 1;
587 loc2 == 0;
588 loc3 == 0;
589 var1 == 0;
590 var2 == 0;
591 var3 == 0;
592 }
593
594 rules (4) {
595 0: loc1 -> loc2
596 when(true)
597 do {};
598 }
599
600 specifications (1) {
601 test1:
602 [](loc2 == 0)
603 }
604 }
605 ";
606
607 let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
608 let solver_builder = SMTSolverBuilder::default();
609
610 let spec = TargetConfig::new_cover([Location::new("loc2")])
611 .unwrap()
612 .into_disjunct_with_name("test");
613
614 let test_tas =
615 IntervalThresholdAutomaton::try_from_general_ta(ta.clone(), &solver_builder, &spec)
616 .unwrap();
617 assert_eq!(test_tas.len(), 1);
618
619 let test_ta = test_tas[0].clone();
620
621 let cs_ta = ACSThresholdAutomaton::from(test_ta);
622
623 let path = PathBuilder::new(ta)
624 .add_parameter_assignment(HashMap::from([
625 (Parameter::new("n"), 1),
626 (Parameter::new("f"), 0),
627 ]))
628 .unwrap()
629 .add_configuration(Configuration::new(
630 HashMap::from([
631 (Variable::new("var1"), 0),
632 (Variable::new("var2"), 0),
633 (Variable::new("var3"), 0),
634 ]),
635 HashMap::from([
636 (Location::new("loc1"), 1),
637 (Location::new("loc2"), 0),
638 (Location::new("loc3"), 0),
639 ]),
640 ))
641 .unwrap()
642 .add_transition(Transition::new(
643 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
644 1,
645 ))
646 .unwrap()
647 .add_configuration(Configuration::new(
648 HashMap::from([
649 (Variable::new("var1"), 0),
650 (Variable::new("var2"), 0),
651 (Variable::new("var3"), 0),
652 ]),
653 HashMap::from([
654 (Location::new("loc1"), 0),
655 (Location::new("loc2"), 1),
656 (Location::new("loc3"), 0),
657 ]),
658 ))
659 .unwrap()
660 .build()
661 .unwrap();
662
663 let e_graph = ErrorGraph::compute_error_graph(spec, cs_ta);
664 assert!(!e_graph.is_empty());
665
666 let ctx = SMTSolverBuilder::default();
667 let res = e_graph.check_for_non_spurious_counter_example(ctx.new_solver());
668 assert!(!res.is_safe());
669
670 let res = match res {
671 ModelCheckerResult::SAFE => unreachable!("checked above"),
672 ModelCheckerResult::UNSAFE(v) => {
673 assert_eq!(v.len(), 1);
674 *v[0].1.clone()
675 }
676 ModelCheckerResult::UNKNOWN(_) => todo!(),
677 };
678
679 assert_eq!(
680 res,
681 path.clone(),
682 "Got:\n{}\n\nExpected:\n{}\n\n",
683 res,
684 path
685 );
686 }
687
688 #[test]
689 fn test_spec_reach_location_one_rule_5_process_no_guards() {
690 let test_spec = "
691 skel test_ta1 {
692 shared var1, var2, var3;
693 parameters n, f;
694
695 assumptions (1) {
696 n > 3 * f;
697 n == 5;
698 f == 0;
699 }
700
701 locations (2) {
702 loc1 : [0];
703 loc2 : [1];
704 loc3 : [2];
705 }
706
707 inits (1) {
708 loc1 == n - f;
709 loc2 == 0;
710 loc3 == 0;
711 var1 == 0;
712 var2 == 0;
713 var3 == 0;
714 }
715
716 rules (4) {
717 0: loc1 -> loc2
718 when(true)
719 do {};
720 }
721
722 specifications (1) {
723 test1:
724 [](loc2 == 0)
725 }
726 }
727 ";
728
729 let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
730 let solver_builder = SMTSolverBuilder::default();
731
732 let spec = TargetConfig::new_general_cover([(Location::new("loc2"), 5)])
733 .unwrap()
734 .into_disjunct_with_name("test");
735
736 let test_tas =
737 IntervalThresholdAutomaton::try_from_general_ta(ta.clone(), &solver_builder, &spec)
738 .unwrap();
739 assert_eq!(test_tas.len(), 1);
740
741 let test_ta = test_tas[0].clone();
742
743 let cs_ta = ACSThresholdAutomaton::from(test_ta);
744
745 let path = PathBuilder::new(ta)
746 .add_parameter_assignment(HashMap::from([
747 (Parameter::new("n"), 5),
748 (Parameter::new("f"), 0),
749 ]))
750 .unwrap()
751 .add_configuration(Configuration::new(
752 HashMap::from([
753 (Variable::new("var1"), 0),
754 (Variable::new("var2"), 0),
755 (Variable::new("var3"), 0),
756 ]),
757 HashMap::from([
758 (Location::new("loc1"), 5),
759 (Location::new("loc2"), 0),
760 (Location::new("loc3"), 0),
761 ]),
762 ))
763 .unwrap()
764 .add_transition(Transition::new(
765 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
766 5,
767 ))
768 .unwrap()
769 .add_configuration(Configuration::new(
770 HashMap::from([
771 (Variable::new("var1"), 0),
772 (Variable::new("var2"), 0),
773 (Variable::new("var3"), 0),
774 ]),
775 HashMap::from([
776 (Location::new("loc1"), 0),
777 (Location::new("loc2"), 5),
778 (Location::new("loc3"), 0),
779 ]),
780 ))
781 .unwrap()
782 .build()
783 .unwrap();
784 let e_graph = ErrorGraph::compute_error_graph(spec, cs_ta);
785 assert!(!e_graph.is_empty());
786
787 let ctx = SMTSolverBuilder::default();
788 let res = e_graph.check_for_non_spurious_counter_example(ctx.new_solver());
789 assert!(!res.is_safe());
790
791 let res = match res {
792 ModelCheckerResult::SAFE => unreachable!("checked above"),
793 ModelCheckerResult::UNSAFE(v) => {
794 assert_eq!(v.len(), 1);
795 *v[0].1.clone()
796 }
797 ModelCheckerResult::UNKNOWN(_) => todo!(),
798 };
799
800 assert_eq!(
801 res,
802 path.clone(),
803 "Got:\n{}\n\nExpected:\n{}\n\n",
804 res,
805 path
806 );
807 }
808
809 #[test]
810 fn test_spec_unreachable_location_graph() {
811 let test_spec = "
812 skel test_ta1 {
813 shared var1, var2, var3;
814 parameters n, t, f;
815
816 assumptions (1) {
817 n > 3 * f;
818 }
819
820 locations (2) {
821 loc1 : [0];
822 loc2 : [1];
823 loc3 : [2];
824 }
825
826 inits (1) {
827 loc1 == n - f;
828 loc2 == 0;
829 loc3 == 0;
830 var1 == 0;
831 var2 == 0;
832 var3 == 0;
833 }
834
835 rules (4) {
836 0: loc1 -> loc2
837 when(true)
838 do {};
839 }
840
841 specifications (1) {
842 test1:
843 [](loc3 == 0)
844 }
845 }
846 ";
847
848 let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
849 let solver_builder = SMTSolverBuilder::default();
850
851 let spec = TargetConfig::new_cover([Location::new("loc3")])
852 .unwrap()
853 .into_disjunct_with_name("test");
854
855 let test_tas =
856 IntervalThresholdAutomaton::try_from_general_ta(ta.clone(), &solver_builder, &spec)
857 .unwrap();
858 assert_eq!(test_tas.len(), 1);
859
860 let test_ta = test_tas[0].clone();
861
862 let cs_ta = ACSThresholdAutomaton::from(test_ta);
863
864 let e_graph = ErrorGraph::compute_error_graph(spec, cs_ta);
865 assert!(e_graph.is_empty());
866
867 let ctx = SMTSolverBuilder::default();
868 let res = e_graph.check_for_non_spurious_counter_example(ctx.new_solver());
869 assert!(res.is_safe());
870
871 assert_eq!(res, ModelCheckerResult::SAFE);
872 }
873
874 #[test]
875 fn test_spec_reach_location_two_rules_one_guard() {
876 let test_spec = "
877 skel test_ta1 {
878 shared var1, var2, var3;
879 parameters n, f;
880
881 assumptions (1) {
882 n > 3 * f;
883 n == 5;
884 f == 0;
885 }
886
887 locations (2) {
888 loc1 : [0];
889 loc2 : [1];
890 loc3 : [2];
891 }
892
893 inits (1) {
894 loc1 == n - f;
895 loc2 == 0;
896 loc3 == 0;
897 var1 == 0;
898 var2 == 0;
899 var3 == 0;
900 }
901
902 rules (4) {
903 0: loc1 -> loc2
904 when(var1 >= 4 && var2 <= 0)
905 do {};
906 1: loc1 -> loc3
907 when(true)
908 do {var1' == var1 + 1};
909 }
910
911 specifications (1) {
912 test1:
913 [](loc2 == 0)
914 }
915 }
916 ";
917
918 let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
919 let solver_builder = SMTSolverBuilder::default();
920
921 let spec = TargetConfig::new_cover([Location::new("loc2")])
922 .unwrap()
923 .into_disjunct_with_name("test");
924
925 let test_tas =
926 IntervalThresholdAutomaton::try_from_general_ta(ta.clone(), &solver_builder, &spec)
927 .unwrap();
928 assert_eq!(test_tas.len(), 1);
929
930 let test_ta = test_tas[0].clone();
931
932 let cs_ta = ACSThresholdAutomaton::from(test_ta);
933
934 let path = PathBuilder::new(ta)
935 .add_parameter_assignment(HashMap::from([
936 (Parameter::new("n"), 5),
937 (Parameter::new("f"), 0),
938 ]))
939 .unwrap()
940 .add_configuration(Configuration::new(
941 HashMap::from([
942 (Variable::new("var1"), 0),
943 (Variable::new("var2"), 0),
944 (Variable::new("var3"), 0),
945 ]),
946 HashMap::from([
947 (Location::new("loc1"), 5),
948 (Location::new("loc2"), 0),
949 (Location::new("loc3"), 0),
950 ]),
951 ))
952 .unwrap()
953 .add_transition(Transition::new(
954 RuleBuilder::new(1, Location::new("loc1"), Location::new("loc3"))
955 .with_action(Action::new_with_update(
956 Variable::new("var1"),
957 UpdateExpression::Inc(1),
958 ))
959 .build(),
960 1,
961 ))
962 .unwrap()
963 .add_configuration(Configuration::new(
964 HashMap::from([
965 (Variable::new("var1"), 1),
966 (Variable::new("var2"), 0),
967 (Variable::new("var3"), 0),
968 ]),
969 HashMap::from([
970 (Location::new("loc1"), 4),
971 (Location::new("loc2"), 0),
972 (Location::new("loc3"), 1),
973 ]),
974 ))
975 .unwrap()
976 .add_transition(Transition::new(
977 RuleBuilder::new(1, Location::new("loc1"), Location::new("loc3"))
978 .with_action(Action::new_with_update(
979 Variable::new("var1"),
980 UpdateExpression::Inc(1),
981 ))
982 .build(),
983 2,
984 ))
985 .unwrap()
986 .add_configuration(Configuration::new(
987 HashMap::from([
988 (Variable::new("var1"), 3),
989 (Variable::new("var2"), 0),
990 (Variable::new("var3"), 0),
991 ]),
992 HashMap::from([
993 (Location::new("loc1"), 2),
994 (Location::new("loc2"), 0),
995 (Location::new("loc3"), 3),
996 ]),
997 ))
998 .unwrap()
999 .add_transition(Transition::new(
1000 RuleBuilder::new(1, Location::new("loc1"), Location::new("loc3"))
1001 .with_action(Action::new_with_update(
1002 Variable::new("var1"),
1003 UpdateExpression::Inc(1),
1004 ))
1005 .build(),
1006 1,
1007 ))
1008 .unwrap()
1009 .add_configuration(Configuration::new(
1010 HashMap::from([
1011 (Variable::new("var1"), 4),
1012 (Variable::new("var2"), 0),
1013 (Variable::new("var3"), 0),
1014 ]),
1015 HashMap::from([
1016 (Location::new("loc1"), 1),
1017 (Location::new("loc2"), 0),
1018 (Location::new("loc3"), 4),
1019 ]),
1020 ))
1021 .unwrap()
1022 .add_transition(Transition::new(
1023 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
1024 .with_guard(BooleanExpression::BinaryExpression(
1025 Box::new(BooleanExpression::ComparisonExpression(
1026 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1027 ComparisonOp::Geq,
1028 Box::new(IntegerExpression::Const(4)),
1029 )),
1030 BooleanConnective::And,
1031 Box::new(BooleanExpression::ComparisonExpression(
1032 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1033 ComparisonOp::Leq,
1034 Box::new(IntegerExpression::Const(0)),
1035 )),
1036 ))
1037 .build(),
1038 1,
1039 ))
1040 .unwrap()
1041 .add_configuration(Configuration::new(
1042 HashMap::from([
1043 (Variable::new("var1"), 4),
1044 (Variable::new("var2"), 0),
1045 (Variable::new("var3"), 0),
1046 ]),
1047 HashMap::from([
1048 (Location::new("loc1"), 0),
1049 (Location::new("loc2"), 1),
1050 (Location::new("loc3"), 4),
1051 ]),
1052 ))
1053 .unwrap()
1054 .build()
1055 .unwrap();
1056
1057 let e_graph = ErrorGraph::compute_error_graph(spec, cs_ta);
1058 assert!(!e_graph.is_empty());
1059
1060 let ctx = SMTSolverBuilder::default();
1061 let res = e_graph.check_for_non_spurious_counter_example(ctx.new_solver());
1062 assert!(!res.is_safe());
1063
1064 let res = match res {
1065 ModelCheckerResult::SAFE => unreachable!("checked above"),
1066 ModelCheckerResult::UNSAFE(v) => {
1067 assert_eq!(v.len(), 1);
1068 *v[0].1.clone()
1069 }
1070 ModelCheckerResult::UNKNOWN(_) => todo!(),
1071 };
1072
1073 assert_eq!(
1074 res,
1075 path.clone(),
1076 "Got:\n{}\n\nExpected:\n{}\n\n",
1077 res,
1078 path
1079 );
1080 }
1081
1082 #[test]
1083 fn test_spec_reach_location_three_rules_one_guard_self_loop() {
1084 let test_spec = "
1085 skel test_ta1 {
1086 shared var1, var2, var3;
1087 parameters n, f;
1088
1089 assumptions (1) {
1090 n > 3 * f;
1091 n == 1;
1092 f == 0;
1093 }
1094
1095 locations (2) {
1096 loc1 : [0];
1097 loc2 : [1];
1098 loc3 : [2];
1099 }
1100
1101 inits (1) {
1102 loc1 == n - f;
1103 loc2 == 0;
1104 loc3 == 0;
1105 var1 == 0;
1106 var2 == 0;
1107 var3 == 0;
1108 }
1109
1110 rules (4) {
1111 0: loc1 -> loc1
1112 when(true)
1113 do {var1' == var1 + 1};
1114 1: loc1 -> loc2
1115 when(true)
1116 do {};
1117 2: loc2 -> loc3
1118 when(var1 > 3 && var2 <= 0 && var1 <= 4)
1119 do {};
1120
1121 }
1122
1123 specifications (1) {
1124 test1:
1125 [](loc3 == 0)
1126 }
1127 }
1128 ";
1129
1130 let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
1131 let solver_builder = SMTSolverBuilder::default();
1132
1133 let spec = TargetConfig::new_cover([Location::new("loc3")])
1134 .unwrap()
1135 .into_disjunct_with_name("test");
1136
1137 let test_tas =
1138 IntervalThresholdAutomaton::try_from_general_ta(ta.clone(), &solver_builder, &spec)
1139 .unwrap();
1140 assert_eq!(test_tas.len(), 1);
1141
1142 let test_ta = test_tas[0].clone();
1143
1144 let cs_ta = ACSThresholdAutomaton::from(test_ta);
1145
1146 let path = PathBuilder::new(ta)
1147 .add_parameter_assignment(HashMap::from([
1148 (Parameter::new("n"), 1),
1149 (Parameter::new("f"), 0),
1150 ]))
1151 .unwrap()
1152 .add_configuration(Configuration::new(
1153 HashMap::from([
1154 (Variable::new("var1"), 0),
1155 (Variable::new("var2"), 0),
1156 (Variable::new("var3"), 0),
1157 ]),
1158 HashMap::from([
1159 (Location::new("loc1"), 1),
1160 (Location::new("loc2"), 0),
1161 (Location::new("loc3"), 0),
1162 ]),
1163 ))
1164 .unwrap()
1165 .add_transition(Transition::new(
1166 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc1"))
1167 .with_action(Action::new_with_update(
1168 Variable::new("var1"),
1169 UpdateExpression::Inc(1),
1170 ))
1171 .build(),
1172 1,
1173 ))
1174 .unwrap()
1175 .add_configuration(Configuration::new(
1176 HashMap::from([
1177 (Variable::new("var1"), 1),
1178 (Variable::new("var2"), 0),
1179 (Variable::new("var3"), 0),
1180 ]),
1181 HashMap::from([
1182 (Location::new("loc1"), 1),
1183 (Location::new("loc2"), 0),
1184 (Location::new("loc3"), 0),
1185 ]),
1186 ))
1187 .unwrap()
1188 .add_transition(Transition::new(
1189 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc1"))
1190 .with_action(Action::new_with_update(
1191 Variable::new("var1"),
1192 UpdateExpression::Inc(1),
1193 ))
1194 .build(),
1195 2,
1196 ))
1197 .unwrap()
1198 .add_configuration(Configuration::new(
1199 HashMap::from([
1200 (Variable::new("var1"), 3),
1201 (Variable::new("var2"), 0),
1202 (Variable::new("var3"), 0),
1203 ]),
1204 HashMap::from([
1205 (Location::new("loc1"), 1),
1206 (Location::new("loc2"), 0),
1207 (Location::new("loc3"), 0),
1208 ]),
1209 ))
1210 .unwrap()
1211 .add_transition(Transition::new(
1212 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc1"))
1213 .with_action(Action::new_with_update(
1214 Variable::new("var1"),
1215 UpdateExpression::Inc(1),
1216 ))
1217 .build(),
1218 1,
1219 ))
1220 .unwrap()
1221 .add_configuration(Configuration::new(
1222 HashMap::from([
1223 (Variable::new("var1"), 4),
1224 (Variable::new("var2"), 0),
1225 (Variable::new("var3"), 0),
1226 ]),
1227 HashMap::from([
1228 (Location::new("loc1"), 1),
1229 (Location::new("loc2"), 0),
1230 (Location::new("loc3"), 0),
1231 ]),
1232 ))
1233 .unwrap()
1234 .add_transition(Transition::new(
1235 RuleBuilder::new(1, Location::new("loc1"), Location::new("loc2")).build(),
1236 1,
1237 ))
1238 .unwrap()
1239 .add_configuration(Configuration::new(
1240 HashMap::from([
1241 (Variable::new("var1"), 4),
1242 (Variable::new("var2"), 0),
1243 (Variable::new("var3"), 0),
1244 ]),
1245 HashMap::from([
1246 (Location::new("loc1"), 0),
1247 (Location::new("loc2"), 1),
1248 (Location::new("loc3"), 0),
1249 ]),
1250 ))
1251 .unwrap()
1252 .add_transition(Transition::new(
1253 RuleBuilder::new(2, Location::new("loc2"), Location::new("loc3"))
1254 .with_guard(
1255 BooleanExpression::ComparisonExpression(
1256 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1257 ComparisonOp::Gt,
1258 Box::new(IntegerExpression::Const(3)),
1259 ) & BooleanExpression::ComparisonExpression(
1260 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1261 ComparisonOp::Leq,
1262 Box::new(IntegerExpression::Const(0)),
1263 ) & BooleanExpression::ComparisonExpression(
1264 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1265 ComparisonOp::Leq,
1266 Box::new(IntegerExpression::Const(4)),
1267 ),
1268 )
1269 .build(),
1270 1,
1271 ))
1272 .unwrap()
1273 .add_configuration(Configuration::new(
1274 HashMap::from([
1275 (Variable::new("var1"), 4),
1276 (Variable::new("var2"), 0),
1277 (Variable::new("var3"), 0),
1278 ]),
1279 HashMap::from([
1280 (Location::new("loc1"), 0),
1281 (Location::new("loc2"), 0),
1282 (Location::new("loc3"), 1),
1283 ]),
1284 ))
1285 .unwrap()
1286 .build()
1287 .unwrap();
1288
1289 let e_graph = ErrorGraph::compute_error_graph(spec, cs_ta);
1290 assert!(!e_graph.is_empty());
1291
1292 let ctx = SMTSolverBuilder::default();
1293 let res = e_graph.check_for_non_spurious_counter_example(ctx.new_solver());
1294 assert!(!res.is_safe());
1295
1296 let res = match res {
1297 ModelCheckerResult::SAFE => unreachable!("checked above"),
1298 ModelCheckerResult::UNSAFE(v) => {
1299 assert_eq!(v.len(), 1);
1300 *v[0].1.clone()
1301 }
1302 ModelCheckerResult::UNKNOWN(_) => todo!(),
1303 };
1304
1305 assert_eq!(
1306 res,
1307 path.clone(),
1308 "Got:\n{}\n\nExpected:\n{}\n\n",
1309 res,
1310 path
1311 );
1312 }
1313}