Skip to content

Commit 06c8899

Browse files
author
Emery Conrad
committed
cpyrt: null-guard the cling.printValue lookup in op_str
Commit 5a33027 dropped the faked cling::runtime::gCling and with it the last cling namespace in the interpreter. The pretty-print fallback in op_str then dereferenced the failed cppjit.gbl.cling lookup. str() of any instance without an ostream inserter crashed with SIGSEGV. Guard both lookups, clear the AttributeError, and fall back to the generic repr. The regression test runs the repro in a subprocess, so a return of the crash cannot kill the test runner. A clean exit passes, whichever repr form the backend prints. Output that names the upstream toString stub xfails. Anything else fails and reports the child's output. Co-developed-with-the-help-of: Claude Code (Fable 5, human in the loop)
1 parent 6d89803 commit 06c8899

2 files changed

Lines changed: 57 additions & 5 deletions

File tree

src/cpyrt/CPPInstance.cxx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -875,17 +875,22 @@ static PyObject* op_str(CPPInstance* self) {
875875
if (!printValue) {
876876
PyObject* gbl =
877877
PyDict_GetItemString(PySys_GetObject((char*)"modules"), "cppjit.gbl");
878-
PyObject* cl = PyObject_GetAttrString(gbl, (char*)"cling");
879-
printValue = PyObject_GetAttrString(cl, (char*)"printValue");
880-
Py_DECREF(cl);
878+
// no cling namespace exists unless user code declares one
879+
PyObject* cl =
880+
gbl ? PyObject_GetAttrString(gbl, (char*)"cling") : nullptr;
881+
printValue =
882+
cl ? PyObject_GetAttrString(cl, (char*)"printValue") : nullptr;
883+
Py_XDECREF(cl);
881884
// gbl is borrowed
882885
if (printValue) {
883886
Py_DECREF(printValue); // make borrowed
884887
if (!PyCallable_Check(printValue))
885888
printValue = nullptr; // unusable ...
886889
}
887-
if (!printValue) // unlikely
890+
if (!printValue) {
891+
PyErr_Clear();
888892
ScopeFlagSet(self, CPPScope::kNoPrettyPrint);
893+
}
889894
}
890895

891896
if (printValue) {

test/test_regression.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import os
22
import sys
33

4-
from pytest import mark, raises, skip
4+
from pytest import mark, raises, skip, xfail
55
from support import (
66
IS_CLANG_REPL,
77
IS_CLING,
@@ -1637,3 +1637,50 @@ def test51_nontype_enum_template_arg(self):
16371637

16381638
# ...nor leave the interpreter unable to compile a later call wrapper
16391639
assert ns.probe(41) == 42
1640+
1641+
def test52_str_fallback_without_ostream_insertion(self):
1642+
"""str() of an instance with no operator<< used to crash.
1643+
1644+
With no ``cling`` namespace in the interpreter, the pretty-print
1645+
fallback dereferenced the failed ``cppjit.gbl.cling`` lookup and the
1646+
process died. A regression is therefore fatal, not an assertion
1647+
failure, so run the repro in a subprocess: the runner survives and the
1648+
output identifies which failure happened.
1649+
"""
1650+
1651+
import subprocess
1652+
import sys
1653+
1654+
repro = """\
1655+
import cppjit
1656+
1657+
cppjit.cppdef("namespace StrFallback { struct Bare { int x; }; }")
1658+
print(repr(str(cppjit.gbl.StrFallback.Bare())))
1659+
"""
1660+
1661+
popen = subprocess.Popen(
1662+
[sys.executable, "-c", repro],
1663+
stdout=subprocess.PIPE,
1664+
stderr=subprocess.STDOUT,
1665+
)
1666+
stdout, _ = popen.communicate()
1667+
output = stdout.decode("utf-8", "replace")
1668+
1669+
# the guard holds: cling prints the @0xADDR form through printValue and
1670+
# ClangRepl falls back to the generic repr, and neither crashes
1671+
if popen.returncode == 0:
1672+
return
1673+
1674+
# Interpreter::toString is an assert(0) stub upstream. str() tries the
1675+
# ostream path first, which reaches it whenever assertions are on.
1676+
if "toString is not implemented" in output:
1677+
xfail(
1678+
"toString stub aborts, see compiler-research/CppInterOp#1100: "
1679+
"%s" % (output[:300],)
1680+
)
1681+
1682+
# a crash banner and its top frames come first, so keep the head
1683+
raise AssertionError(
1684+
"str() without an ostream inserter did not fall back cleanly: "
1685+
"returncode=%s output=%r" % (popen.returncode, output[:2000])
1686+
)

0 commit comments

Comments
 (0)