def edge_covering_checker(p: Phase) -> Optional[Tuple[Phase, Diagram]]: t = self.solver.get_translator(KEY_NEW, KEY_OLD) f = self.fs[-1] prog = syntax.the_program with self.solver: for c in f.summary_of(p): self.solver.add(t.translate_expr(c, old=True)) transitions_from_phase = self.automaton.transitions_from(p) for trans in prog.transitions(): edges_from_phase_matching_prog_trans = [ t for t in transitions_from_phase if t.prog_transition_name() == trans.name ] if any(delta.precond is None for delta in edges_from_phase_matching_prog_trans): utils.logger.debug( 'transition %s is covered trivially by %s' % (trans.name, p.name())) continue utils.logger.debug( 'checking transition %s is covered by %s' % (trans.name, p.name())) with self.solver: self.solver.add(t.translate_transition(trans)) preconds = ( z3.Not( t.translate_precond_of_transition( delta.precond(), trans)) for delta in edges_from_phase_matching_prog_trans) self.solver.add(z3.And(*preconds)) if self.solver.check() != z3.unsat: utils.logger.debug( 'phase %s cex to edge covering of transition %s' % (p.name(), trans.name)) z3m: z3.ModelRef = self.solver.model() mod = Trace.from_z3([KEY_OLD, KEY_NEW], z3m) self.record_state(mod) diag = mod.as_diagram(i=0) return (p, diag) utils.logger.debug( 'transition %s is covered non-trivially by %s' % (trans.name, p.name())) continue utils.logger.debug('all edges covered from phase %s' % p.name()) return None
def safety_property_checker( p: Phase) -> Optional[Tuple[Phase, Diagram]]: res = logic.check_implication(self.solver, f.summary_of(p), (inv.expr for inv in phases.phase_safety(p))) if res is None: utils.logger.debug( "Frontier frame phase %s implies safety, summary is %s" % (p.name(), f.summary_of(p))) return None utils.logger.debug("Frontier frame phase %s cex to safety" % p.name()) z3m: z3.ModelRef = res mod = Trace.from_z3([KEY_ONE], z3m) self.record_state(mod) diag = mod.as_diagram() return (p, diag)
def add(self, p: Phase, e: Expr, depth: Optional[int] = None) -> None: self.counter += 1 self.record_predicate(e) if depth is None: depth = len(self) if utils.args.smoke_test and utils.logger.isEnabledFor(logging.DEBUG): utils.logger.debug('smoke testing at depth %s...' % (depth, )) logic.check_bmc(self.solver, e, depth) self.debug_assert_inductive_trace() for i in range(depth + 1): self[i].strengthen(p, e) utils.logger.debug("%d %s %s" % (i, p.name(), e)) self.debug_assert_inductive_trace() self.debug_assert_inductive_trace()
def find_predecessor( self, pre_frame: Frame, current_phase: Phase, diag: Diagram ) -> Tuple[z3.CheckSatResult, Union[Optional[MySet[int]], Tuple[ PhaseTransition, Tuple[Phase, Diagram]]]]: t = self.solver.get_translator(KEY_NEW, KEY_OLD) if utils.args.use_z3_unsat_cores: core: Optional[MySet[int]] = MySet() else: core = None with self.solver: with self.solver.mark_assumptions_necessary(): self.solver.add(diag.to_z3(t)) transitions_into = self.automaton.transitions_to_grouped_by_src( current_phase) for src in self._predecessor_precedence( current_phase, list(transitions_into.keys())): transitions = transitions_into[src] assert transitions utils.logger.debug( "check predecessor of %s from %s by %s" % (current_phase.name(), src.name(), transitions)) (sat_res, pre_diag) = self.find_predecessor_from_src_phase( t, pre_frame, src, transitions, diag, core) if sat_res == z3.unsat: continue return (sat_res, pre_diag) if utils.args.use_z3_unsat_cores: assert core is not None ret_core: Optional[MySet[int]] = MySet(sorted(core)) else: ret_core = None return (z3.unsat, ret_core)
def block(self, diag: Diagram, j: int, p: Phase, trace: Optional[RelaxedTrace] = None, safety_goal: bool = True) -> Union[Blocked, CexFound]: if trace is None: trace = [] if j == 0 or (j == 1 and self.valid_in_initial_frame( self.solver, p, diag) is not None): if safety_goal: utils.logger.always_print('\n'.join( ((t.pp() + ' ') if t is not None else '') + str(diag) for t, diag in trace)) print( 'abstract counterexample: the system has no universal inductive invariant proving safety' ) # TODO: placeholder for analyzing relaxed trace # import relaxed_traces # print(relaxed_traces.diagram_trace_to_explicitly_relaxed_trace(trace, phases.phase_safety(p))) if utils.args.checkpoint_out: self.store_frames(utils.args.checkpoint_out) raise AbstractCounterexample() else: if utils.logger.isEnabledFor(logging.DEBUG): utils.logger.debug('failed to block diagram') return CexFound() while True: with utils.LogTag(utils.logger, 'block-attempt'): if utils.logger.isEnabledFor(logging.DEBUG): utils.logger.debug('blocking diagram in frame %s' % j) utils.logger.debug(str(diag)) self.print_frame(j - 1, lvl=logging.DEBUG) res, x = self.find_predecessor(self[j - 1], p, diag) if res == z3.unsat: utils.logger.debug('no predecessor: blocked!') assert x is None or isinstance(x, MySet) core: Optional[MySet[int]] = x self.augment_core_for_init(p, diag, core) break assert isinstance(x, tuple), (res, x) trans, (pre_phase, pre_diag) = x trace.append((trans, pre_diag)) ans = self.block(pre_diag, j - 1, pre_phase, trace, safety_goal) if not isinstance(ans, Blocked): return ans trace.pop() if utils.logger.isEnabledFor(logging.DEBUG) and core is not None: utils.logger.debug('core %s' % core) utils.logger.debug('unminimized diag\n%s' % diag) diag.minimize_from_core(core) diag.generalize(self.solver, self[j - 1], self.automaton.transitions_to_grouped_by_src(p), p == self.automaton.init_phase(), j) e = syntax.Not(diag.to_ast()) if utils.logger.isEnabledFor(logging.DEBUG): utils.logger.debug( 'adding new clause to frames 0 through %d phase %s' % (j, p.name())) if utils.logger.isEnabledFor(logging.INFO): utils.logger.info("[%d] %s" % (j, str(e))) self.add(p, e, j) utils.logger.debug("Done blocking") return Blocked()
def push_conjunct(self, frame_no: int, c: Expr, p: Phase, frame_old_count: Optional[int] = None) -> None: is_safety = c in phases.phase_safety(p) f = self.fs[frame_no] while True: with utils.LogTag(utils.logger, 'pushing-conjunct-attempt', lvl=logging.DEBUG, frame=str(frame_no), conj=str(c)): utils.logger.debug('frame %s phase %s attempting to push %s' % (frame_no, p.name(), c)) res = self.clause_implied_by_transitions_from_frame( f, p, c, minimize=is_safety or utils.args.block_may_cexs) if res is None: utils.logger.debug('frame %s phase %s managed to push %s' % (frame_no, p.name(), c)) if utils.args.smoke_test and utils.logger.isEnabledFor( logging.DEBUG): utils.logger.debug('smoke testing...') # TODO: phases logic.check_bmc(self.solver, c, frame_no + 1) # assert self.clause_implied_by_transitions_from_frame(f, p, c) is None self[frame_no + 1].strengthen(p, c) self.debug_assert_inductive_trace() break pre_phase, (m, t) = res mod = Trace.from_z3([KEY_OLD, KEY_NEW], m) self.record_state(mod) diag = mod.as_diagram(i=0) if utils.logger.isEnabledFor(logging.DEBUG): utils.logger.debug( 'frame %s failed to immediately push %s due to ' 'transition %s' % (frame_no, c, t.pp())) # utils.logger.debug(str(mod)) if is_safety: utils.logger.debug( 'note: current clause is safety condition') self.block(diag, frame_no, pre_phase, [(None, c), (t, diag)], safety_goal=True) else: if utils.args.block_may_cexs: ans = self.block(diag, frame_no, pre_phase, [(None, c), (t, diag)], safety_goal=False) if isinstance(ans, CexFound): break else: break
def block(self, diag: Diagram, j: int, p: Phase, trace: Optional[List[Tuple[Optional[PhaseTransition], Union[Diagram, Expr]]]] = None, safety_goal: bool = True) -> Union[Blocked, CexFound]: if trace is None: trace = [] if j == 0 or (j == 1 and self.valid_in_initial_frame( self.solver, p, diag) is not None): if safety_goal: utils.logger.always_print('\n'.join( ((t.pp() + ' ') if t is not None else '') + str(diag) for t, diag in trace)) print('abstract counterexample') raise Exception('abstract counterexample') else: if utils.logger.isEnabledFor(logging.DEBUG): utils.logger.debug('failed to block diagram') # utils.logger.debug(str(diag)) return CexFound() # print fs while True: with utils.LogTag(utils.logger, 'block-attempt'): if utils.logger.isEnabledFor(logging.DEBUG): utils.logger.debug('blocking diagram in frame %s' % j) utils.logger.debug(str(diag)) self.print_frame(j - 1, lvl=logging.DEBUG) res, x = self.find_predecessor(self[j - 1], p, diag) if res == z3.unsat: utils.logger.debug('no predecessor: blocked!') assert x is None or isinstance(x, MySet) core: Optional[MySet[int]] = x self.augment_core_for_init(p, diag, core) break assert isinstance(x, tuple), (res, x) trans, (pre_phase, pre_diag) = x trace.append((trans, pre_diag)) ans = self.block(pre_diag, j - 1, pre_phase, trace, safety_goal) if not isinstance(ans, Blocked): return ans trace.pop() if utils.logger.isEnabledFor(logging.DEBUG) and core is not None: utils.logger.debug('core %s' % core) utils.logger.debug('unminimized diag\n%s' % diag) diag.minimize_from_core(core) diag.generalize(self.solver, self[j - 1], self.automaton.transitions_to_grouped_by_src(p), p == self.automaton.init_phase(), j) e = syntax.Not(diag.to_ast()) if utils.logger.isEnabledFor(logging.DEBUG): utils.logger.debug( 'adding new clause to frames 0 through %d phase %s' % (j, p.name())) if utils.logger.isEnabledFor(logging.INFO): utils.logger.info("[%d] %s" % (j, str(e))) self.add(p, e, j) utils.logger.debug("Done blocking") return Blocked()