def __init__(self, variables: Set[VariableIdentifier], precursory: State = None): """Map each program variable to the type representing its value. :param variables: set of program variables """ lattices = defaultdict(lambda: TypeLattice) arguments = defaultdict( lambda: {'type_status': TypeLattice.Status.String}) arguments[BooleanLyraType()] = { 'type_status': TypeLattice.Status.Boolean } arguments[ListLyraType(BooleanLyraType())] = { 'type_status': TypeLattice.Status.Boolean } arguments[IntegerLyraType()] = { 'type_status': TypeLattice.Status.Integer } arguments[ListLyraType(IntegerLyraType())] = { 'type_status': TypeLattice.Status.Integer } arguments[FloatLyraType()] = {'type_status': TypeLattice.Status.Float} arguments[ListLyraType(FloatLyraType())] = { 'type_status': TypeLattice.Status.Float } super().__init__(variables, lattices, arguments) InputMixin.__init__(self, precursory)
def visit_While(self, node, types=None, typ=None): header_node = Loop(self._id_gen.next) cfg = self._translate_body(node.body, types, typ) body_in_node = cfg.in_node body_out_node = cfg.out_node pp = ProgramPoint(node.test.lineno, node.test.col_offset) test = self.visit(node.test, types, BooleanLyraType()) neg_test = Call(pp, "not", [test], BooleanLyraType()) cfg.add_node(header_node) cfg.in_node = header_node cfg.add_edge(Conditional(header_node, test, body_in_node, Edge.Kind.LOOP_IN)) cfg.add_edge(Conditional(header_node, neg_test, None)) if body_out_node: # if control flow can exit the body at all, add an unconditional LOOP_OUT edge cfg.add_edge(Unconditional(body_out_node, header_node, Edge.Kind.LOOP_OUT)) if node.orelse: # if there is else branch orelse_cfg = self._translate_body(node.orelse, types) if orelse_cfg.out_node: # if control flow can exit the else at all, add an unconditional DEFAULT edge orelse_cfg.add_edge(Unconditional(orelse_cfg.out_node, None, Edge.Kind.DEFAULT)) cfg.append(orelse_cfg) for special_edge, edge_type in cfg.special_edges: if edge_type == LooseControlFlowGraph.SpecialEdgeType.CONTINUE: cfg.add_edge(Unconditional(special_edge.source, header_node, Edge.Kind.LOOP_OUT)) elif edge_type == LooseControlFlowGraph.SpecialEdgeType.BREAK: cfg.add_edge(Unconditional(special_edge.source, None, Edge.Kind.LOOP_OUT)) cfg.special_edges.clear() return cfg
def visit_If(self, node, types=None, typ=None): body_cfg = self._translate_body(node.body, types) pp = ProgramPoint(node.test.lineno, node.test.col_offset) test = self.visit(node.test, types, BooleanLyraType()) neg_test = Call(pp, "not", [test], BooleanLyraType()) body_cfg.add_edge(Conditional(None, test, body_cfg.in_node, Edge.Kind.IF_IN)) if body_cfg.out_node: # if control flow can exit the body at all, add an unconditional IF_OUT edge body_cfg.add_edge(Unconditional(body_cfg.out_node, None, Edge.Kind.IF_OUT)) if node.orelse: # if there is else branch orelse_cfg = self._translate_body(node.orelse, types) orelse_cfg.add_edge(Conditional(None, neg_test, orelse_cfg.in_node, Edge.Kind.IF_IN)) if orelse_cfg.out_node: # if control flow can exit the else at all, add an unconditional IF_OUT edge orelse_cfg.add_edge(Unconditional(orelse_cfg.out_node, None, Edge.Kind.IF_OUT)) else: orelse_cfg = LooseControlFlowGraph() orelse_cfg.add_edge(Conditional(None, neg_test, None, Edge.Kind.DEFAULT)) # extend special edges with IF_OUT edges and additional necessary dummy nodes for special_edge, edge_type in body_cfg.special_edges: dummy = _dummy(self._id_gen) body_cfg.add_node(dummy) # add a new IF_OUT edge where the special edge is at the moment, # ending in new dummy node body_cfg.add_edge(Unconditional(special_edge.source, dummy, Edge.Kind.IF_OUT)) # change position of special edge to be AFTER the new dummy special_edge._source = dummy cfg = body_cfg.combine(orelse_cfg) return cfg
def _assume(self, condition: Expression) -> 'IntervalState': normal = NegationFreeNormalExpression().visit(condition) if isinstance(normal, VariableIdentifier) and isinstance(normal.typ, BooleanLyraType): evaluation = self._evaluation.visit(normal, self, dict()) return self._refinement.visit(normal, evaluation, IntervalLattice(1, 1), self) elif isinstance(normal, UnaryBooleanOperation): if normal.operator == UnaryBooleanOperation.Operator.Neg: expression = normal.expression if isinstance(expression, VariableIdentifier): if isinstance(expression.typ, BooleanLyraType): evaluation = self._evaluation.visit(normal, self, dict()) false = IntervalLattice(0, 0) return self._refinement.visit(expression, evaluation, false, self) elif isinstance(normal, BinaryBooleanOperation): if normal.operator == BinaryBooleanOperation.Operator.And: right = deepcopy(self)._assume(normal.right) return self._assume(normal.left).meet(right) if normal.operator == BinaryBooleanOperation.Operator.Or: right = deepcopy(self)._assume(normal.right) return self._assume(normal.left).join(right) elif isinstance(normal, BinaryComparisonOperation): if normal.operator == BinaryComparisonOperation.Operator.Is: error = f"Assumption of a comparison with {normal.operator} is unsupported!" raise ValueError(error) elif normal.operator == BinaryComparisonOperation.Operator.IsNot: error = f"Assumption of a comparison with {normal.operator} is unsupported!" raise ValueError(error) elif normal.operator == BinaryComparisonOperation.Operator.In: if isinstance(normal.right, Range): typ = BooleanLyraType() left = normal.left lower_operator = BinaryComparisonOperation.Operator.GtE lower_right = normal.right.start lower = BinaryComparisonOperation(typ, left, lower_operator, lower_right) upper_operator = BinaryComparisonOperation.Operator.Lt upper_right = normal.right.stop upper = BinaryComparisonOperation(typ, left, upper_operator, upper_right) right = deepcopy(self)._assume(upper) return self._assume(lower).meet(right) elif normal.operator == BinaryComparisonOperation.Operator.NotIn: if isinstance(normal.right, Range): typ = BooleanLyraType() left = normal.left lower_operator = BinaryComparisonOperation.Operator.Lt lower_right = normal.right.start lower = BinaryComparisonOperation(typ, left, lower_operator, lower_right) upper_operator = BinaryComparisonOperation.Operator.GtE upper_right = normal.right.stop upper = BinaryComparisonOperation(typ, left, upper_operator, upper_right) right = deepcopy(self)._assume(upper) return self._assume(lower).join(right) evaluation = self._evaluation.visit(normal.left, self, dict()) return self._refinement.visit(normal.left, evaluation, IntervalLattice(upper=0), self) error = f"Assumption of a {normal.__class__.__name__} expression is unsupported!" raise ValueError(error)
def visit_Compare(self, node, types=None, typ=None): pp = ProgramPoint(node.lineno, node.col_offset) last_comp = self.visit(node.comparators[0], types, None) result = Call(pp, type(node.ops[0]).__name__.lower(), [self.visit(node.left, types, None), last_comp], BooleanLyraType()) for op, comp in list(zip(node.ops, node.comparators))[1:]: cur_call = Call( pp, type(op).__name__.lower(), [last_comp, self.visit(comp, types, None)], BooleanLyraType()) result = Call(pp, 'and', [result, cur_call], BooleanLyraType()) return result
def visit_For(self, node, types=None, typ=None): header_node = Loop(self._id_gen.next) cfg = self._translate_body(node.body, types, typ) body_in_node = cfg.in_node body_out_node = cfg.out_node pp = ProgramPoint(node.target.lineno, node.target.col_offset) iteration = self.visit(node.iter, types, ListLyraType(IntegerLyraType())) if isinstance(iteration, Call) and iteration.name == "range": target_type = IntegerLyraType() else: error = f"The for loop iteration statment {node.iter} is not yet translatable to CFG!" raise NotImplementedError(error) target = self.visit(node.target, types, target_type) test = Call(pp, "in", [target, iteration], BooleanLyraType()) neg_test = Call(pp, "not", [test], BooleanLyraType()) cfg.add_node(header_node) cfg.in_node = header_node cfg.add_edge( Conditional(header_node, test, body_in_node, Edge.Kind.LOOP_IN)) cfg.add_edge(Conditional(header_node, neg_test, None)) if body_out_node: # if control flow can exit the body at all, add an unconditional LOOP_OUT edge cfg.add_edge( Unconditional(body_out_node, header_node, Edge.Kind.LOOP_OUT)) if node.orelse: # if there is else branch orelse_cfg = self._translate_body(node.orelse, types) if orelse_cfg.out_node: # if control flow can exit the else at all, add an unconditional DEFAULT edge orelse_cfg.add_edge( Unconditional(orelse_cfg.out_node, None, Edge.Kind.DEFAULT)) cfg.append(orelse_cfg) for special_edge, edge_type in cfg.special_edges: if edge_type == LooseControlFlowGraph.SpecialEdgeType.CONTINUE: cfg.add_edge( Unconditional(special_edge.source, header_node, Edge.Kind.LOOP_OUT)) elif edge_type == LooseControlFlowGraph.SpecialEdgeType.BREAK: cfg.add_edge( Unconditional(special_edge.source, None, Edge.Kind.LOOP_OUT)) cfg.special_edges.clear() return cfg
def visit_If(self, node, types=None, typ=None): """Visitor function for an if statement. The attribute test stores a single AST node. The attributes body and orelse each store a list of AST nodes to be executed.""" pp = ProgramPoint(node.test.lineno, node.test.col_offset) body = self._visit_body(node.body, types, typ) test = self.visit(node.test, types, BooleanLyraType()) body.add_edge(Conditional(None, test, body.in_node, Edge.Kind.IF_IN)) if body.out_node: # control flow can exit the body # add an unconditional IF_OUT edge body.add_edge(Unconditional(body.out_node, None, Edge.Kind.IF_OUT)) if node.orelse: # there is an else branch orelse = self._visit_body(node.orelse, types, typ) not_test = Call(pp, 'not', [test], BooleanLyraType()) orelse.add_edge(Conditional(None, not_test, orelse.in_node, Edge.Kind.IF_IN)) if orelse.out_node: # control flow can exit the else # add an unconditional IF_OUT edge orelse.add_edge(Unconditional(orelse.out_node, None, Edge.Kind.IF_OUT)) # handle special edges for special, _ in orelse.special_edges: # create dummy node dummy = _dummy_node(self._id_gen) orelse.add_node(dummy) # add an unconditional IF_OUT edge to the newly created dummy node orelse.add_edge(Unconditional(special.source, dummy, Edge.Kind.IF_OUT)) # move the special edge after the dummy node special._source = dummy else: # there is no else branch orelse = LooseControlFlowGraph() not_test = Call(pp, 'not', [test], BooleanLyraType()) orelse.add_edge(Conditional(None, not_test, None, Edge.Kind.DEFAULT)) # handle special edges for special, edge_type in body.special_edges: # create dummy node dummy = _dummy_node(self._id_gen) body.add_node(dummy) # add an unconditional IF_OUT edge to the newly created dummy node body.add_edge(Unconditional(special.source, dummy, Edge.Kind.IF_OUT)) # move the special edge after the dummy node special._source = dummy cfg = body.combine(orelse) return cfg
def visit_VariableIdentifier(self, expr: 'VariableIdentifier', invert=False): if isinstance(expr.typ, BooleanLyraType) and invert: operator = UnaryBooleanOperation.Operator.Neg return UnaryBooleanOperation(BooleanLyraType(), operator, expr) return expr # nothing to be done
def visit_NameConstant(self, node, types=None, typ=None): 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"Name constant {node.value.__class__.__name__} is not yet supported!")
def bool_call_semantics(self, stmt: Call, state: State) -> State: """Semantics of a call to 'bool'. :param stmt: call to 'bool' to be executed :param state: state before executing the call statement :return: state modified by the call statement """ return self._cast_call_semantics(stmt, state, BooleanLyraType())
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 visit_IfExp(self, node, targets, op=None, types=None, typ=None): """Visitor function for an if expression. The components of the expression are stored in the attributes test, body, and orelse.""" pp = ProgramPoint(node.lineno, node.col_offset) then = CFGFactory(self._id_gen) body = self.visit(node.body, types, typ) assignments = list() for target in targets: if op: left = self.visit(target, types, typ) name = type(op).__name__.lower() value = Call(pp, name, [left, body], left.typ) assignments.append(Assignment(pp, left, value)) else: assignments.append(Assignment(pp, self.visit(target, types, typ), body)) then.add_stmts(assignments) then.complete_basic_block() then = then.cfg test = self.visit(node.test, types, BooleanLyraType()) then.add_edge(Conditional(None, test, then.in_node, Edge.Kind.IF_IN)) then.add_edge(Unconditional(then.out_node, None, Edge.Kind.IF_OUT)) orelse = CFGFactory(self._id_gen) body = self.visit(node.orelse, types, typ) assignments = list() for target in targets: if op: left = self.visit(target, types, typ) name = type(op).__name__.lower() value = Call(pp, name, [left, body], left.typ) assignments.append(Assignment(pp, left, value)) else: assignments.append(Assignment(pp, self.visit(target, types, typ), body)) orelse.add_stmts(assignments) orelse.complete_basic_block() orelse = orelse.cfg not_test = Call(pp, 'not', [test], BooleanLyraType()) orelse.add_edge(Conditional(None, not_test, orelse.in_node, Edge.Kind.IF_IN)) orelse.add_edge(Unconditional(orelse.out_node, None, Edge.Kind.IF_OUT)) return then.combine(orelse)
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 visit_While(self, node, types=None, typ=None): """Visitor function for an while statement. The attribute test stores a single AST node. The attributes body and orelse each store a list of AST nodes to be executed.""" pp = ProgramPoint(node.test.lineno, node.test.col_offset) body = self._visit_body(node.body, types, typ) test = self.visit(node.test, types, BooleanLyraType()) header = Loop(self._id_gen.next) body_in_node = body.in_node body_out_node = body.out_node body.add_node(header) body.in_node = header body.add_edge(Conditional(header, test, body_in_node, Edge.Kind.LOOP_IN)) not_test = Call(pp, 'not', [test], BooleanLyraType()) body.add_edge(Conditional(header, not_test, None)) if body_out_node: # control flow can exit the body # add an unconditional LOOP_OUT edge body.add_edge(Unconditional(body_out_node, header, Edge.Kind.LOOP_OUT)) if node.orelse: # there is an else branch orelse = self._visit_body(node.orelse, types) if orelse.out_node: # control flow can exit the else # add an unconditional DEFAULT edge orelse.add_edge(Unconditional(orelse.out_node, None, Edge.Kind.DEFAULT)) body.append(orelse) # handle special edges for special, kind in body.special_edges: if kind == LooseControlFlowGraph.SpecialEdgeType.CONTINUE: body.add_edge(Unconditional(special.source, header, Edge.Kind.LOOP_OUT)) elif kind == LooseControlFlowGraph.SpecialEdgeType.BREAK: body.add_edge(Unconditional(special.source, None, Edge.Kind.LOOP_OUT)) body.special_edges.clear() return body
def visit_For(self, node, types=None, typ=None): """Visitor function for a for statement. The attribute target stores the variable(s) the loop assigns to (as a single Name, Tuple, or List node). The attribute iter stores a single AST node representing the item to be looped over. The attributes body and orelse each store a list of AST nodes to be executed.""" pp = ProgramPoint(node.target.lineno, node.target.col_offset) iterated = self.visit(node.iter, types, None) target_typ = None if isinstance(iterated, VariableAccess): if isinstance(iterated.typ, ListLyraType): # iteration over list items target_typ = iterated.typ.typ elif isinstance(iterated.typ, SetLyraType): # iteration over set items target_typ = iterated.typ.typ elif isinstance(iterated.typ, DictLyraType): # iteration over dictionary keys iterated = Call(iterated.pp, 'keys', [iterated], SetLyraType(iterated.typ.key_typ)) target_typ = iterated.typ.typ elif isinstance(iterated, Call): if iterated.name == 'range': assert isinstance(iterated.typ, ListLyraType) target_typ = iterated.typ.typ elif iterated.name == 'items' or iterated.name == 'keys' or iterated.name == 'values': assert isinstance(iterated.typ, SetLyraType) target_typ = iterated.typ.typ elif iterated.name == 'list': assert len(iterated.arguments) == 1 if isinstance(iterated.arguments[0].typ, ListLyraType): target_typ = iterated.arguments[0].typ.typ else: error = "The type of the target {} is not yet determinable!".format(iterated) raise NotImplementedError(error) else: error = "The iteration attribute {} is not yet translatable to CFG!".format(iterated) raise NotImplementedError(error) target = self.visit(node.target, types, target_typ) body = self._visit_body(node.body, types, typ) test = Call(pp, 'in', [target, iterated], BooleanLyraType(), forloop=True) header = Loop(self._id_gen.next) body_in_node = body.in_node body_out_node = body.out_node body.add_node(header) body.in_node = header body.add_edge(Conditional(header, test, body_in_node, Edge.Kind.LOOP_IN)) not_test = Call(pp, 'notin', [target, iterated], BooleanLyraType(), forloop=True) body.add_edge(Conditional(header, not_test, None)) if body_out_node: # control flow can exit the body # add an unconditional LOOP_OUT edge body.add_edge(Unconditional(body_out_node, header, Edge.Kind.LOOP_OUT)) if node.orelse: # there is an else branch orelse = self._visit_body(node.orelse, types) if orelse.out_node: # control flow can exit the else # add an unconditional DEFAULT edge orelse.add_edge(Unconditional(orelse.out_node, None, Edge.Kind.DEFAULT)) body.append(orelse) # handle special edges for special, kind in body.special_edges: if kind == LooseControlFlowGraph.SpecialEdgeType.CONTINUE: body.add_edge(Unconditional(special.source, header, Edge.Kind.LOOP_OUT)) elif kind == LooseControlFlowGraph.SpecialEdgeType.BREAK: body.add_edge(Unconditional(special.source, None, Edge.Kind.LOOP_OUT)) body.special_edges.clear() return body