Skip to content

[mypyc] Fix seg fault due to heap type objects with static tp_doc #19636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 10, 2025
Merged
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
4 changes: 2 additions & 2 deletions mypyc/codegen/emitclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ def emit_line() -> None:
flags.append("Py_TPFLAGS_MANAGED_DICT")
fields["tp_flags"] = " | ".join(flags)

fields["tp_doc"] = native_class_doc_initializer(cl)
fields["tp_doc"] = f"PyDoc_STR({native_class_doc_initializer(cl)})"

emitter.emit_line(f"static PyTypeObject {emitter.type_struct_name(cl)}_template_ = {{")
emitter.emit_line("PyVarObject_HEAD_INIT(NULL, 0)")
Expand Down Expand Up @@ -925,7 +925,7 @@ def generate_methods_table(cl: ClassIR, name: str, emitter: Emitter) -> None:
flags.append("METH_CLASS")

doc = native_function_doc_initializer(fn)
emitter.emit_line(" {}, {}}},".format(" | ".join(flags), doc))
emitter.emit_line(" {}, PyDoc_STR({})}},".format(" | ".join(flags), doc))

# Provide a default __getstate__ and __setstate__
if not cl.has_method("__setstate__") and not cl.has_method("__getstate__"):
Expand Down
2 changes: 1 addition & 1 deletion mypyc/codegen/emitmodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -983,7 +983,7 @@ def emit_module_methods(
emitter.emit_line(
(
'{{"{name}", (PyCFunction){prefix}{cname}, {flag} | METH_KEYWORDS, '
"{doc} /* docstring */}},"
"PyDoc_STR({doc}) /* docstring */}},"
).format(
name=name, cname=fn.cname(emitter.names), prefix=PREFIX, flag=flag, doc=doc
)
Expand Down
15 changes: 15 additions & 0 deletions mypyc/lib-rt/misc_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,21 @@ PyObject *CPyType_FromTemplate(PyObject *template,

Py_XDECREF(dummy_class);

// Unlike the tp_doc slots of most other object, a heap type's tp_doc
// must be heap allocated.
if (template_->tp_doc) {
// Silently truncate the docstring if it contains a null byte
Py_ssize_t size = strlen(template_->tp_doc) + 1;
char *tp_doc = (char *)PyMem_Malloc(size);
if (tp_doc == NULL) {
PyErr_NoMemory();
goto error;
}

memcpy(tp_doc, template_->tp_doc, size);
t->ht_type.tp_doc = tp_doc;
}

#if PY_MINOR_VERSION == 11
// This is a hack. Python 3.11 doesn't include good public APIs to work with managed
// dicts, which are the default for heap types. So we try to opt-out until Python 3.12.
Expand Down
16 changes: 16 additions & 0 deletions mypyc/test-data/run-signatures.test
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,22 @@ for cls in [Empty, HasInit, InheritedInit]:
assert getattr(cls, "__doc__") == ""
assert getattr(HasInitBad, "__doc__") is None

[case testSignaturesConstructorsNonExt]
from mypy_extensions import mypyc_attr

@mypyc_attr(native_class=False)
class NonExt:
def __init__(self, x) -> None: pass

[file driver.py]
import inspect
from testutil import assertRaises
from native import *

# TODO: support constructor signatures for non-extension classes
with assertRaises(ValueError, "no signature found for builtin"):
inspect.signature(NonExt)

[case testSignaturesHistoricalPositionalOnly]
import inspect

Expand Down
Loading