Example #1
0
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
Example #2
0
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('}')