def current_event_compatible_with_enabled_EventRule( self, action_rule: EventRule) -> bool: assert self.cur_event is not None rv: bool self.evaluation_is_in_next_action_rule = True if isinstance(action_rule, DeadlineEventRule): rv = self.cur_event.role_id == ENV_ROLE else: role_action_match = self.cur_event.role_id in action_rule.role_ids and self.cur_event.action_id == action_rule.action_id if not role_action_match: rv = False elif not self.evalTimeConstraint(action_rule.time_constraint): rv = False elif action_rule.where_clause: rv = chcast(bool, self.evalTerm(action_rule.where_clause)) elif action_rule.param_setter: if self.cur_event.actionparam_subst_list: argvals = [ self.evalTerm(arg) for arg in action_rule.param_setter ] rv = all( argvals[i] == self.cur_event.actionparam_subst_list[i] for i in range( len(self.cur_event.actionparam_subst_list))) else: rv = len(action_rule.param_setter) == 0 else: rv = True self.evaluation_is_in_next_action_rule = False return rv
def evalTimeConstraint(self, time_constraint: Optional[Term]) -> bool: # print(f"evalTimeConstraint({time_constraint})", # "cur event td", self.cur_event_delta(), # "next event td", self.evalFnApp(FnApp("next_event_td",[],None)), # "sit entrance td", self.evalFnApp(FnApp("last_situation_td", [], None))) if time_constraint is None: return True else: return chcast(bool, self.evalTerm(time_constraint))
def check(formula:Z3Term) -> Z3.CheckSatResult: sz3.push() sz3.add(chcast(Z3.ExprRef,formula)) rv = sz3.check() if rv == Z3.unknown: print("Reason unknown:", sz3.reason_unknown()) print("Z3 gave up on this query:\n\n", sz3.to_smt2()) # sz3.pop() # sz3.push() # simplified = Z3.simplify(chcast(Z3.ExprRef,formula)) # sz3.add(simplified) # rv2 = sz3.check() # # if rv2 == Z3.unknown: # # print("Z3 gave up (twice, once post-simplify) on this query:\n\n", sz3.to_smt2()) # print("Z3 gave up (twice, once post-simplify) on this query:\n\n", formula) # sz3.pop() # return rv2 sz3.pop() return rv
def query(qp:QueryPath) -> SEvalRV: if isinstance(qp, GuardedBlockPath): checkres = check(qp.core.full_constraint()) if checkres == Z3.sat:# or checkres == Z3.unknown: res = sevalBlock(qp.block, qp.next_state, qp.action, qp.action_params, qp.core) if isinstance(res, SEvalRVChange): if qp.next_statement: return sevalStatement(qp.next_statement, res.next_state, qp.action, qp.action_params, CoreSymbExecState(res.path_constraint, qp.core.time_path_constraint, res.next_state, qp.core.time, qp.core.env_vars, res.extra)) else: # case when the ifelse is the last statement in its block return sevalActionAfterStateTransform(qp.action, res.next_state, CoreSymbExecState(res.path_constraint, qp.core.time_path_constraint, # qp.core.state, ?? res.next_state, qp.core.time, qp.core.env_vars, res.extra)) # elif isinstance(res, (SEvalRVStopThread, SEvalRVInconsistent)): # return res elif isinstance(res, SEvalRVStopThread): return res else: assert False, res # # making this case explicit even though doesn't need to be cuz doesn't hurt and makes it clearer # elif isinstance(res, SEvalRVInconsistent): # raise res # else: # raise NotImplementedError(str(res)) elif checkres == Z3.unsat: if qp.next_statement: return sevalStatement(chcast(Statement, qp.next_statement), chcast(LedgerDict, qp.next_state), qp.action, qp.action_params, qp.core) else: return sevalActionAfterStateTransform(qp.action, qp.next_state, qp.core) # return SEvalRVInconsistent("GuardedBlockPath query unsat") elif isinstance(qp, EventRuleEnabledPath): checkres = check(qp.core.full_constraint()) if checkres == Z3.sat:# or checkres == Z3.unknown: return sevalRuleIgnoreGuard(qp.rule, qp.core) elif checkres == Z3.unsat: return SEvalRVInconsistent("EventRuleEnabledPath query unsat") elif isinstance(qp, EventRuleParamsConstraintPath): assert isinstance(qp.core.state, LedgerDict) # print("z3 query", qp.core.path_constraint) checkres = check(qp.core.full_constraint()) # invalid # syntax( < string >, line # 1) if checkres == Z3.sat:# or checkres == Z3.unknown: if TRACE: print("path constraint sat:", z3termPrettyPrint(qp.core.full_constraint())) return sevalRuleIgnoreGuardAndParamConstraints(qp.rule, qp.action_params, qp.core) elif checkres == Z3.unsat: if TRACE: print("path constraint unsat:", z3termPrettyPrint(qp.core.full_constraint())) return SEvalRVInconsistent("EventRuleParamsConstraintPath query unsat") elif isinstance(qp, AssertionPath): checkres = check(Z3.And(Z3.Not(qp.assertion), qp.core.full_constraint())) if checkres == Z3.sat: raise Exception(f"Negation of assertion is satisfiable!:\n{qp}") if checkres == Z3.unknown: print(f"Failed to prove assertion:\n{qp}\nReason unknown:{sz3.reason_unknown()}") addUnprovedAssertion(qp) # return SEvalRVStopThread('') if qp.next_statement: return sevalStatement(chcast(Statement,qp.next_statement),chcast(LedgerDict,qp.next_state),qp.action,qp.action_params,qp.core) else: return sevalActionAfterStateTransform(qp.action, qp.next_state, qp.core) # raise NotImplementedError("no assertions till working without them") else: raise NotImplementedError(qp) assert checkres == Z3.unknown, checkres if TRACE: if checkres == Z3.unknown: print(f"UNKNOWN: {qp.core.full_constraint()}") # if checkres_time == Z3.unknown: # print(f"UNKNOWN (time): {qp.core.full_constraint()}") # addTimeoutQueryPath(qp) # done in query() # sz3.push() # sz3.add(simplified) # print("Try again:",sz3.check(simplified), sz3.reason_unknown()) # sz3.pop() addTimeoutQueryPath(qp) return SEvalRVStopThread("timeout on " + str(qp))
def evalStatement(self, stmt: Statement, verbose: bool): # An Action's StateTransform block is *always* evaluated in the most recent state variable and # action parameter substitution context that's visible to it, as given by self.gvarvals and self.cur_event, # even when applying an action from a PartlyInstantiatedPartyFutureEventRule. # Therefore, this function does not take an EvalContext argument. if isinstance(stmt, StateVarAssign): gvardec = stmt.vardec assert gvardec is not None, f"Global variable declaration for {stmt.varname} not found." todo_once("kill writeOnceMore") if gvardec.isWriteOnceMore( ) and self.gvar_write_cnt[stmt.varname] >= 2: raise Exception( f"Attempt to write twice more (after init) to writeOnceMore variable `{stmt.varname}`" ) dictInc(self.gvar_write_cnt, stmt.varname, init=1) todo_once("use vardec for runtime type checking") rhs_value = self.evalTerm(stmt.value_expr) if stmt.varop == ":=": self.gvarvals[primed(stmt.varname)] = rhs_value else: current_var_val: Data = self.gvarvals[stmt.varname] if stmt.varop == "+=": self.gvarvals[stmt.varname] = current_var_val + rhs_value elif stmt.varop == "-=": self.gvarvals[stmt.varname] = current_var_val - rhs_value elif stmt.varop == "*=": self.gvarvals[stmt.varname] = current_var_val * rhs_value else: raise Exception('fixme') if verbose: print(f"\t{stmt.varname} ← {self.gvarvals[stmt.varname]}") elif isinstance(stmt, FVRequirement): # print("conjecture " + str(stmt)) assert self.evalTerm( stmt.value_expr ), f"""Conjecture {stmt.value_expr} is false! Variable values and action params: {str(self.gvarvals)} {str(self.last_appliedaction_params)} """ elif isinstance(stmt, IfElse): test_result = chcast(bool, self.evalTerm(stmt.test)) if test_result: self.evalCodeBlock(StateTransform(stmt.true_branch), verbose) elif stmt.false_branch: self.evalCodeBlock(StateTransform(stmt.false_branch), verbose) elif isinstance(stmt, LocalVarDec): rhs_value = self.evalTerm(stmt.value_expr) self.localvar_vals[stmt.varname] = rhs_value if stmt.is_writeout: todo_once( "Writeouts don't work currently because of local var elim." ) self.writeout(stmt.varname, rhs_value) else: raise NotImplementedError("Unhandled Statement: " + str(stmt))
def assess_event_legal_wrt_nextrules(self, event: Event, verbose=True ) -> EventLegalityAssessment: enabled_strong_obligs: List[ActorEventRule] = list() enabled_permissions: List[ActorEventRule] = list() enabled_env_action_rules: List[DeadlineEventRule] = list() enabled_weak_obligs_by_role: Dict[RoleId, List[ActorEventRule]] = dict() enabled_weak_obligs = list() nar: EventRule for nar in self.last_or_current_situation.action_rules(): entrance_enabled = not nar.entrance_enabled_guard or chcast( bool, self.evalTerm(nar.entrance_enabled_guard)) if entrance_enabled: if isinstance(nar, DeadlineEventRule): enabled_env_action_rules.append(nar) else: assert isinstance(nar, ActorEventRule) if nar.deontic_keyword == 'may' or nar.deontic_keyword == 'should': enabled_permissions.append(nar) elif nar.deontic_keyword == 'must': enabled_strong_obligs.append(nar) elif nar.deontic_keyword == 'quasi-responsibility': enabled_weak_obligs.append(nar) for role_id in nar.role_ids: if role_id not in enabled_weak_obligs_by_role: enabled_weak_obligs_by_role[role_id] = [nar] else: enabled_weak_obligs_by_role[role_id].append( nar) else: assert False # assert len(enabled_weak_obligs_by_role) == 0, enabled_weak_obligs_by_role # ===================================================== # CASE 1: 1 or more entrance-enabled strong obligations # ===================================================== if len(enabled_strong_obligs) > 1: contract_bug( "There are multiple enabled strong obligations. This is a contract bug that eventually will be ruled out statically." ) # return ContractFlawedError() if len(enabled_strong_obligs) == 1: if len(enabled_weak_obligs) > 0 or len( enabled_permissions) > 0 or len( enabled_env_action_rules) > 0: contract_bug( "There is exactly one enabled strong obligation, but there is also at least one " "enabled permission, environment-action, or weak obligation." "This is a contract bug that eventually will be ruled out statically." ) # return ContractFlawedError() else: assert isinstance(enabled_strong_obligs[0], ActorEventRule) if self.current_event_compatible_with_enabled_EventRule( enabled_strong_obligs[0]): return EventOk() else: return BreachResult( enabled_strong_obligs[0].role_ids, f"The unique enabled strong obligation (of {enabled_strong_obligs[0].role_ids}) is incompatible " "with the current event (expired or not yet active, wrong action, wrong role, etc):" f"\t{event}\n" f"See rule {enabled_strong_obligs[0]}\n with time constraint {enabled_strong_obligs[0].time_constraint}." f"Environment looks like:\n" + self.environ_tostr()) # =============================================================== # CASE 2: 0 entrance-enabled strong obligations (SO action-rules) # =============================================================== # -------------------------------------------------------------------------------- # CASE 2a: the current event is compatible with some permission or env action rule # -------------------------------------------------------------------------------- todo_once( "Verbose print option here for showing compatible enabled env actions and permissions" ) # We ignore permissions for roles that are not the current role id, for actions that aren't the current # action id, whose time constraint is false, or whose where clause is false compat_enabled_permissions = list( filter(self.current_event_compatible_with_enabled_EventRule, enabled_permissions)) if len(compat_enabled_permissions) > 0: return EventOk() # Similarly for Env actions: compat_enabled_env_action_rules = list( filter(self.current_event_compatible_with_enabled_EventRule, enabled_env_action_rules)) if len(compat_enabled_env_action_rules) > 0: return EventOk() # ----------------------------------------------------------------------------------- # CASE 2b: the current event is NOT compatible with any permission or env action rule # ----------------------------------------------------------------------------------- # Case 2b1: there are actually no enabled weak obligations. Thus the current event is not allowed, # so we blame the subject of the current event. if len(enabled_weak_obligs) == 0: return BreachResult([ event.role_id ], f"Role {event.role_id} attempted an unpermitted action {event.action_id}({event.actionparam_subst_list})" ) # contract_bug(f"No rules apply to the current event\n{event}.\n" # "This is a contract bug that eventually will be ruled out statically.") # Case 2b2: weak obligations are relevant even if they are not compatible with `event`, # since in that case they can result in a breach. Let's see who has weak obligations that matchTerm # the current event, by "filtering out" those that don't. # if there are any left, for and role, then all is well. for roleid_with_wo in enabled_weak_obligs_by_role: for rule in enabled_weak_obligs_by_role[roleid_with_wo]: # print(f"An enabled weak oblig rule for {roleid_with_wo}: " + str(rule)) if self.current_event_compatible_with_enabled_EventRule(rule): if verbose: print("weak oblig rule checks out: ", rule) return EventOk() # enabled_weak_obligs_by_role[roleid_with_wo] = list(filter( compat_checker, enabled_weak_obligs_by_role[roleid_with_wo] )) # if len(enabled_weak_obligs_by_role[roleid_with_wo]) > 0: # return EventOk() # Case 2b3: all the roles (and there's at least one) who had an enabled weak oblig are jointly responsible for the breach breach_roles = list(enabled_weak_obligs_by_role.keys()) return BreachResult( list(breach_roles), f"Role(s) {list(breach_roles)} had weak obligations that went unfulfilled:\n" + str(enabled_weak_obligs_by_role))
def eliminate_ifthenelse_block(block: Block) -> Block: global progress_made if len(block) == 0: return block rest = block[1:] s = block[0] maybe_ite_term = s.findFirstTerm(isite) snew: Statement if not maybe_ite_term: snew = s else: progress_made = True # type:ignore ite_term = chcast(FnApp, maybe_ite_term) assert ite_term.fnsymb_name == "ifthenelse", ite_term assert len(ite_term.args) == 3, ite_term.args ite_test, ite_true_expr, ite_false_expr = ite_term.args if isinstance(s, LocalVarDec): raise Exception("Use eliminate_local_vars first") elif isinstance(s, StateVarAssign): snew = IfElse(ite_test, [ StateVarAssign( s.vardec, s.value_expr.substForTerm( ite_term, ite_true_expr), s.varop) ], [ StateVarAssign( s.vardec, s.value_expr.substForTerm( ite_term, ite_false_expr), s.varop) ]) return cast(Block, [snew]) + eliminate_ifthenelse_block(rest) elif isinstance(s, IfElse): maybe_occurence = s.test.findFirstTerm(lambda y: y == ite_term) if not maybe_occurence: if s.false_branch is not None: snew = IfElse( s.test, eliminate_ifthenelse_block(s.true_branch), eliminate_ifthenelse_block(s.false_branch)) else: snew = IfElse( s.test, eliminate_ifthenelse_block(s.true_branch)) else: snew = IfElse(ite_test, [s.substForTerm(ite_test, ite_true_expr)], [s.substForTerm(ite_test, ite_false_expr)]) elif isinstance(s, FVRequirement): maybe_occurence = s.value_expr.findFirstTerm( lambda y: y == ite_term) if not maybe_occurence: snew = s else: snew = IfElse(ite_test, [s.substForTerm(ite_test, ite_true_expr)], [s.substForTerm(ite_test, ite_false_expr)]) else: raise NotImplementedError return cast(Block, [snew]) + eliminate_ifthenelse_block(rest)
def subst(self, paramvals: List[SExprOrStr]) -> List[SExpr]: return [ chcast(SExpr, sexpr_subst_mult_string(x, self.macroparams, paramvals)) for x in self.macrobody ]
def subst(self, paramvals: List[SExprOrStr]) -> SExpr: return chcast( SExpr, sexpr_subst_mult_string(self.macrobody, self.macroparams, paramvals))