from __future__ import annotations from typing import Set, Tuple from mypyc.analysis.dataflow import CFG, MAYBE_ANALYSIS, AnalysisResult, run_analysis from mypyc.ir.ops import ( Assign, AssignMulti, BasicBlock, Box, Branch, Call, CallC, Cast, ComparisonOp, Extend, FloatComparisonOp, FloatNeg, FloatOp, GetAttr, GetElementPtr, Goto, InitStatic, IntOp, KeepAlive, LoadAddress, LoadErrorValue, LoadGlobal, LoadLiteral, LoadMem, LoadStatic, MethodCall, OpVisitor, RaiseStandardError, Register, RegisterOp, Return, SetAttr, SetMem, Truncate, TupleGet, TupleSet, Unbox, Unreachable, ) from mypyc.ir.rtypes import RInstance GenAndKill = Tuple[Set[None], Set[None]] CLEAN: GenAndKill = (set(), set()) DIRTY: GenAndKill = ({None}, {None}) class SelfLeakedVisitor(OpVisitor[GenAndKill]): """Analyze whether 'self' may be seen by arbitrary code in '__init__'. More formally, the set is not empty if along some path from IR entry point arbitrary code could have been executed that has access to 'self'. (We don't consider access via 'gc.get_objects()'.) """ def __init__(self, self_reg: Register) -> None: self.self_reg = self_reg def visit_goto(self, op: Goto) -> GenAndKill: return CLEAN def visit_branch(self, op: Branch) -> GenAndKill: return CLEAN def visit_return(self, op: Return) -> GenAndKill: # Consider all exits from the function 'dirty' since they implicitly # cause 'self' to be returned. return DIRTY def visit_unreachable(self, op: Unreachable) -> GenAndKill: return CLEAN def visit_assign(self, op: Assign) -> GenAndKill: if op.src is self.self_reg or op.dest is self.self_reg: return DIRTY return CLEAN def visit_assign_multi(self, op: AssignMulti) -> GenAndKill: return CLEAN def visit_set_mem(self, op: SetMem) -> GenAndKill: return CLEAN def visit_call(self, op: Call) -> GenAndKill: fn = op.fn if fn.class_name and fn.name == "__init__": self_type = op.fn.sig.args[0].type assert isinstance(self_type, RInstance) cl = self_type.class_ir if not cl.init_self_leak: return CLEAN return self.check_register_op(op) def visit_method_call(self, op: MethodCall) -> GenAndKill: return self.check_register_op(op) def visit_load_error_value(self, op: LoadErrorValue) -> GenAndKill: return CLEAN def visit_load_literal(self, op: LoadLiteral) -> GenAndKill: return CLEAN def visit_get_attr(self, op: GetAttr) -> GenAndKill: cl = op.class_type.class_ir if cl.get_method(op.attr): # Property -- calls a function return self.check_register_op(op) return CLEAN def visit_set_attr(self, op: SetAttr) -> GenAndKill: cl = op.class_type.class_ir if cl.get_method(op.attr): # Property - calls a function return self.check_register_op(op) return CLEAN def visit_load_static(self, op: LoadStatic) -> GenAndKill: return CLEAN def visit_init_static(self, op: InitStatic) -> GenAndKill: return self.check_register_op(op) def visit_tuple_get(self, op: TupleGet) -> GenAndKill: return CLEAN def visit_tuple_set(self, op: TupleSet) -> GenAndKill: return self.check_register_op(op) def visit_box(self, op: Box) -> GenAndKill: return self.check_register_op(op) def visit_unbox(self, op: Unbox) -> GenAndKill: return self.check_register_op(op) def visit_cast(self, op: Cast) -> GenAndKill: return self.check_register_op(op) def visit_raise_standard_error(self, op: RaiseStandardError) -> GenAndKill: return CLEAN def visit_call_c(self, op: CallC) -> GenAndKill: return self.check_register_op(op) def visit_truncate(self, op: Truncate) -> GenAndKill: return CLEAN def visit_extend(self, op: Extend) -> GenAndKill: return CLEAN def visit_load_global(self, op: LoadGlobal) -> GenAndKill: return CLEAN def visit_int_op(self, op: IntOp) -> GenAndKill: return CLEAN def visit_comparison_op(self, op: ComparisonOp) -> GenAndKill: return CLEAN def visit_float_op(self, op: FloatOp) -> GenAndKill: return CLEAN def visit_float_neg(self, op: FloatNeg) -> GenAndKill: return CLEAN def visit_float_comparison_op(self, op: FloatComparisonOp) -> GenAndKill: return CLEAN def visit_load_mem(self, op: LoadMem) -> GenAndKill: return CLEAN def visit_get_element_ptr(self, op: GetElementPtr) -> GenAndKill: return CLEAN def visit_load_address(self, op: LoadAddress) -> GenAndKill: return CLEAN def visit_keep_alive(self, op: KeepAlive) -> GenAndKill: return CLEAN def check_register_op(self, op: RegisterOp) -> GenAndKill: if any(src is self.self_reg for src in op.sources()): return DIRTY return CLEAN def analyze_self_leaks( blocks: list[BasicBlock], self_reg: Register, cfg: CFG ) -> AnalysisResult[None]: return run_analysis( blocks=blocks, cfg=cfg, gen_and_kill=SelfLeakedVisitor(self_reg), initial=set(), backward=False, kind=MAYBE_ANALYSIS, )