From da95b3decea2a5467d048305e4f400cd215f8459 Mon Sep 17 00:00:00 2001 From: Ryan Heard Date: Thu, 8 Oct 2026 18:39:23 -0400 Subject: [PATCH] [mypyc] Fix segfault when a native __get__ returns an unboxed value --- mypyc/codegen/emitwrapper.py | 12 +++- mypyc/test-data/run-dunders.test | 95 ++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/mypyc/codegen/emitwrapper.py b/mypyc/codegen/emitwrapper.py index f048778bfb1b..7aeb664501d7 100644 --- a/mypyc/codegen/emitwrapper.py +++ b/mypyc/codegen/emitwrapper.py @@ -523,8 +523,18 @@ def generate_get_wrapper(cl: ClassIR, fn: FuncIR, emitter: Emitter) -> str: name=name ) ) + # CPython passes NULL for a missing instance (when the attribute is read from the + # class) or owner (as in d.__get__(obj, None)), but __get__ expects None. emitter.emit_line("instance = instance ? instance : Py_None;") - emitter.emit_line(f"return {emitter.native_function_call(fn.decl)}(self, instance, owner);") + emitter.emit_line("owner = owner ? owner : Py_None;") + call = f"{emitter.native_function_call(fn.decl)}(self, instance, owner)" + if fn.ret_type.is_unboxed: + emitter.emit_line(f"{emitter.ctype_spaced(fn.ret_type)}retval = {call};") + emitter.emit_error_check("retval", fn.ret_type, "return NULL;") + emitter.emit_box("retval", "retbox", fn.ret_type, declare_dest=True) + emitter.emit_line("return retbox;") + else: + emitter.emit_line(f"return {call};") emitter.emit_line("}") return name diff --git a/mypyc/test-data/run-dunders.test b/mypyc/test-data/run-dunders.test index 370de875b556..72bea099f7bf 100644 --- a/mypyc/test-data/run-dunders.test +++ b/mypyc/test-data/run-dunders.test @@ -353,6 +353,101 @@ def test_unary_dunders_native() -> None: assert +c == 15 assert abs(c) == 16 +[case testDundersGet] +from typing import Any + +from mypy_extensions import i64, mypyc_attr +from testutil import assertRaises + +class IntDescr: + def __get__(self, instance: object, owner: object) -> int: + if instance is None: + return -1 + return 42 + +class BoolDescr: + def __init__(self, value: bool) -> None: + self.value = value + + def __get__(self, instance: object, owner: object) -> bool: + return self.value + +class FloatDescr: + def __get__(self, instance: object, owner: object) -> float: + return 1.5 + +class I64Descr: + def __get__(self, instance: object, owner: object) -> i64: + # The error value of i64 + return -113 + +class TupleDescr: + def __get__(self, instance: object, owner: object) -> tuple[int, str]: + return (1, "a") + +class OwnerDescr: + def __get__(self, instance: object, owner: object) -> object: + return owner + +class Raises: + def __get__(self, instance: object, owner: object) -> int: + raise ValueError("bad get") + +@mypyc_attr(native_class=False) +class Host: + i = IntDescr() + t = BoolDescr(True) + f = BoolDescr(False) + fl = FloatDescr() + n = I64Descr() + tup = TupleDescr() + o = OwnerDescr() + r = Raises() + +def test_unboxed_return_types() -> None: + h = Host() + assert h.i == 42 + assert Host.i == -1 + assert h.t is True + assert h.f is False + assert h.fl == 1.5 + assert h.n == -113 + assert h.tup == (1, "a") + +def test_generic() -> None: + a: Any = Host() + assert a.i == 42 + assert a.f is False + d: Any = IntDescr() + assert d.__get__(a, Host) == 42 + assert d.__get__(None, Host) == -1 + +def test_owner() -> None: + assert Host().o is Host + assert Host.o is Host + d: Any = OwnerDescr() + assert d.__get__(1, None) is None + +def test_error() -> None: + with assertRaises(ValueError, "bad get"): + Host().r + +def test_interpreted_host() -> None: + from interp import InterpHost + + assert InterpHost().i == 42 + assert InterpHost.i == -1 + assert InterpHost().f is False + assert InterpHost().o is InterpHost + +[file interp.py] +from native import BoolDescr, IntDescr, OwnerDescr + +class InterpHost: + i = IntDescr() + f = BoolDescr(False) + o = OwnerDescr() + [case testDundersIndex] import operator from typing import Any