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
1 change: 1 addition & 0 deletions bindings/pyroot/pythonizations/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ ROOT_ADD_PYUNITTEST(pyroot_pyz_ttree_branch ttree_branch.py PYTHON_DEPS numpy)

# TColor-related pythonizations
ROOT_ADD_PYUNITTEST(pyroot_pyz_tcolor tcolor.py)
ROOT_ADD_PYUNITTEST(regression_20018 regression_20018.py)

# TH1 and subclasses pythonizations
ROOT_ADD_PYUNITTEST(pyroot_pyz_th1 th1.py)
Expand Down
45 changes: 45 additions & 0 deletions bindings/pyroot/pythonizations/test/regression_20018.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import unittest

import ROOT


class Regression20018(unittest.TestCase):
"""
Regression test for https://github.com/root-project/root/issues/20018

Calling ``TColor.DefinedColors(1)`` puts the global color bookkeeping into
the "always store colors" mode (``gLastDefinedColors = -1``). When a canvas
is afterwards serialized to JSON for the web display - which is exactly what
JupyROOT does to show a canvas inline in a notebook - ``TCanvas::Streamer``
used to unconditionally call ``fPrimitives->Add(...)`` to attach the list of
colors. During the web-canvas snapshot the pad primitives are temporarily
detached (``fPrimitives`` is set to ``nullptr``, see
``TWebCanvas::CreatePadSnapshot``), so this dereferenced a null pointer and
crashed with a segmentation violation.
"""

def test_definedcolors_web_canvas_json(self):
# TWebCanvas (and thus CreateCanvasJSON) is only available when ROOT is
# built with the web display (root7/webgui). Without it, JupyROOT uses a
# different code path that is not affected by this issue.
if not hasattr(ROOT, "TWebCanvas") or not hasattr(ROOT.TWebCanvas, "CreateCanvasJSON"):
self.skipTest("ROOT was built without the web display (webgui)")

ROOT.gROOT.SetBatch(True)

# This is what triggers the bug: force the "always store colors" mode.
ROOT.TColor.DefinedColors(1)

c = ROOT.TCanvas("c_regression_20018", "Basic ROOT Plot", 800, 600)
h = ROOT.TH1F("h_regression_20018", "Example Histogram;X axis;Entries", 100, 0, 10)
h.FillRandom("gaus", 1000)
h.Draw()
c.Update()

# Used to segfault in TCanvas::Streamer due to a null fPrimitives.
json = ROOT.TWebCanvas.CreateCanvasJSON(c, 23, True)
self.assertGreater(len(json.Data()), 0)


if __name__ == "__main__":
unittest.main()
8 changes: 7 additions & 1 deletion graf2d/gpad/src/TCanvas.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -2343,7 +2343,13 @@ void TCanvas::Streamer(TBuffer &b)
//in the buffer, do not add the list of colors to the list of primitives.
TObjArray *colors = nullptr;
TObjArray *CurrentColorPalette = nullptr;
if (TColor::DefinedColors()) {
// fPrimitives can be temporarily null while streaming a web-canvas
// snapshot (see TWebCanvas::CreatePadSnapshot). In that case colors and
// palette are delivered separately, so the list of colors must not be
// added here. The guard also avoids a null dereference that crashes when
// colors storage has been forced on via TColor::DefinedColors(1)
// (see https://github.com/root-project/root/issues/20018).
if (fPrimitives && TColor::DefinedColors()) {
if (!b.CheckObject(gROOT->GetListOfColors(),TObjArray::Class())) {
colors = (TObjArray*)gROOT->GetListOfColors();
fPrimitives->Add(colors);
Expand Down
12 changes: 12 additions & 0 deletions roottest/python/JupyROOT/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ if(imt)
PYTHON_DEPS jupyter)
endif()

# Regression test for https://github.com/root-project/root/issues/20018 :
# TColor.DefinedColors(1) followed by drawing a canvas inline used to segfault.
# This notebook draws a canvas, so its output (unique div ids, random data) is
# not reproducible - just check that it runs without error ("OFF", no compare).
set(TCOLOR_NB tcolor_definedcolors.ipynb)
get_filename_component(NOTEBOOKBASE ${TCOLOR_NB} NAME_WE)
ROOTTEST_ADD_TEST(${NOTEBOOKBASE}_notebook
COPY_TO_BUILDDIR ${TCOLOR_NB}
COMMAND ${Python3_EXECUTABLE} ${NBDIFFUTIL} ${TCOLOR_NB} "OFF"
RUN_SERIAL
PYTHON_DEPS jupyter)

endif()

endif()
95 changes: 95 additions & 0 deletions roottest/python/JupyROOT/tcolor_definedcolors.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "b8ef0e09",
"metadata": {},
"outputs": [],
"source": [
"import ROOT"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ac871508",
"metadata": {},
"outputs": [],
"source": [
"ROOT.TColor.DefinedColors(1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4800d2c7",
"metadata": {},
"outputs": [],
"source": [
"c = ROOT.TCanvas(\"c\", \"Basic ROOT Plot\", 800, 600)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f3900d8a",
"metadata": {},
"outputs": [],
"source": [
"h = ROOT.TH1F(\"h\", \"Example Histogram;X axis;Entries\", 100, 0, 10)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d83cc9da",
"metadata": {},
"outputs": [],
"source": [
"for _ in range(10000):\n",
" h.Fill(ROOT.gRandom.Gaus(5, 1))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1df7d032",
"metadata": {},
"outputs": [],
"source": [
"h.Draw()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3743fddc",
"metadata": {},
"outputs": [],
"source": [
"c.Draw()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading