def analyze_always_defined_attrs_in_class(cl: ClassIR, seen: Set[ClassIR]) -> None: if cl in seen: return seen.add(cl) if (cl.is_trait or cl.inherits_python or cl.allow_interpreted_subclasses or cl.builtin_base is not None or cl.children is None or cl.is_serializable()): # Give up -- we can't enforce that attributes are always defined. return # First analyze all base classes. Track seen classes to avoid duplicate work. for base in cl.mro[1:]: analyze_always_defined_attrs_in_class(base, seen) m = cl.get_method('__init__') if m is None: cl._always_initialized_attrs = cl.attrs_with_defaults.copy() cl._sometimes_initialized_attrs = cl.attrs_with_defaults.copy() return self_reg = m.arg_regs[0] cfg = get_cfg(m.blocks) dirty = analyze_self_leaks(m.blocks, self_reg, cfg) maybe_defined = analyze_maybe_defined_attrs_in_init( m.blocks, self_reg, cl.attrs_with_defaults, cfg) all_attrs: Set[str] = set() for base in cl.mro: all_attrs.update(base.attributes) maybe_undefined = analyze_maybe_undefined_attrs_in_init( m.blocks, self_reg, initial_undefined=all_attrs - cl.attrs_with_defaults, cfg=cfg) always_defined = find_always_defined_attributes(m.blocks, self_reg, all_attrs, maybe_defined, maybe_undefined, dirty) always_defined = {a for a in always_defined if not cl.is_deletable(a)} cl._always_initialized_attrs = always_defined if dump_always_defined: print(cl.name, sorted(always_defined)) cl._sometimes_initialized_attrs = find_sometimes_defined_attributes( m.blocks, self_reg, maybe_defined, dirty) mark_attr_initialiation_ops(m.blocks, self_reg, maybe_defined, dirty) # Check if __init__ can run unpredictable code (leak 'self'). any_dirty = False for b in m.blocks: for i, op in enumerate(b.ops): if dirty.after[b, i] and not isinstance(op, Return): any_dirty = True break cl.init_self_leak = any_dirty
def generate_new_for_class(cl: ClassIR, func_name: str, vtable_name: str, setup_name: str, init_fn: Optional[FuncIR], emitter: Emitter) -> None: emitter.emit_line('static PyObject *') emitter.emit_line( f'{func_name}(PyTypeObject *type, PyObject *args, PyObject *kwds)') emitter.emit_line('{') # TODO: Check and unbox arguments if not cl.allow_interpreted_subclasses: emitter.emit_line(f'if (type != {emitter.type_struct_name(cl)}) {{') emitter.emit_line( 'PyErr_SetString(PyExc_TypeError, "interpreted classes cannot inherit from compiled");' ) emitter.emit_line('return NULL;') emitter.emit_line('}') if (not init_fn or cl.allow_interpreted_subclasses or cl.builtin_base or cl.is_serializable()): # Match Python semantics -- __new__ doesn't call __init__. emitter.emit_line(f'return {setup_name}(type);') else: # __new__ of a native class implicitly calls __init__ so that we # can enforce that instances are always properly initialized. This # is needed to support always defined attributes. emitter.emit_line(f'PyObject *self = {setup_name}(type);') emitter.emit_lines('if (self == NULL)', ' return NULL;') emitter.emit_line( f'PyObject *ret = {PREFIX}{init_fn.cname(emitter.names)}(self, args, kwds);') emitter.emit_lines('if (ret == NULL)', ' return NULL;') emitter.emit_line('return self;') emitter.emit_line('}')