def evalTimeConstraint(self, time_constraint: Term, ctx: EvalContext) -> bool: assert time_constraint is not None # return True rv = chcast(bool, self.evalTerm(time_constraint, ctx)) # print('evalTimeConstraint: ', rv) return rv
def mk_l4contract(self, l:List[SExpr]) -> L4Contract: x : SExpr for x in l: #assert len(x) >= 2, "Problem top-level: " + str(x) rem = x.tillEnd(1) def head(constant:str) -> bool: nonlocal x return streqci(x[0], constant) if head( STR_ARG_MACRO_DEC_LABEL): macroname = chcaststr(x[1]) macroparams : List[str] if isinstance(x[2],str): macroparams = [x[2]] else: macroparams = cast(List[str],castse(x[2]).lst) macrobody = chcast(SExpr, x[3]) self.top.str_arg_macros[ macroname ] = L4Macro(macroparams, macrobody) elif head(GLOBAL_VARS_AREA_LABEL): self.top.global_var_decs = self._mk_global_vars(rem) elif head(CONTRACT_PARAMETERS_AREA_LABEL): # assert all(isinstance(expr[0],str) for expr in rem) self.top.contract_params = {castid(ContractParamId,expr[0]) : self._mk_contract_param(expr) for expr in rem} elif head(TOPLEVEL_CLAIMS_AREA_LABEL): self.top.claims = self._mk_claims(rem) elif head(ROLES_DEC_LABEL): self.top.roles.extend(self._mk_actors(rem)) elif head(PROSE_CONTRACT_AREA_LABEL): self.top.prose_contract = self._mk_prose_contract(cast(List[List[str]], rem)) elif head(FORMAL_CONTRACT_AREA_LABEL): self._mk_main_program_area(rem) elif head(TIMEUNIT_DEC_LABEL): self.top.timeunit = chcaststr(x[1]).lower() self.assertOrSyntaxError( self.top.timeunit in SUPPORTED_TIMEUNITS, x, f"{TIMEUNIT_DEC_LABEL} must be one of {str(SUPPORTED_TIMEUNITS)}") elif head( DEFINITIONS_AREA ): self.top.definitions = self._mk_definitions(rem) elif head( DOT_FILE_NAME_LABEL ): self.top.dot_file_name = chcaststr(x[1][1]) # the extra [1] is because its parse is of the form ['STRLIT', 'filename'] elif head( IMG_FILE_NAME_LABEL ): self.top.img_file_name = chcaststr(x[1][1]) # the extra [1] is because its parse is of the form ['STRLIT', 'filename'] else: raise Exception("Unsupported: ", x[0]) for f in self.after_model_build_requirements: if not f[0](): raise Exception(f[1]) return self.top
def current_event_compatible_with_enabled_NextActionRule( self, action_rule: NextActionRule) -> bool: assert self.cur_event is not None role_action_match = self.cur_event.role_id == action_rule.role_id and self.cur_event.action_id == action_rule.action_id if not role_action_match: return False if not action_rule.where_clause: return True # ctx2 = EvalContext(self.gvarvals, self.cur_event.params, ctx.abapvals) return chcast(bool, self.evalTerm(action_rule.where_clause, None))
def apply_action(self, action: Action, to_breach_section_id: Optional[SectionId] = None ) -> ApplyActionResult: assert self.cur_event is not None if to_breach_section_id is not None: self.last_or_current_section_id = to_breach_section_id self.last_section_entrance_delta = self.cur_event_delta() return ApplyActionResult(None) self.last_appliedaction_params = self.cur_event.params rv: ApplyActionResult if action.global_state_transform: self.evalCodeBlock(action.global_state_transform) future: PartlyInstantiatedPartyFutureActionRule floatingrules_added: List[PartlyInstantiatedPartyFutureActionRule] = [] for far in action.futures: is_disabled = far.entrance_enabled_guard and \ not chcast(bool,self.evalTerm(far.entrance_enabled_guard,None)) # TODO: unsure about this None if is_disabled: continue if far.where_clause: new_where_clause = PartialEvalTerm( far.where_clause, EvalContext( self.gvarvals.copy(), self.last_appliedaction_params.copy() if self.last_appliedaction_params else None, True)) future = PartlyInstantiatedPartyFutureActionRule( far, new_where_clause) # far = copy.copy(far) # far.where_clause = new_where_clause else: future = PartlyInstantiatedPartyFutureActionRule(far, None) if far.deontic_keyword == 'must-later': self.future_obligations.append(future) else: assert far.deontic_keyword == 'may-later' self.future_permissions.append(future) floatingrules_added.append(future) # print("new future:", ef) if action.dest_section_id != LOOP_KEYWORD: self.last_or_current_section_id = action.dest_section_id self.last_section_entrance_delta = self.cur_event_delta() return ApplyActionResult( floatingrules_added if len(floatingrules_added) > 0 else None)
def current_event_compatible_with_floatingrule( self, pif_action_rule: PartlyInstantiatedPartyFutureActionRule) -> bool: assert self.cur_event is not None role_action_match = self.cur_event.role_id == pif_action_rule.rule.role_id and self.cur_event.action_id == pif_action_rule.rule.action_id if not role_action_match: return False if not pif_action_rule.pe_where_clause: return True # will sub in self.cur_event.params for *rule*-bound action params in pif_action_rule.pe_where_clause # all other variables value in pif_action_rule.pe_where_clause are supplied by pif_action_rule.ctx # note that whether the current event is compatible with a future is not allowed to depend on # anything from the current execution context except self.cur_event.params. that is the reason for the None # argument. # TODO UNCERTAIN ctx = EvalContext( self.gvarvals, None, use_current_event_params_for_rulebound_action_params=True) return chcast(bool, self.evalTerm(pif_action_rule.pe_where_clause, ctx))
def subst(self, paramvals: List[SExprOrStr]) -> SExpr: return chcast( SExpr, sexpr_subst_mult_string(self.macrobody, self.macroparams, paramvals))
def evalStatement(self, stmt: GlobalStateTransformStatement): # An Action's GlobalStateTransform block is *always* evaluated in the most recent global 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 PartlyInstantiatedPartyFutureActionRule. # Therefore, this function does not take an EvalContext argument. # gvardec : GlobalVarDec if isinstance(stmt, (GlobalVarAssignStatement, IncrementStatement, DecrementStatement, TimesEqualsStatement)): gvardec = self.top.gvarDecObj(stmt.varname) assert gvardec is not None, f"Global variable declaration for {stmt.varname} not found." 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}`" ) dictSetOrInc(self.gvar_write_cnt, stmt.varname, init=1) todo_once("use vardec for runtime type checking") rhs_value = self.evalTerm(stmt.value_expr, None) if isinstance(stmt, GlobalVarAssignStatement): self.gvarvals[stmt.varname] = rhs_value print(f"\t{stmt.varname} := {rhs_value}") elif isinstance( stmt, (IncrementStatement, DecrementStatement, TimesEqualsStatement)): current_var_val: Data = self.gvarvals[stmt.varname] assert current_var_val is not None if isinstance(stmt, IncrementStatement): self.gvarvals[stmt.varname] = current_var_val + rhs_value print(f"\t{stmt.varname} := {self.gvarvals[stmt.varname]}") elif isinstance(stmt, DecrementStatement): self.gvarvals[stmt.varname] = current_var_val - rhs_value print(f"\t{stmt.varname} := {self.gvarvals[stmt.varname]}") elif isinstance(stmt, TimesEqualsStatement): self.gvarvals[stmt.varname] = current_var_val * rhs_value print(f"\t{stmt.varname} := {self.gvarvals[stmt.varname]}") else: raise Exception('fixme') elif isinstance(stmt, InCodeConjectureStatement): # print("conjecture " + str(stmt)) assert self.evalTerm( stmt.value_expr, None ), 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 = chcast(bool, self.evalTerm(stmt.test, None)) self.evalCodeBlock( GlobalStateTransform( stmt.true_branch if test else stmt.false_branch)) elif isinstance(stmt, StateTransformLocalVarDec): rhs_value = self.evalTerm(stmt.value_expr, None) self.localvar_vals[stmt.varname] = rhs_value else: raise NotImplementedError( "Unhandled GlobalStateTransformStatement: " + str(stmt))
def assess_event_legal_wrt_nextrules( self, event: Event) -> EventLegalityAssessment: # for far in chain(self.future_obligations, self.future_permissions): # if self.current_event_compatible_with_enabled_NextActionRule(event, far): # print("COMPAT!") entrance_enabled_strong_obligs: List[PartyNextActionRule] = list() entrance_enabled_weak_obligs: List[PartyNextActionRule] = list() entrance_enabled_permissions: List[PartyNextActionRule] = list() entrance_enabled_env_action_rules: List[EnvNextActionRule] = list() # entrance_enabled_future_strong_obligs : List[PartyNextActionRule] = list() # entrance_enabled_future_permissions : List[PartyNextActionRule] = list() nar: NextActionRule ctx = EvalContext( self.gvarvals, self.last_appliedaction_params if self.last_or_current_section.is_anon() else None, use_current_event_params_for_rulebound_action_params=False) for nar in self.last_or_current_section.action_rules(): entrance_enabled = True if nar.entrance_enabled_guard: # next_timestamp param to evalTerm is None because enabled-guards # are evaluated only at section entrance time. if not chcast(bool, self.evalTerm(nar.entrance_enabled_guard, ctx)): entrance_enabled = False if entrance_enabled: if isinstance(nar, EnvNextActionRule): entrance_enabled_env_action_rules.append(nar) else: assert isinstance(nar, PartyNextActionRule) if nar.deontic_keyword == 'may' or nar.deontic_keyword == 'should': entrance_enabled_permissions.append(nar) elif nar.deontic_keyword == 'must': entrance_enabled_strong_obligs.append(nar) elif nar.deontic_keyword == 'obligation-options-include': entrance_enabled_weak_obligs.append(nar) else: assert False # CASE 0a: Exactly one must-later: # if len(entrance_enabled_future_strong_obligs) > 0: # assert (len(entrance_enabled_future_permissions) == len(entrance_enabled_weak_obligs) == # len(entrance_enabled_strong_obligs) == len(entrance_enabled_permissions) == # len(entrance_enabled_env_action_rules) == 0), "usage of must-later currently very restricted" # fo = entrance_enabled_future_strong_obligs[0] # self.future_obligations.append(fo) # return EventOk(True) # # if len(entrance_enabled_future_permissions) > 0: # assert (len(entrance_enabled_future_strong_obligs) == len(entrance_enabled_weak_obligs) == # len(entrance_enabled_strong_obligs) == len(entrance_enabled_permissions) == # len(entrance_enabled_env_action_rules) == 0), "usage of may-later currently very restricted" # fp = entrance_enabled_future_permissions[0] # self.future_permissions.append(fp) # return EventOk(True) # CASE 1: 1 or more entrance-enabled strong obligations if len(entrance_enabled_strong_obligs) > 1: contract_bug( "There are multiple active strong obligations. This is an error." ) return ContractFlawedError() if len(entrance_enabled_strong_obligs) == 1: if len(entrance_enabled_weak_obligs) > 0 or len( entrance_enabled_permissions) > 0 or len( entrance_enabled_env_action_rules) > 0: contract_bug( "There is exactly one active strong obligation, but there is also at least one " "active permission, environment-action, or weak obligation. This is an error." ) return ContractFlawedError() else: assert isinstance(entrance_enabled_strong_obligs[0], PartyNextActionRule) if not self.current_event_compatible_with_enabled_NextActionRule( entrance_enabled_strong_obligs[0]): contract_bug( f"There is exactly one active strong obligation\n" f"\t{entrance_enabled_strong_obligs[0]}\n" f"but it is not compatible with the current event\n" f"\t{event}\n" f"Environment looks like:\n" f"{self.environ_tostr()}") else: time_constraint_ok = self.evalTimeConstraint( entrance_enabled_strong_obligs[0].time_constraint, ctx) # print("time_constraint_ok", time_constraint_ok) if time_constraint_ok: return EventOk() #(None,None) else: return BreachResult({ event.role_id }, f"Strong obligation of {event.role_id} expired or not yet active. See rule {entrance_enabled_strong_obligs[0]}\n with time constraint {entrance_enabled_strong_obligs[0].time_constraint}" ) # CASE 2: 0 entrance-enabled strong obligations (SO action-rules) present_enabled_weak_obligs: List[NextActionRule] = list( filter(lambda c: self.evalTimeConstraint(c.time_constraint, ctx), entrance_enabled_weak_obligs)) present_enabled_permissions: List[NextActionRule] = list( filter(lambda c: self.evalTimeConstraint(c.time_constraint, ctx), entrance_enabled_permissions)) present_enabled_env_action_rules: List[NextActionRule] = list( filter(lambda c: self.evalTimeConstraint(c.time_constraint, ctx), entrance_enabled_env_action_rules)) # present_enabled_nonSO_action_rules : Iterator[NextActionRule] = chain( # present_enabled_permissions.__iter__(), # present_enabled_weak_obligs.__iter__(), # present_enabled_env_action_rules.__iter__()) present_enabled_nonSO_action_rules: List[ NextActionRule] = present_enabled_permissions + present_enabled_weak_obligs + present_enabled_env_action_rules # print("present_enabled_nonSO_action_rules", present_enabled_nonSO_action_rules) # CASE 2a: `event` compatible with exactly one present-enabled non-SO action_rule # for x in present_enabled_nonSO_action_rules: # print("maybe...", self.current_event_compatible_with_enabled_NextActionRule(x) ) compatible_present_enabled_nonSO_action_rules = list( filter( lambda x: self. current_event_compatible_with_enabled_NextActionRule(x), present_enabled_nonSO_action_rules)) if len(compatible_present_enabled_nonSO_action_rules) == 1: return EventOk() #(None,None) if len(compatible_present_enabled_nonSO_action_rules) == 0 and len( present_enabled_weak_obligs) == 0: return BreachResult([ event.role_id ], f"{event.role_id} tried to do an action {event.action_id} that no rule in the current section {self.last_or_current_section_id} permits them to do." ) # assert len(present_enabled_permissions) > 0 or len(present_enabled_env_action_rules) > 0 # self.evalError("TODO: correctly assign breach in this case! And check that there's not a problem with role ids") # print("compatible_present_enabled_nonSO_action_rules", compatible_present_enabled_nonSO_action_rules) # CASE 2b: `event` compatible with more than one present-enabled non-SO action-rules # ...This is actually ok as long as timeConstraintsPartitionFuture isn't used. if len(compatible_present_enabled_nonSO_action_rules) > 1: return EventOk() #(None,None) # CASE 2c: `event` compatible with 0 present-enabled non-SO action-rules # This is a breach. We need to determine what subset of the roles is at fault # You're at fault if you had an entrance-enabled weak obligation (which now expired, # from previous cases). breach_roles = filter( lambda r: len( list( filter(lambda c: c.role_id == r, entrance_enabled_weak_obligs))) > 0, self.top.roles) # print("event:", event) # print("vars:", self.gvarvals) # print("entrance_enabled_permissions", mapjoin(str,entrance_enabled_permissions,', ')) # print("present_enabled_permissions", list(present_enabled_permissions)) # print("present_enabled_weak_obligs", list(present_enabled_weak_obligs)) # print("present_enabled_env_action_rules", list(present_enabled_env_action_rules)) # print() return BreachResult( list(breach_roles), f"{list(breach_roles)} had weak obligations that they didn't fulfill in time." )