def prev_frame_constraint(diag: Diagram) -> bool: pre_frame = self[j - 1].summary() return ( logic.check_two_state_implication_all_transitions( self.solver, pre_frame, syntax.Not(diag.to_ast()), minimize=False ) is None and self.valid_in_initial_frame(syntax.Not(diag.to_ast())) )
def block(self, diag_or_expr: Union[Diagram, Expr], j: int, trace: RelaxedTrace) -> None: utils.logger.info(f'block({j})') def as_expr() -> Expr: return diag_or_expr.to_ast() if isinstance( diag_or_expr, Diagram) else diag_or_expr if j == 0 or (j == 1 and not self.valid_in_initial_frame(syntax.Not(as_expr()))): utils.logger.always_print('\n'.join( ((t.name + ' ') 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' ) if utils.args.checkpoint_out: self.store_frames(utils.args.checkpoint_out) raise AbstractCounterexample() while True: res, x = self.find_predecessor(j - 1, diag_or_expr) if res == z3.unsat: assert x is None or isinstance(x, MySet) core: Optional[MySet[int]] = x self.augment_core_for_init(diag_or_expr, core) break assert isinstance(x, tuple), (res, x) trans, cti = x pre_diag = cti.as_diagram(index=0) trace.append((trans, pre_diag)) self.block(pre_diag, j - 1, trace) trace.pop() if isinstance(diag_or_expr, Diagram): diag_or_expr.minimize_from_core(core) def prev_frame_constraint(diag: Diagram) -> bool: pre_frame = self[j - 1].summary() return (logic.check_two_state_implication_all_transitions( self.solver, pre_frame, syntax.Not(diag.to_ast()), minimize=False) is None and self.valid_in_initial_frame(syntax.Not(diag.to_ast()))) if isinstance(diag_or_expr, Diagram): diag_or_expr.generalize(self.solver, prev_frame_constraint) e = syntax.Not(as_expr()) utils.logger.info(f'block({j}) using {e}') self.add(e, j) k = j while k + 1 < len(self) and self.push_conjunct(k, e): utils.logger.info(f'and pushed to {k + 1}') k += 1
def as_onestate_formula(self, index: Optional[int] = None) -> Expr: # TODO: move to class State, this shouldn't be here assert self.num_states == 1 or index is not None, \ 'to generate a onestate formula from a multi-state model, ' + \ 'you must specify which state you want' assert index is None or (0 <= index and index < self.num_states) if index is None: index = 0 if index not in self.onestate_formula_cache: prog = syntax.the_program mut_rel_interps = self.rel_interps[index] mut_const_interps = self.const_interps[index] mut_func_interps = self.func_interps[index] vs: List[syntax.SortedVar] = [] ineqs: Dict[SortDecl, List[Expr]] = {} rels: Dict[RelationDecl, List[Expr]] = {} consts: Dict[ConstantDecl, Expr] = {} funcs: Dict[FunctionDecl, List[Expr]] = {} for sort in self.univs: vs.extend(syntax.SortedVar(v, syntax.UninterpretedSort(sort.name)) for v in self.univs[sort]) u = [syntax.Id(v) for v in self.univs[sort]] ineqs[sort] = [syntax.Neq(a, b) for a, b in combinations(u, 2)] for R, l in chain(mut_rel_interps.items(), self.immut_rel_interps.items()): rels[R] = [] for tup, ans in l.items(): e: Expr = ( syntax.AppExpr(R.name, tuple(syntax.Id(col) for col in tup)) if tup else syntax.Id(R.name) ) rels[R].append(e if ans else syntax.Not(e)) for C, c in chain(mut_const_interps.items(), self.immut_const_interps.items()): consts[C] = syntax.Eq(syntax.Id(C.name), syntax.Id(c)) for F, fl in chain(mut_func_interps.items(), self.immut_func_interps.items()): funcs[F] = [ syntax.Eq(syntax.AppExpr(F.name, tuple(syntax.Id(col) for col in tup)), syntax.Id(res)) for tup, res in fl.items() ] # get a fresh variable, avoiding names of universe elements in vs fresh = prog.scope.fresh('x', [v.name for v in vs]) e = syntax.Exists(tuple(vs), syntax.And( *chain(*ineqs.values(), *rels.values(), consts.values(), *funcs.values(), ( syntax.Forall((syntax.SortedVar(fresh, syntax.UninterpretedSort(sort.name)),), syntax.Or(*(syntax.Eq(syntax.Id(fresh), syntax.Id(v)) for v in self.univs[sort]))) for sort in self.univs )))) assert prog.scope is not None with prog.scope.n_states(1): typechecker.typecheck_expr(prog.scope, e, None) self.onestate_formula_cache[index] = e return self.onestate_formula_cache[index]
def brat_next_frame(s: Solver, prev_frame: List[Expr], bound: int, inits: List[Expr], safety: Expr, bad_cache: Set[Diagram], minimize: bool) -> List[Expr]: current_frame: List[Expr] = new_frame(s, prev_frame) for bad_model in bad_cache: if logic.check_implication(s, current_frame, [syntax.Not(bad_model.to_ast())]) is None: continue current_frame.append( post_image_prime_consequence( s, prev_frame, inits, bad_model, gen_order=utils.args.generalization_order)) while (bad_trace := bmc_upto_bound( s, safety, bound, preconds=current_frame, minimize=minimize, relaxed_semantics=utils.args.relax_backwards)) is not None: bad_model = Diagram(bad_trace.as_state(0)) utils.logger.debug("Example to block: %s" % str(bad_model)) bad_cache.add(bad_model) learned_clause = post_image_prime_consequence( s, prev_frame, inits, bad_model, gen_order=utils.args.generalization_order) utils.logger.info("Learned clause: %s" % str(learned_clause)) current_frame.append(learned_clause)
def check_bmc(s: Solver, safety: Expr, depth: int, preconds: Optional[Iterable[Expr]] = None, minimize: Optional[bool] = None) -> Optional[Trace]: prog = syntax.the_program if preconds is None: preconds = (init.expr for init in prog.inits()) t = s.get_translator(depth + 1) with s.new_frame(): for precond in preconds: s.add(t.translate_expr(precond)) s.add(t.translate_expr(syntax.New(syntax.Not(safety), depth))) for i in range(depth): s.add(t.translate_expr(New(safety, i))) assert_any_transition(s, t, i, allow_stutter=False) res = s.check() if res == solver.sat: z3m = s.model(minimize=minimize) m = Z3Translator.model_to_trace(z3m, depth + 1) return m elif res == solver.unknown: print('unknown!') return None
def _read_first_order_structure( struct: FirstOrderStructure ) -> Tuple[List[syntax.SortedVar], # vs Dict[SortDecl, List[Expr]], # ineqs Dict[RelationDecl, List[Expr]], # rels Dict[ConstantDecl, Expr], # consts Dict[FunctionDecl, List[Expr]], # funcs ]: vars_by_sort: Dict[SortDecl, List[syntax.SortedVar]] = {} ineqs: Dict[SortDecl, List[Expr]] = {} rels: Dict[RelationDecl, List[Expr]] = {} consts: Dict[ConstantDecl, Expr] = {} funcs: Dict[FunctionDecl, List[Expr]] = {} for sort in struct.univs: vars_by_sort[sort] = [ syntax.SortedVar(v, syntax.UninterpretedSort(sort.name)) for v in struct.univs[sort] ] u = [syntax.Id(s) for s in struct.univs[sort]] ineqs[sort] = [ syntax.Neq(a, b) for a, b in itertools.combinations(u, 2) ] for R, l in struct.rel_interps.items(): rels[R] = [] for tup, ans in l.items(): e: Expr if tup: args: List[Expr] = [] for (col, col_sort) in zip(tup, R.arity): assert isinstance(col_sort, syntax.UninterpretedSort) assert col_sort.decl is not None args.append(syntax.Id(col)) e = syntax.AppExpr(R.name, tuple(args)) else: e = syntax.Id(R.name) e = e if ans else syntax.Not(e) rels[R].append(e) for C, c in struct.const_interps.items(): e = syntax.Eq(syntax.Id(C.name), syntax.Id(c)) consts[C] = e for F, fl in struct.func_interps.items(): funcs[F] = [] for tup, res in fl.items(): e = syntax.AppExpr(F.name, tuple(syntax.Id(col) for col in tup)) e = syntax.Eq(e, syntax.Id(res)) funcs[F].append(e) vs = list(itertools.chain(*(vs for vs in vars_by_sort.values()))) return vs, ineqs, rels, consts, funcs
def itp_gen(s: Solver) -> None: k = utils.args.forward_depth prog = syntax.the_program safety = syntax.And(*(inv.expr for inv in prog.invs() if inv.is_safety)) inits = [init.expr for init in prog.inits()] utils.logger.info("initial state: %s" % str(inits)) utils.logger.info("proving safety property: %s" % safety) candidate = [safety] while True: cti = get_cti(s, syntax.And(*candidate)) if cti is None: break pre_diag = cti[0] pre_diag.generalize(s, lambda diag: bmc_upto_bound( s, syntax.Not(diag.to_ast()), k) is None, order=utils.args.generalization_order) e = syntax.Not(pre_diag.to_ast()) utils.logger.info('adding new clause to the invariant: %s' % str(e)) candidate.append(e) if logic.check_implication(s, inits, candidate) is not None: utils.logger.always_print( "Failure: candidate %s excludes initial states" % ' & '.join(str(clause) for clause in candidate)) else: utils.logger.always_print("Success! Inductive invariant:") for clause in candidate: utils.logger.always_print(str(clause))
def bmc_prime_consequence(s: Solver, bound: int, inits: List[Expr], bad_model: Diagram, relaxed_semantics: bool, generalization_order: Optional[int] = None) -> Expr: def bmc_constraint(diag: Diagram) -> bool: return bmc_upto_bound(s, syntax.Not(diag.to_ast()), bound, preconds=inits, relaxed_semantics=relaxed_semantics) is None bad_model_copy = copy.deepcopy(bad_model) bad_model_copy.generalize(s, bmc_constraint, order=generalization_order) return syntax.Not(bad_model_copy.to_ast())
def post_image_prime_consequence(s: Solver, prev_frame: List[Expr], inits: List[Expr], bad_model: Diagram, gen_order: Optional[int] = None) -> Expr: # TODO: duplicated from updr def prev_frame_constraint(diag: Diagram) -> bool: return (logic.check_two_state_implication_all_transitions( s, prev_frame, syntax.Not(diag.to_ast()), minimize=False) is None and valid_in_initial_frame(s, inits, syntax.Not( diag.to_ast()))) # TODO: unsat core first bad_model_copy = copy.deepcopy(bad_model) bad_model_copy.generalize(s, prev_frame_constraint, order=gen_order) return syntax.Not(bad_model_copy.to_ast())
def negate_clause(c: Expr) -> Expr: if isinstance(c, syntax.Bool): return syntax.Bool(not c.val) elif isinstance(c, syntax.UnaryExpr): assert c.op == 'NOT' return c.arg elif isinstance(c, syntax.BinaryExpr): assert c.op in ['EQUAL', 'NOTEQ'] op = 'NOTEQ' if c.op == 'EQUAL' else 'EQUAL' return syntax.BinaryExpr(op, c.arg1, c.arg2) elif isinstance(c, syntax.NaryExpr): assert c.op == 'OR' return syntax.NaryExpr('AND', [negate_clause(arg) for arg in c.args]) elif isinstance(c, syntax.AppExpr) or isinstance(c, syntax.Id): return syntax.Not(c) else: assert False, f'unsupported expression {c} in negate_clause'
def diagram_trace_to_explicitly_relaxed_trace(trace: RelaxedTrace, safety: Sequence[syntax.InvariantDecl]) -> None: relaxed_prog = relaxed_program(syntax.the_program) with syntax.prog_context(relaxed_prog): s = Solver() end_expr = syntax.Not(syntax.And(*(invd.expr for invd in safety))) with syntax.the_program.scope.n_states(1): end_expr.resolve(syntax.the_program.scope, syntax.BoolSort) trace_decl = diagram_trace_to_explicitly_relaxed_trace_decl(trace, end_expr) with syntax.the_program.scope.n_states(1): trace_decl.resolve(syntax.the_program.scope) print(trace_decl) res = bmc_trace(relaxed_prog, trace_decl, s, lambda slvr, ks: logic.check_solver(slvr, ks, minimize=True)) print(res) assert False
def is_rel_blocking_relax(trns: Trace, idx: int, derived_rel: Tuple[List[Tuple[syntax.SortedVar, str]], Expr]) -> bool: # TODO: probably can obtain the sort from the sortedvar when not using scapy free_vars, derived_relation_formula = derived_rel free_vars_active_clause = syntax.And(*(active_var(v.name, sort_name) for (v, sort_name) in free_vars)) diffing_formula = syntax.Exists([v for (v, _) in free_vars], syntax.And(syntax.Old(syntax.And(free_vars_active_clause, derived_relation_formula)), syntax.And(free_vars_active_clause, syntax.Not(derived_relation_formula)))) with syntax.the_program.scope.two_state(twostate=True): # TODO: what is this doing? probably misusing diffing_formula.resolve(syntax.the_program.scope, syntax.BoolSort) res = trns.eval_double_vocab(diffing_formula, idx) assert isinstance(res, bool) return cast(bool, res)
def negate_clause(c: Expr) -> Expr: if isinstance(c, syntax.Bool): return syntax.Bool(not c.val) elif isinstance(c, syntax.UnaryExpr): assert c.op == 'NOT' return c.arg elif isinstance(c, syntax.BinaryExpr): assert c.op in ['EQUAL', 'NOTEQ'] op = 'NOTEQ' if c.op == 'EQUAL' else 'EQUAL' return syntax.BinaryExpr(op, c.arg1, c.arg2) elif isinstance(c, syntax.NaryExpr): assert c.op == 'OR' return syntax.NaryExpr('AND', tuple(negate_clause(arg) for arg in c.args)) elif isinstance(c, syntax.AppExpr) or isinstance(c, syntax.Id): return syntax.Not(c) elif isinstance(c, syntax.QuantifierExpr): assert c.quant == 'FORALL' return syntax.QuantifierExpr('EXISTS', c.get_vs(), negate_clause(c.body)) else: assert False, f'unsupported expression {c} in negate_clause'
def is_rel_blocking_relax_step( trns: Trace, idx: int, derived_rel: Tuple[List[Tuple[syntax.SortedVar, str]], Expr] ) -> bool: # TODO: probably can obtain the sort from the sortedvar when not using pickle free_vars, derived_relation_formula = derived_rel free_vars_active_clause = syntax.And(*(active_var(v.name, sort_name) for (v, sort_name) in free_vars)) diffing_formula = syntax.Exists([v for (v, _) in free_vars], syntax.And(syntax.And(free_vars_active_clause, derived_relation_formula), syntax.New(syntax.And(free_vars_active_clause, syntax.Not(derived_relation_formula))))) with syntax.the_program.scope.fresh_stack(): with syntax.the_program.scope.n_states(2): diffing_formula.resolve(syntax.the_program.scope, syntax.BoolSort) res = trns.eval(diffing_formula, idx) assert isinstance(res, bool) return cast(bool, res)
def itp_gen(s: Solver) -> None: k = 4 prog = syntax.the_program safety = syntax.And(*(inv.expr for inv in prog.invs() if inv.is_safety)) inits = [init.expr for init in prog.inits()] utils.logger.info("initial state: %s" % str(inits)) utils.logger.info("proving safety property: %s" % safety) candidate = [safety] while True: cti = get_cti(s, syntax.And(*candidate)) if cti is None: break pre_diag = cti[0] with logic.BoundedReachabilityCheck(s, syntax.the_program, k) as bmc_checker: core = bmc_checker.check_and_core(pre_diag) pre_diag.minimize_from_core(core) pre_diag.generalize( s, lambda diag: generalize_cex_omission_checker(s, diag, k)) e = syntax.Not(pre_diag.to_ast()) utils.logger.info('adding new clause to the invariant: %s' % str(e)) candidate.append(e) res = logic.check_implication(s, inits, candidate) if res is not None: utils.logger.always_print( "Failure: candidate %s excludes initial states" % ' & '.join(str(clause) for clause in candidate)) else: utils.logger.always_print("Success! Inductive invariant:") for clause in candidate: utils.logger.always_print(str(clause))
key=lambda b: (b.known_absent_until_frame, b.num_steps_to_bad), default=None) if bstate_min is None or (min_frame_no := bstate_min.known_absent_until_frame) == len(self) - 1: break if isinstance(state := bstate_min.state_or_expr, State): eval_res = state.eval(syntax.And(*(self[min_frame_no + 1].summary()))) assert isinstance(eval_res, bool) if eval_res: return bstate_min else: expr = state res = logic.check_implication(self.solver, self[min_frame_no + 1].summary(), [syntax.Not(expr)], minimize=False) if res is not None: return bstate_min bstate_min.known_absent_until_frame += 1 utils.logger.info('no existing states to block. looking for a new state.') f = self.fs[-1] if len(self.safeties) == 0 or (res := logic.check_implication(self.solver, f.summary(), self.safeties)) is None: utils.logger.info('frontier is safe. nothing new to block either.') return None state = Z3Translator.model_to_trace(res, 1).as_state(0) assert len(self) >= 2
def bmc_constraint(diag: Diagram) -> bool: return bmc_upto_bound(s, syntax.Not(diag.to_ast()), bound, preconds=inits, relaxed_semantics=relaxed_semantics) is None
def valid_in_initial_frame(self, s: Solver, p: Phase, diag: Diagram) -> Optional[z3.ModelRef]: return logic.check_implication(s, self.fs[0].summary_of(p), [syntax.Not(diag.to_ast())])
def as_expr(self, els_trans: Callable[[str],str]) -> Expr: fact_free_vars = syntax.Apply(self._rel.name, [syntax.Id(None, els_trans(e)) for e in self._els]) if not self._is_positive(): fact_free_vars = syntax.Not(fact_free_vars) return fact_free_vars
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()
def prev_frame_constraint(diag: Diagram) -> bool: return (logic.check_two_state_implication_all_transitions( s, prev_frame, syntax.Not(diag.to_ast()), minimize=False) is None and valid_in_initial_frame(s, inits, syntax.Not( diag.to_ast())))
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()