Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions mypyc/irbuild/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,11 @@ def handle_ext_method(builder: IRBuilder, cdef: ClassDef, fdef: FuncDef) -> None
# children.
if class_ir.allow_interpreted_subclasses:
f = gen_glue(builder, func_ir.sig, func_ir, class_ir, class_ir, fdef, do_py_ops=True)
class_ir.glue_methods[(class_ir, name)] = f
# Use func_ir.decl.name (unique) rather than fdef.name, because for properties
# the getter and setter share the same fdef.name but have distinct decl names
# (e.g. "prop" vs "__mypyc_setter__prop"). Using fdef.name would cause the
# setter's glue to overwrite the getter's glue in the shadow vtable.
class_ir.glue_methods[(class_ir, func_ir.decl.name)] = f
builder.functions.append(f)

if fdef.name == "__getattr__":
Expand Down Expand Up @@ -653,8 +657,9 @@ def gen_glue(
"""
if fdef.is_property:
return gen_glue_property(builder, base_sig, target, cls, base, fdef.line, do_py_ops)
else:
return gen_glue_method(builder, base_sig, target, cls, base, fdef.line, do_py_ops)
if do_py_ops and target.name.startswith(PROPSET_PREFIX):
return gen_glue_property_setter(builder, base_sig, target, cls, base, fdef.line)
return gen_glue_method(builder, base_sig, target, cls, base, fdef.line, do_py_ops)


class ArgInfo(NamedTuple):
Expand Down Expand Up @@ -846,6 +851,56 @@ def gen_glue_property(
)


def gen_glue_property_setter(
builder: IRBuilder, sig: FuncSignature, target: FuncIR, cls: ClassIR, base: ClassIR, line: int
) -> FuncIR:
"""Generate a shadow glue method for a property setter.

For interpreted subclasses, property setters can't be called via the
internal __mypyc_setter__<name> method. Instead, use Python's setattr
to set the property via the standard descriptor protocol.
"""
builder.enter()
builder.ret_types[-1] = sig.ret_type

rt_args = list(sig.args)
rt_args[0] = RuntimeArg(sig.args[0].name, RInstance(cls))

arg_info = get_args(builder, rt_args, line)
args = arg_info.args

self_arg = args[0]
value_arg = args[1]

# Extract the property name from "__mypyc_setter__<name>"
assert target.name.startswith(PROPSET_PREFIX)
prop_name = target.name[len(PROPSET_PREFIX) :]

builder.primitive_op(
py_setattr_op,
[
self_arg,
builder.load_str(prop_name),
builder.coerce(value_arg, object_rprimitive, line),
],
line,
)
retval = builder.coerce(builder.none(), sig.ret_type, line)
builder.add(Return(retval))

arg_regs, _, blocks, return_type, _ = builder.leave()
return FuncIR(
FuncDecl(
target.name + "__" + base.name + "_glue",
cls.name,
builder.module_name,
FuncSignature(rt_args, return_type),
),
arg_regs,
blocks,
)


def get_func_target(builder: IRBuilder, fdef: FuncDef) -> AssignmentTarget:
"""Given a FuncDef, return the target for the instance of its callable class.

Expand Down
42 changes: 42 additions & 0 deletions mypyc/test-data/run-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -5774,3 +5774,45 @@ from native import Concrete
c = Concrete()
assert c.value() == 42
assert c.derived() == 42

[case testPropertyShadowVtableGlue]
# The setter's shadow glue overwrites the getter's (both keyed by fdef.name),
# misaligning subsequent vtable entries -> segfault on interpreted subclasses.
from typing import List
import mypy_extensions

@mypy_extensions.mypyc_attr(allow_interpreted_subclasses=True)
class Base:
_x: str
_y: List[int]
def __init__(self) -> None:
self._x = ""
self._y = []

@property
def x(self) -> str:
return self._x

@x.setter
def x(self, v: str) -> None:
self._x = v

@property
def y(self) -> List[int]:
return self._y

@y.setter
def y(self, v: List[int]) -> None:
self._y = v

def method(self) -> str:
self.x = "a"
self.y = [1]
return self.x + str(len(self.y))

[file driver.py]
from native import Base

Sub = type("Sub", (Base,), {})
s = Sub()
assert s.method() == "a1"
Loading