Skip to content

Commit 94eb6b7

Browse files
[mypyc] Fix seg fault due to heap type objects with static tp_doc (#19636)
See #19634 (comment) Also took the opportunity to add `PyDoc_STR` to the static docstrings. AFAIK this isn't strictly necessary, but it's better style and in theory makes it possible to compile without docstrings if someone wanted to do that.
1 parent 660d911 commit 94eb6b7

File tree

4 files changed

+34
-3
lines changed

4 files changed

+34
-3
lines changed

mypyc/codegen/emitclass.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ def emit_line() -> None:
375375
flags.append("Py_TPFLAGS_MANAGED_DICT")
376376
fields["tp_flags"] = " | ".join(flags)
377377

378-
fields["tp_doc"] = native_class_doc_initializer(cl)
378+
fields["tp_doc"] = f"PyDoc_STR({native_class_doc_initializer(cl)})"
379379

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

927927
doc = native_function_doc_initializer(fn)
928-
emitter.emit_line(" {}, {}}},".format(" | ".join(flags), doc))
928+
emitter.emit_line(" {}, PyDoc_STR({})}},".format(" | ".join(flags), doc))
929929

930930
# Provide a default __getstate__ and __setstate__
931931
if not cl.has_method("__setstate__") and not cl.has_method("__getstate__"):

mypyc/codegen/emitmodule.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -983,7 +983,7 @@ def emit_module_methods(
983983
emitter.emit_line(
984984
(
985985
'{{"{name}", (PyCFunction){prefix}{cname}, {flag} | METH_KEYWORDS, '
986-
"{doc} /* docstring */}},"
986+
"PyDoc_STR({doc}) /* docstring */}},"
987987
).format(
988988
name=name, cname=fn.cname(emitter.names), prefix=PREFIX, flag=flag, doc=doc
989989
)

mypyc/lib-rt/misc_ops.c

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,21 @@ PyObject *CPyType_FromTemplate(PyObject *template,
300300

301301
Py_XDECREF(dummy_class);
302302

303+
// Unlike the tp_doc slots of most other object, a heap type's tp_doc
304+
// must be heap allocated.
305+
if (template_->tp_doc) {
306+
// Silently truncate the docstring if it contains a null byte
307+
Py_ssize_t size = strlen(template_->tp_doc) + 1;
308+
char *tp_doc = (char *)PyMem_Malloc(size);
309+
if (tp_doc == NULL) {
310+
PyErr_NoMemory();
311+
goto error;
312+
}
313+
314+
memcpy(tp_doc, template_->tp_doc, size);
315+
t->ht_type.tp_doc = tp_doc;
316+
}
317+
303318
#if PY_MINOR_VERSION == 11
304319
// This is a hack. Python 3.11 doesn't include good public APIs to work with managed
305320
// dicts, which are the default for heap types. So we try to opt-out until Python 3.12.

mypyc/test-data/run-signatures.test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,22 @@ for cls in [Empty, HasInit, InheritedInit]:
184184
assert getattr(cls, "__doc__") == ""
185185
assert getattr(HasInitBad, "__doc__") is None
186186

187+
[case testSignaturesConstructorsNonExt]
188+
from mypy_extensions import mypyc_attr
189+
190+
@mypyc_attr(native_class=False)
191+
class NonExt:
192+
def __init__(self, x) -> None: pass
193+
194+
[file driver.py]
195+
import inspect
196+
from testutil import assertRaises
197+
from native import *
198+
199+
# TODO: support constructor signatures for non-extension classes
200+
with assertRaises(ValueError, "no signature found for builtin"):
201+
inspect.signature(NonExt)
202+
187203
[case testSignaturesHistoricalPositionalOnly]
188204
import inspect
189205

0 commit comments

Comments
 (0)