Skip to content
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
12 changes: 9 additions & 3 deletions bindings/pyroot/cppyy/CPyCppyy/src/API.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,17 @@ static bool Initialize()
#if PY_VERSION_HEX < 0x03080000
PySys_SetArgv(sizeof(argv)/sizeof(argv[0]), argv);
#endif

// force loading of the cppyy module
PyRun_SimpleString(const_cast<char*>("import cppyy"));
}

// Make sure the cppyy extension module is imported, as this is what runs the
// CPyCppyy module initialization that sets up internals such as gThisModule.
if (!CPyCppyy::gThisModule) {
PyObject* cppyymod = PyImport_ImportModule("cppyy");
Comment thread
vepadulano marked this conversation as resolved.
if (!cppyymod)
return false;
Py_DECREF(cppyymod);
}

if (!gMainDict) {
// retrieve the main dictionary
gMainDict = PyModule_GetDict(
Expand Down
2 changes: 2 additions & 0 deletions bindings/pyroot/pythonizations/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ endif()

ROOT_ADD_PYUNITTEST(regression_18441 regression_18441.py)

ROOT_ADD_PYUNITTEST(regression_pickle_no_cppyy regression_pickle_no_cppyy.py)

ROOT_ADD_PYUNITTEST(package_metadata package_metadata.py)

if(NOT MSVC OR CMAKE_SIZEOF_VOID_P EQUAL 8 OR win_broken_tests)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os
import pickle
import subprocess
import sys
import tempfile
import textwrap
import unittest


class RegressionPickleNoCppyy(unittest.TestCase):
"""
Reading back a pickled ROOT object in a fresh interpreter, where the cppyy
backend has not been imported yet, must not crash.

Unpickling a ROOT object only imports ``ROOT.libROOTPythonizations`` (to
resolve ``_CPPInstance__expand__``), which does not initialize the cppyy
backend. Entering CPyCppyy through the public API in that state used to
leave ``gThisModule`` null, so that ``CreateScopeProxy`` crashed with a
segmentation violation when using it as a fake scope.

See the ROOT forum report:
https://root-forum.cern.ch/t/issue-with-new-root-version-on-lxplus
"""

def test_load_without_prior_cppyy_import(self):
import ROOT

# Create a pickle of a ROOT object. Touching a class here imports the
# cppyy backend, but only in this (throw-away) process.
h = ROOT.TH1F("h", "h", 10, 0, 1)
h.Fill(0.5)

fname = tempfile.NamedTemporaryFile(suffix=".pkl", delete=False).name
try:
with open(fname, "wb") as f:
pickle.dump(h, f)

# Read it back in a *fresh* interpreter that only imports pickle, so
# that the cppyy backend is not initialized before the object is
# expanded. This reproduces the exact scenario from the bug report.
code = textwrap.dedent(
"""
import pickle
with open({fname!r}, "rb") as f:
h = pickle.load(f)
assert h.GetName() == "h", h.GetName()
assert h.GetEntries() == 1.0, h.GetEntries()
"""
).format(fname=fname)

# Do not use check=True so we can give a helpful message on crash
# (a segfault shows up as a negative return code on POSIX).
proc = subprocess.run([sys.executable, "-c", code])
self.assertEqual(
proc.returncode,
0,
"Unpickling a ROOT object in a fresh interpreter crashed (return code {}).".format(proc.returncode),
)
finally:
os.remove(fname)


if __name__ == "__main__":
unittest.main()
Loading