def user_defined_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter): """Forward semantics of a user-defined function/method call. :param stmt: call statement to be executed :param state: state before executing the call statement :return: state modified by the call statement """ fname, fcfg = stmt.name, interpreter.cfgs[stmt.name] # add formal function parameters to the state and assign their actual values formal_args = interpreter.fargs[fname] for formal, actual in zip(formal_args, stmt.arguments): rhs = self.semantics(actual, state, interpreter).result state = state.add_variable(formal).forget_variable(formal) state = state.assign({formal}, rhs) if isinstance(actual, Call) and actual.name in interpreter.cfgs: _ret = VariableIdentifier(formal.typ, '{}#return'.format(actual.name)) state = state.remove_variable(_ret) # add local function variables to the state _formal_args = set() for formal in formal_args: _formal_args.add(formal) # if isinstance(formal.typ, (SequenceLyraType, ContainerLyraType)): # _formal_args.add(LengthIdentifier(formal)) # if isinstance(formal.typ, DictLyraType): # _formal_args.add(KeysIdentifier(formal)) # _formal_args.add(ValuesIdentifier(formal)) local_vars = set(fcfg.variables).difference(_formal_args) for local in local_vars: state = state.add_variable(local).forget_variable(local) fresult = interpreter.analyze(fcfg, state) # analyze the function fstate = fresult.get_node_result(fcfg.out_node)[state][-1] state = state.bottom().join(deepcopy(fstate)) # assign return variable if state.result: ret = VariableIdentifier(stmt.typ, '{}#return'.format(fname)) state = state.add_variable(ret).forget_variable(ret) state = state.assign({ret}, state.result) state.result = {ret} # remove local function variables and formal function parameters for local in local_vars: if not local.special: state = state.remove_variable(local) for formal in formal_args: state = state.remove_variable(formal) return state
def __init__(self, key_domain: Type[KeyWrapper], precursory: FularaState, scalar_vars: Set[VariableIdentifier] = None, map_vars: Set[VariableIdentifier] = None, k_pre_k_conv: Callable[[KeyWrapper], KeyWrapper] = lambda x: x): """Map each program variable/dictionary segment to its liveness status. :param key_domain: domain for abstraction of dictionary keys, ranges over the scalar variables and the special key variable v_k, should support backward assignments with _substitute :param precursory: Forward analysis (Fulara analysis) result above the current statement :param scalar_vars: list of scalar variables, whose liveness should be abstracted :param map_vars: list of map variables, whose usage should be abstracted :param k_pre_k_conv: Conversion function to convert from key domain elements of the precursory analysis to key domain elements of this analysis (if the domains differ) """ super().__init__(precursory) self._s_vars = scalar_vars or set() self._m_vars = map_vars or set() self._k_domain = key_domain self._scalar_liveness = StrongLivenessState(scalar_vars) arguments = {} for dv in map_vars: typ = dv.typ if isinstance(typ, DictLyraType): k_var = VariableIdentifier(typ.key_typ, k_name) elif isinstance(typ, ListLyraType): k_var = VariableIdentifier(IntegerLyraType(), k_name) else: raise TypeError("Map variables should be of type DictLyraType or ListLyraType") if typ not in arguments: arguments[typ] = {'key_domain': key_domain, 'value_domain': LivenessLattice, 'key_d_args': {'scalar_variables': scalar_vars, 'k_var': k_var}} lattices = defaultdict(lambda: FularaLattice) self._dict_liveness = Store(map_vars, lattices, arguments) # start with 'dead' for var in map_vars: self._dict_liveness.store[var].empty() # self._length_usage self._k_pre_k_conv = k_pre_k_conv
def __init__(self, key_domain: Type[Union[KeyWrapper, State]], scalar_vars: Set[VariableIdentifier] = None, map_vars: Set[VariableIdentifier] = None): """Map each program variable/dictionary segment to its usage status. :param key_domain: domain for abstraction of dictionary keys, ranges over the scalar variables and the special key variable v_k :param scalar_vars: list of scalar variables, whose usage should be abstracted :param map_vars: list of map variables, whose usage should be abstracted """ super().__init__() self._s_vars = scalar_vars or set() self._m_vars = map_vars or set() self._k_domain = key_domain self._scalar_usage = SimpleUsageStore(scalar_vars) arguments = {} for dv in map_vars: typ = dv.typ if isinstance(typ, DictLyraType): k_var = VariableIdentifier(typ.key_typ, k_name) elif isinstance(typ, ListLyraType): k_var = VariableIdentifier(IntegerLyraType(), k_name) else: raise TypeError( "Map variables should be of type DictLyraType or ListLyraType" ) if typ not in arguments: arguments[typ] = { 'key_domain': key_domain, 'value_domain': UsageLattice, 'key_d_args': { 'scalar_variables': scalar_vars, 'k_var': k_var } } lattices = defaultdict(lambda: FularaLattice) self._dict_usage = Store(map_vars, lattices, arguments) # start with 'not used' for var in map_vars: self._dict_usage.store[var].empty() self._loop_flag = False
def _cast_call_semantics(self, stmt: Call, state: State, typ: LyraType) -> State: """Semantics of a call to 'int' or 'bool'. :param stmt: call to 'int' or 'bool' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ if len(stmt.arguments) != 1: error = f"Semantics for multiple arguments of {stmt.name} is not yet implemented!" raise NotImplementedError(error) argument = self.semantics(stmt.arguments[0], state).result result = set() for expression in argument: if isinstance(expression, Input): result.add(Input(typ)) elif isinstance(expression, Literal): result.add(Literal(typ, expression.val)) elif isinstance(expression, VariableIdentifier): result.add(VariableIdentifier(typ, expression.name)) elif isinstance(expression, Subscription): pass # TODO else: error = f"Argument of type {expression.typ} of {stmt.name} is not yet supported!" raise NotImplementedError(error) state.result = result return state
def split_call_semantics(self, stmt: Call, state: State) -> State: if len(stmt.arguments) != 1: error = f"Semantics for multiple arguments of {stmt.name} is not yet implemented!" raise NotImplementedError(error) argument = self.semantics(stmt.arguments[0], state).result result = set() for arg in argument: assert isinstance(arg, Expression) if not isinstance(arg.typ, StringLyraType): error = f"Call to {stmt.name} of argument with unexpected type!" raise ValueError(error) typ = ListLyraType(StringLyraType()) if isinstance(arg, Literal): # "a b c".split() -> ["a", "b", "c"] items = [ Literal(StringLyraType(), val) for val in arg.val.split() ] result.add(ListDisplay(typ, items)) continue elif isinstance(arg, VariableIdentifier): # x.split() result.add(VariableIdentifier(typ, arg.name)) continue elif isinstance(arg, Input): # input().split() result.add(Input(typ)) continue error = f"Call to {stmt.name} of unexpected argument!" raise ValueError(error) state.result = result return state
def assignment_semantics(self, stmt: Assignment, state, interpreter) -> State: """Forward semantics of an assignment. :param stmt: assignment statement to be executed :param state: state before executing the assignment :return: state modified by the assignment """ lhs = self.semantics(stmt.left, state, interpreter).result # lhs evaluation ret = None if isinstance(stmt.right, Call) and stmt.right.name in interpreter.cfgs: ret = VariableIdentifier(stmt.right.typ, '{}#return'.format(stmt.right.name)) state = state.add_variable(ret).forget_variable(ret) rhs = self.semantics(stmt.right, state, interpreter).result # rhs evaluation state = state.assign(lhs, rhs) if isinstance(stmt.right, Call) and stmt.right.name in interpreter.cfgs: state = state.remove_variable(ret) return state
def strip_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter) -> State: if len(stmt.arguments) != 1: error = f"Semantics for multiple arguments of {stmt.name} is not yet implemented!" raise NotImplementedError(error) argument = self.semantics(stmt.arguments[0], state, interpreter).result result = set() for arg in argument: assert isinstance(arg, Expression) if not isinstance(arg.typ, StringLyraType): error = f"Call to {stmt.name} of argument with unexpected type!" raise ValueError(error) typ = StringLyraType() if isinstance(arg, Input): # input().strip() result.add(Input(typ)) continue elif isinstance(arg, VariableIdentifier): # x.strip() result.add(VariableIdentifier(typ, arg.name)) continue elif isinstance(arg, Subscription): # x[i].strip() result.add(Subscription(typ, arg.target, arg.key)) continue error = f"Call to {stmt.name} of unexpected argument {arg}!" raise ValueError(error) state.result = result return state
def do(variable): if isinstance(variable.typ, StringLyraType): typ = ListLyraType(variable.typ) state.result = {VariableIdentifier(typ, variable.name)} return state elif isinstance(variable.typ, ListLyraType): state.result = {variable} return state elif isinstance(variable.typ, TupleLyraType): raise NotImplementedError(f"Conversion to list of {variable.typ} is not yet implemented!") elif isinstance(variable.typ, SetLyraType): typ = ListLyraType(variable.typ.typ) state.result = {VariableIdentifier(typ, variable.name)} return state elif isinstance(variable.typ, DictLyraType): typ = ListLyraType(variable.typ.key_typ) state.result = {VariableIdentifier(typ, variable.name)} return state raise TypeError(f"Unexpected type {variable.typ} for list conversion!")
def __init__(self, key_domain: Type[Union[KeyWrapper, State]], scalar_vars: Set[VariableIdentifier] = None, dict_vars: Set[VariableIdentifier] = None): """Map each program variable/dictionary segment to its usage status. :param key_domain: domain for abstraction of dictionary keys, ranges over the scalar variables and the special key variable v_k :param scalar_vars: list of scalar variables, whose usage should be abstracted :param dict_vars: list of dictionary variables, whose usage should be abstracted """ super().__init__() if scalar_vars is None: scalar_vars = set() if dict_vars is None: dict_vars = set() self._s_vars = scalar_vars self._d_vars = dict_vars self._k_domain = key_domain self._scalar_usage = SimpleUsageStore(scalar_vars) arguments = {} for dv in dict_vars: typ = dv.typ if isinstance(typ, DictLyraType): # should be true if typ not in arguments: # if issubclass(key_domain, Store): # not relational -> don't need scalar vars # key_vars = [] # else: # key_vars = scalar_vars.copy() # if issubclass(value_domain, Store): # value_vars = [] # else: # value_vars = scalar_vars.copy() k_var = VariableIdentifier(typ.key_type, k_name) arguments[typ] = { 'key_domain': key_domain, 'value_domain': UsageLattice, 'key_d_args': { 'scalar_variables': scalar_vars, 'k_var': k_var } } else: raise TypeError( "Dictionary variables should be of DictLyraType") lattices = defaultdict(lambda: FularaLattice) self._dict_usage = Store(dict_vars, lattices, arguments)
def bitxor_call_semantics(self, stmt: Call, state: State, interpreter: Interpreter) -> State: """Semantics of calls to '^' (bitwise xor). :param stmt: call to '^' (bitwise xor) to be executed :param state: state before executing the call statement :return: state modified by the call statement """ argument = self.semantics(stmt.arguments[0], state, interpreter).result result = set() for arg in argument: result.add(VariableIdentifier(stmt.typ, arg.name)) state.result = result return state
def input_var(self): return VariableIdentifier(StringLyraType(), '.IN')
def visit_Input(self, expr: Input): name = "{}.{}".format(self.pp.line, self.nonce) return VariableIdentifier(expr.typ, name)
def visit_VariableIdentifier(self, expr: VariableIdentifier): return VariableIdentifier(expr.typ, expr.name)