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))
Exemple #7
0
    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)
Exemple #8
0
 def subst(self, paramvals: List[SExprOrStr]) -> List[SExpr]:
     return [
         chcast(SExpr,
                sexpr_subst_mult_string(x, self.macroparams, paramvals))
         for x in self.macrobody
     ]
Exemple #9
0
 def subst(self, paramvals: List[SExprOrStr]) -> SExpr:
     return chcast(
         SExpr,
         sexpr_subst_mult_string(self.macrobody, self.macroparams,
                                 paramvals))