def range_call_semantics(self, stmt: Call, state: State) -> State: result = set() if len(stmt.arguments) == 1: start = Literal(IntegerLyraType(), "0") stops = self.semantics(stmt.arguments[0], state).result step = Literal(IntegerLyraType(), "1") for stop in stops: range = Range(stmt.typ, start, stop, step) result.add(range) state.result = result return state elif len(stmt.arguments) == 2: starts = self.semantics(stmt.arguments[0], state).result stops = self.semantics(stmt.arguments[1], state).result step = Literal(IntegerLyraType(), "1") for start in starts: for stop in stops: range = Range(stmt.typ, start, stop, step) result.add(range) state.result = result return state elif len(stmt.arguments) == 3: starts = self.semantics(stmt.arguments[0], state).result stops = self.semantics(stmt.arguments[1], state).result steps = self.semantics(stmt.arguments[2], state).result for start in starts: for stop in stops: for step in steps: range = Range(stmt.typ, start, stop, step) result.add(range) state.result = result return state error = f"Call to {stmt.name} with unexpected number of arguments!" raise ValueError(error)
def _join(self, other: InputLattice) -> InputLattice: # TODO: """``Ɣ(self) ⋃ Ɣ(other) ⊆ Ɣ(self \/ other)``.""" def do(constraint1, constraint2): if isinstance(constraint1, tuple) and isinstance(constraint2, tuple): # the constraints are StarConstraints or BasicConstraints if not constraint1 and not constraint2: # the constraints are StarConstraints return () else: # the constraints are BasicConstraints pp1: ProgramPoint = constraint1[0] pp2: ProgramPoint = constraint2[0] pp: ProgramPoint = pp1 if pp1.line <= pp2.line else pp2 l1: Tuple[JSONMixin, ...] = constraint1[1] l2: Tuple[JSONMixin, ...] = constraint2[1] return pp, tuple(x.join(y) for x, y in zip(l1, l2)) else: # the constraints are InputLattices m1: Expression = constraint1.multiplier m2: Expression = constraint2.multiplier c1 = constraint1.constraints c2 = constraint2.constraints if isinstance(m1, Literal) and isinstance(m2, Literal): assert isinstance(m1.typ, IntegerLyraType) assert isinstance(m2.typ, IntegerLyraType) val = str(min(int(m1.val), int(m2.val))) m: Expression = Literal(IntegerLyraType(), val) else: m: Expression = m1 if m1 == m2 else one r: bool = m1 != m2 # are the multipliers different? c = [do(x, y) for x, y in zip(c1, c2)] # do the list of constraints have different length? r = r or len(c1) != len(c2) if r: # multipliers of lengths of list of constraints are different c.append(()) # add a star constraint return AssumptionState.InputStack.InputLattice(m, c) multiplier1 = self.multiplier multiplier2 = other.multiplier assert isinstance(multiplier1, Literal) and multiplier1.val == "1" assert isinstance(multiplier2, Literal) and multiplier2.val == "1" if len(self.constraints) == len(other.constraints): constraints = [do(x, y) for x, y in zip(self.constraints, other.constraints)] one = Literal(IntegerLyraType(), "1") return self._replace(AssumptionState.InputStack.InputLattice(one, constraints)) else: # the join is happening at a loop head assert abs(len(self.constraints) - len(other.constraints)) == 1 one = Literal(IntegerLyraType(), "1") if len(self.constraints) < len(other.constraints): tail = other.constraints[1:] constraints = [do(x, y) for x, y in zip(self.constraints, tail)] other.constraints[1:] = constraints join = AssumptionState.InputStack.InputLattice(one, other.constraints) self._replace(join) else: tail = self.constraints[1:] constraints = [do(x, y) for x, y in zip(tail, other.constraints)] self.constraints[1:] = constraints join = AssumptionState.InputStack.InputLattice(one, self.constraints) self._replace(join) return self
def visit_Num(self, node, types=None, typ=None): """Visitor function for a number (integer, float, or complex). The n attribute stores the value, already converted to the relevant type.""" pp = ProgramPoint(node.lineno, node.col_offset) if isinstance(node.n, int): expr = Literal(IntegerLyraType(), str(node.n)) return LiteralEvaluation(pp, expr) elif isinstance(node.n, float): expr = Literal(FloatLyraType(), str(node.n)) return LiteralEvaluation(pp, expr) raise NotImplementedError(f"Num of type {node.n.__class__.__name__} is unsupported!")
def __init__(self, multiplier: Expression = Literal( IntegerLyraType(), "1"), constraints: List[InputConstraint] = list()): super().__init__() self._multiplier = multiplier self._constraints = constraints
def do(constraint1, constraint2): if isinstance(constraint1, tuple) and isinstance(constraint2, tuple): # the constraints are StarConstraints or BasicConstraints if not constraint1 and not constraint2: # the constraints are StarConstraints return () else: # the constraints are BasicConstraints pp1: ProgramPoint = constraint1[0] pp2: ProgramPoint = constraint2[0] pp: ProgramPoint = pp1 if pp1.line <= pp2.line else pp2 l1: Tuple[JSONMixin, ...] = constraint1[1] l2: Tuple[JSONMixin, ...] = constraint2[1] return pp, tuple(x.join(y) for x, y in zip(l1, l2)) else: # the constraints are InputLattices m1: Expression = constraint1.multiplier m2: Expression = constraint2.multiplier c1 = constraint1.constraints c2 = constraint2.constraints if isinstance(m1, Literal) and isinstance(m2, Literal): assert isinstance(m1.typ, IntegerLyraType) assert isinstance(m2.typ, IntegerLyraType) val = str(min(int(m1.val), int(m2.val))) m: Expression = Literal(IntegerLyraType(), val) else: m: Expression = m1 if m1 == m2 else one r: bool = m1 != m2 # are the multipliers different? c = [do(x, y) for x, y in zip(c1, c2)] # do the list of constraints have different length? r = r or len(c1) != len(c2) if r: # multipliers of lengths of list of constraints are different c.append(()) # add a star constraint return AssumptionState.InputStack.InputLattice(m, c)
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 visit_NameConstant(self, node, types=None, typ=None): """Visitor function for True, False or None. The value attribute stores the constant.""" if isinstance(node.value, bool): pp = ProgramPoint(node.lineno, node.col_offset) expr = Literal(BooleanLyraType(), str(node.value)) return LiteralEvaluation(pp, expr) raise NotImplementedError(f"Constant {node.value.__class__.__name__} is unsupported!")
def range_call_semantics(self, stmt: Call, state: State) -> State: arguments = [ self.semantics(arg, state).result.pop() for arg in stmt.arguments ] start = Literal(IntegerLyraType(), "0") step = Literal(IntegerLyraType(), "1") if len(arguments) == 1: end = arguments[0] elif len(arguments) in [2, 3]: start = arguments[0] end = arguments[1] if len(arguments) == 3: step = arguments[2] else: error = f"Semantics for range call with {len(arguments)} arguments is not implemented!" raise NotImplementedError(error) state.result = {Range(stmt.typ, start, end, step)} return state
def _join(self, other: InputLattice) -> InputLattice: def do(constraint1, constraint2): if isinstance(constraint1, tuple) and isinstance( constraint2, tuple): # the constraints are StarConstraints or BasicConstraints if not constraint1 and not constraint2: # the constraints are StarConstraints return () else: # the constraints are BasicConstraints pp1: ProgramPoint = constraint1[0] pp2: ProgramPoint = constraint2[0] pp: ProgramPoint = pp1 if pp1.line <= pp2.line else pp2 l1: Tuple[JSONMixin, ...] = constraint1[1] l2: Tuple[JSONMixin, ...] = constraint2[1] return pp, tuple(x.join(y) for x, y in zip(l1, l2)) else: # the constraints are InputLattices assert isinstance( constraint1, AssumptionState.InputStack.InputLattice) assert isinstance( constraint2, AssumptionState.InputStack.InputLattice) m1: Expression = constraint1.multiplier m2: Expression = constraint2.multiplier c1 = constraint1.constraints c2 = constraint2.constraints is_one1 = isinstance(m1, Literal) and m1.val == "1" is_one2 = isinstance(m2, Literal) and m2.val == "1" if is_one1 and is_one2: m = Literal(IntegerLyraType(), "1") c = [do(x, y) for x, y in zip(c1, c2)] if len(c1) != len( c2 ): # lengths of list of constraints are different c.append(()) # add a star constraint else: assert m1 == m2 m: Expression = m1 c = [do(x, y) for x, y in zip(c1, c2)] c.extend(c1[len(c2):]) c.extend(c2[len(c1):]) return AssumptionState.InputStack.InputLattice(m, c) multiplier1 = self.multiplier multiplier2 = other.multiplier assert isinstance(multiplier1, Literal) and multiplier1.val == "1" assert isinstance(multiplier2, Literal) and multiplier2.val == "1" constraints = [ do(x, y) for x, y in zip(self.constraints, other.constraints) ] one = Literal(IntegerLyraType(), "1") return self._replace( AssumptionState.InputStack.InputLattice(one, constraints))
def visit_BinaryArithmeticOperation(self, expr, evaluation=None, value=None, state=None): updated = super().visit_BinaryArithmeticOperation( expr, evaluation, value, state) if expr.operator == BinaryArithmeticOperation.Operator.Div: left = expr.right operator = BinaryComparisonOperation.Operator.NotEq right = Literal(IntegerLyraType(), "0") condition = BinaryComparisonOperation(BooleanLyraType(), left, operator, right) return updated.assume({condition}) return updated
def do(lattice, typ, key, current: Lattice) -> Lattice: updated: Lattice = current if isinstance(typ, (BooleanLyraType, IntegerLyraType, FloatLyraType, StringLyraType)): if hasattr(lattice, 'from_literal'): literal = Literal(typ, key) updated = updated.join(lattice.from_literal(literal)) else: updated = updated.join(lattice().top()) else: assert isinstance(typ, TupleLyraType) val = literal_eval(key) for i, subtyp in enumerate(typ.typs): updated = do(lattice, subtyp, val[i], updated) return updated
def from_literal(cls, lattice: Type[Lattice], literal: Literal, bound: int = 3): """Indexed lattice creation from a literal expression. :param lattice: the (sub)lattice whose elements are to be indexed :param literal: the literal :param bound: bound on the size of the index """ if isinstance(literal.typ, StringLyraType): index = dict() for idx, item in enumerate(literal.val): if hasattr(lattice, 'from_literal'): index[str(idx)] = lattice.from_literal( Literal(StringLyraType(), item)) else: index[str(idx)] = lattice().top() return cls(lattice, index, bound) return cls(lattice, None, 3)
def _cast_call_semantics(self, stmt: Call, state, interpreter, typ: LyraType) -> State: """Semantics of a call to 'int', 'bool', or 'float'. :param stmt: call to 'int', 'bool', 'float', or 'str' 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, interpreter).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)) else: result.add(CastOperation(typ, expression)) state.result = result return state
def visit_Literal(self, expr: Literal): return Literal(expr.typ, expr.val)
def top(self): """The top lattice element is ``1 * [★]``.""" one = Literal(IntegerLyraType(), "1") return self._replace( AssumptionState.InputStack.InputLattice(one, [()]))
def visit_Str(self, node, types=None, typ=None): """Visitor function for a string. The s attribute stores the value.""" pp = ProgramPoint(node.lineno, node.col_offset) expr = Literal(StringLyraType(), node.s) return LiteralEvaluation(pp, expr)