Skip to content
Draft
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: 3 additions & 1 deletion .github/workflows/root-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,10 +157,11 @@
INCREMENTAL: ${{ !contains(github.event.pull_request.labels.*.name, 'clean build') && !matrix.platform == 'mac15' && !matrix.platform == 'mac26'}}
GITHUB_PR_ORIGIN: ${{ github.event.pull_request.head.repo.clone_url }}
OVERRIDES: ${{ join( matrix.overrides, ' ') }}
ROOT_IMPLICIT_OWNERSHIP: 0
run: |

Check failure on line 161 in .github/workflows/root-ci.yml

View workflow job for this annotation

GitHub Actions / lint-action-files

"github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details
[ -d "${VIRTUAL_ENV_DIR}" ] && source ${VIRTUAL_ENV_DIR}/bin/activate
echo "Python is now $(which python3) $(python3 --version)"
src/.github/workflows/root-ci-config/build_root.py \
src/.github/workflows/root-ci-config/build_root.py \
--buildtype RelWithDebInfo \
--incremental $INCREMENTAL \
--base_ref ${{ github.base_ref }} \
Expand Down Expand Up @@ -287,7 +288,7 @@
INCREMENTAL: ${{ !contains(github.event.pull_request.labels.*.name, 'clean build') }}
GITHUB_PR_ORIGIN: ${{ github.event.pull_request.head.repo.clone_url }}
shell: cmd
run: "C:\\setenv.bat ${{ matrix.target_arch }} &&

Check failure on line 291 in .github/workflows/root-ci.yml

View workflow job for this annotation

GitHub Actions / lint-action-files

"github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details
python .github/workflows/root-ci-config/build_root.py
--buildtype ${{ matrix.config }}
--platform windows10
Expand Down Expand Up @@ -447,7 +448,7 @@
- self-hosted
- linux
- ${{ matrix.architecture == null && 'x64' || matrix.architecture }}
- ${{ matrix.extra-runs-on == null && 'cpu' || matrix.extra-runs-on }}

Check failure on line 451 in .github/workflows/root-ci.yml

View workflow job for this annotation

GitHub Actions / lint-action-files

property "extra-runs-on" is not defined in object type {architecture: string; image: string; is_special: bool; overrides: array<string>; platform_config: string; property: string}

Check failure on line 451 in .github/workflows/root-ci.yml

View workflow job for this annotation

GitHub Actions / lint-action-files

property "extra-runs-on" is not defined in object type {architecture: string; image: string; is_special: bool; overrides: array<string>; platform_config: string; property: string}

name: |
${{ matrix.image }} ${{ matrix.property }}
Expand Down Expand Up @@ -512,7 +513,8 @@
INCREMENTAL: ${{ !contains(github.event.pull_request.labels.*.name, 'clean build') }}
GITHUB_PR_ORIGIN: ${{ github.event.pull_request.head.repo.clone_url }}
OVERRIDES: ${{ join( matrix.overrides, ' ') }}
ROOT_IMPLICIT_OWNERSHIP: 0
run: ".github/workflows/root-ci-config/build_root.py

Check failure on line 517 in .github/workflows/root-ci.yml

View workflow job for this annotation

GitHub Actions / lint-action-files

"github.event.pull_request.head.ref" is potentially untrusted. avoid using it directly in inline scripts. instead, pass it through an environment variable. see https://docs.github.com/en/actions/reference/security/secure-use#good-practices-for-mitigating-script-injection-attacks for more details
--buildtype RelWithDebInfo
--platform ${{ matrix.image }}
--platform_config ${{ matrix.platform_config }}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import os
import unittest

import ROOT

from ROOT import TFile

Check failure on line 6 in bindings/pyroot/pythonizations/test/tfile_context_manager.py

View workflow job for this annotation

GitHub Actions / ruff

ruff (I001)

bindings/pyroot/pythonizations/test/tfile_context_manager.py:1:1: I001 Import block is un-sorted or un-formatted help: Organize imports


class TFileContextManager(unittest.TestCase):
Expand Down Expand Up @@ -64,6 +64,7 @@
histoname = "myhisto_3"
with TFile(filename, "recreate") as outfile:
hout = ROOT.TH1F(histoname, histoname, self.NBINS, self.XMIN, self.XMAX)
hout.SetDirectory(outfile)
outfile.Write()

self.check_file_data(outfile, filename, histoname)
Expand Down
1 change: 1 addition & 0 deletions cmake/modules/RootTestDriver.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ if(CMD)
string(STRIP "${_errvar0}" _errvar0)
string(REPLACE "\n" ";" _lines "${_errvar0}")
list(FILTER _lines EXCLUDE REGEX "^Info in <.+::ACLiC>: creating shared library.+")
list(FILTER _lines EXCLUDE REGEX "^Info in <TROOT>: Implicit object ownership.*") # ROOT 7 demo mode in ROOT 6
string(REPLACE ";" "\n" _errvar0 "${_lines}")
if(_errvar0)
message(FATAL_ERROR "Unexpected error output")
Expand Down
5 changes: 5 additions & 0 deletions core/base/inc/TROOT.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ namespace ROOT {
void DisableImplicitMT();
Bool_t IsImplicitMTEnabled();
UInt_t GetThreadPoolSize();
namespace Experimental {
void EnableImplicitObjectOwnership();
void DisableImplicitObjectOwnership();
bool IsImplicitObjectOwnershipEnabled();
} // namespace Experimental
}

class TROOT : public TDirectory {
Expand Down
6 changes: 4 additions & 2 deletions core/base/src/TDirectory.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -207,8 +207,10 @@ void TDirectory::Append(TObject *obj, Bool_t replace /* = kFALSE */)
if (replace && obj->GetName() && obj->GetName()[0]) {
TObject *old;
while (nullptr != (old = GetList()->FindObject(obj->GetName()))) {
Warning("Append","Replacing existing %s: %s (Potential memory leak).",
obj->IsA()->GetName(),obj->GetName());
if (obj != old) {
Warning("Append","Replacing existing %s: %s (Potential memory leak).",
obj->IsA()->GetName(),obj->GetName());
}
ROOT::DirAutoAdd_t func = old->IsA()->GetDirectoryAutoAdd();
if (func) {
func(old,nullptr);
Expand Down
118 changes: 117 additions & 1 deletion core/base/src/TROOT.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ of a main program creating an interactive version is shown below:
#include <ROOT/RVersion.hxx>
#include "RConfigure.h"
#include "RConfigOptions.h"
#include <atomic>
#include <filesystem>
#include <string>
#include <map>
#include <sstream>
#include <set>
#include <cstdlib>
#ifdef WIN32
Expand Down Expand Up @@ -471,6 +473,63 @@ namespace Internal {
return isImplicitMTEnabled;
}

////////////////////////////////////////////////////////////////////////////////
/// \brief Test if objects such as TH1-derived classes should be implicitly
/// registered to gDirectory.
/// A default can be set in a .rootrc using "Root.ImplicitOwnership: 1" or setting
/// the environment variable "ROOT_IMPLICIT_OWNERSHIP=0".
static std::atomic_bool &IsImplicitOwnershipEnabledImpl()
{
static std::atomic_bool initCompleted = false;
static std::atomic_bool implicitOwnership = true;

if (!initCompleted.load(std::memory_order_relaxed)) {
R__LOCKGUARD(gROOTMutex);
// test again, because another thread might have raced us here
if (!initCompleted) {
std::stringstream infoMessage;

if (gEnv) {
const auto desiredValue = gEnv->GetValue("Root.ImplicitOwnership", -1);
if (desiredValue == 0) {
implicitOwnership = false;
infoMessage << "Implicit object ownership switched off in rootrc\n";
} else if (desiredValue == 1) {
implicitOwnership = true;
infoMessage << "Implicit object ownership switched on in rootrc\n";
} else if (desiredValue != -1) {
Error("TROOT", "Root.ImplicitOwnership should be 0 or 1");
}
}

if (auto env = gSystem->Getenv("ROOT_IMPLICIT_OWNERSHIP"); env) {
int desiredValue = -1;
try {
desiredValue = std::stoi(env);
} catch (std::invalid_argument &e) {
Error("TROOT", "ROOT_IMPLICIT_OWNERSHIP should be 0 or 1");
}
if (desiredValue == 0) {
implicitOwnership = false;
infoMessage << "Implicit object ownership switched off using ROOT_IMPLICIT_OWNERSHIP\n";
} else if (desiredValue == 1) {
implicitOwnership = true;
infoMessage << "Implicit object ownership switched on using ROOT_IMPLICIT_OWNERSHIP\n";
} else {
Error("TROOT", "ROOT_IMPLICIT_OWNERSHIP should be 0 or 1");
}
}

if (!infoMessage.str().empty()) {
Info("TROOT", "%s", infoMessage.str().c_str());
}

initCompleted = true;
}
}

return implicitOwnership;
}
} // end of Internal sub namespace
// back to ROOT namespace

Expand Down Expand Up @@ -616,6 +675,63 @@ namespace Internal {
return 0;
#endif
}

namespace Experimental {
////////////////////////////////////////////////////////////////////////////////
/// \brief Switch ROOT's object ownership model to ROOT 6 mode.
///
/// In ROOT 6 mode, ROOT will implicitly assign ownership of histograms or TTrees
/// to the current \ref gDirectory, for example to the last TFile that was opened.
/// \code{.cpp}
/// TFile file(...);
/// TTree* tree = new TTree(...);
/// TH1D* histo = new TH1D(...);
/// file.Write(); // Both tree and histogram are in the file now
/// \endcode
///
/// In ROOT 7 mode, these objects won't register themselves to the current gDirectory,
/// so they are fully owned by the user. To write these to files, the user needs to do
/// one of the following:
/// - Explicitly transfer ownership:
/// \code{.cpp}
/// TFile file(...);
/// TTree* tree = new TTree(...);
/// tree->SetDirectory(&file);
/// \endcode
/// - Keep ownership of the object, but write explicitly:
/// \code{.cpp}
/// TFile file(...);
/// std::unique_ptr<TH1D> histo{new TH1D(...)};
/// file.WriteObject(histo.get(), "HistogramName");
/// file.Close();
/// // histo is still valid
/// \endcode
///
/// \note This setting has higher priority than TH1::AddDirectoryStatus() and TDirectory::AddDirectoryStatus().
/// These two will always evaluate to false if implicit ownership is off.
///
void EnableImplicitObjectOwnership()
{
ROOT::Internal::IsImplicitOwnershipEnabledImpl() = true;
}

////////////////////////////////////////////////////////////////////////////////
/// \brief Switch ROOT's object ownership model to ROOT 7 mode (no ownership).
/// \copydetails ROOT::Experimental::EnableImplicitObjectOwnership()
void DisableImplicitObjectOwnership()
{
ROOT::Internal::IsImplicitOwnershipEnabledImpl() = false;
}

////////////////////////////////////////////////////////////////////////////////
/// Test whether the current directory should take ownership of objects such as
/// TH1-derived classes, TTree, TEntryList etc.
/// \copydetails ROOT::Experimental::EnableImplicitObjectOwnership()
bool IsImplicitObjectOwnershipEnabled()
{
return ROOT::Internal::IsImplicitOwnershipEnabledImpl();
}
} // namespace Experimental
} // end of ROOT namespace

TROOT *ROOT::Internal::gROOTLocal = ROOT::GetROOT();
Expand Down Expand Up @@ -2832,7 +2948,7 @@ void TROOT::SetBatch(Bool_t batch)
/// interactive mode.
/// - "server:port": turns the web display into server mode with specified port. Web widgets will not be displayed,
/// only text message with window URL will be printed on standard output
///
///
/// \note See more details related to webdisplay on RWebWindowsManager::ShowWindow

void TROOT::SetWebDisplay(const char *webdisplay)
Expand Down
29 changes: 16 additions & 13 deletions hist/hist/src/TH1.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -742,11 +742,13 @@ TH1::TH1(const char *name,const char *title,Int_t nbins,const Double_t *xbins)
}

////////////////////////////////////////////////////////////////////////////////
/// Static function: cannot be inlined on Windows/NT.
/// Check whether TH1-derived classes should register themselves to the current gDirectory.
/// \note ROOT::Experimental::IsImplicitObjectOwnershipEnabled() might lead to this
/// setting being always off, since it has higher precedence.

Bool_t TH1::AddDirectoryStatus()
{
return fgAddDirectory;
return ROOT::Experimental::IsImplicitObjectOwnershipEnabled() && fgAddDirectory;
}

////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -1275,16 +1277,19 @@ Bool_t TH1::Add(const TH1 *h1, const TH1 *h2, Double_t c1, Double_t c2)
}

////////////////////////////////////////////////////////////////////////////////
/// Sets the flag controlling the automatic add of histograms in memory
/// Sets the flag controlling the automatic add of histograms in memory.
///
/// By default (fAddDirectory = kTRUE), histograms are automatically added
/// to the list of objects in memory.
/// to the current directory (gDirectory).
/// Note that one histogram can be removed from its support directory
/// by calling h->SetDirectory(nullptr) or h->SetDirectory(dir) to add it
/// to the list of objects in the directory dir.
///
/// NOTE that this is a static function. To call it, use;
/// TH1::AddDirectory
/// This is a static function. To call it, use `TH1::AddDirectory`
///
/// \note When ROOT::Experimental::IsImplicitOwnershipEnabled() is off, AddDirectory is
/// without effect.
///

void TH1::AddDirectory(Bool_t add)
{
Expand Down Expand Up @@ -2750,7 +2755,7 @@ void TH1::Copy(TObject &obj) const
// will be added to gDirectory independently of the fDirectory stored.
// and if the AddDirectoryStatus() is false it will not be added to
// any directory (fDirectory = nullptr)
if (fgAddDirectory && gDirectory) {
if (AddDirectoryStatus() && gDirectory) {
gDirectory->Append(&obj);
((TH1&)obj).fFunctions->UseRWLock();
((TH1&)obj).fDirectory = gDirectory;
Expand Down Expand Up @@ -2806,16 +2811,14 @@ TObject* TH1::Clone(const char* newname) const
}

////////////////////////////////////////////////////////////////////////////////
/// Perform the automatic addition of the histogram to the given directory
/// Callback to perform the automatic addition of the histogram to the given directory.
///
/// Note this function is called in place when the semantic requires
/// this object to be added to a directory (I.e. when being read from
/// a TKey or being Cloned)
/// This callback is used to register a histogram to the current directory when a TKey
/// is read or an object is being Cloned.

void TH1::DirectoryAutoAdd(TDirectory *dir)
{
Bool_t addStatus = TH1::AddDirectoryStatus();
if (addStatus) {
if (fgAddDirectory) {
SetDirectory(dir);
if (dir) {
ResetBit(kCanDelete);
Expand Down
48 changes: 48 additions & 0 deletions hist/hist/test/test_TH1.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,54 @@ TEST(TH1, Normalize)
EXPECT_FLOAT_EQ(v2.GetMaximum(), 7.9999990);
}

TEST(TH1, RegistrationToTDirectory_ImplicitOwnershipOff)
{
const bool ownershipEnabledBefore = ROOT::Experimental::IsImplicitObjectOwnershipEnabled();
ROOT::Experimental::DisableImplicitObjectOwnership();

TH1D histo1("histo1", "Test Histogram", 10, 0, 10);
auto histo2 = std::make_unique<TH1D>("histo2", "Test Histogram", 10, 0, 10);
TH1D *histo3 = new TH1D("histo3", "Test Histogram", 10, 0, 10);

{
TDirectory dir("dir", "Test Directory");
histo3->SetDirectory(&dir);

dir.cd();

TH1D histo4("histo4", "Test Histogram", 10, 0, 10);
auto histo5 = std::make_unique<TH1D>("histo5", "Test Histogram", 10, 0, 10);

EXPECT_EQ(dir.GetList()->GetSize(), 1);
EXPECT_EQ(dir.Get<TH1D>("histo3"), histo3);

EXPECT_EQ(histo1.GetDirectory(), nullptr);
EXPECT_EQ(histo2->GetDirectory(), nullptr);
EXPECT_EQ(histo3->GetDirectory(), &dir);
EXPECT_EQ(histo4.GetDirectory(), nullptr);
EXPECT_EQ(histo5->GetDirectory(), nullptr);

histo5.reset();

EXPECT_EQ(dir.GetList()->GetSize(), 1);
EXPECT_EQ(dir.Get<TH1D>("histo3"), histo3);
}

EXPECT_STREQ(histo1.GetName(), "histo1");
EXPECT_STREQ(histo2->GetName(), "histo2");

EXPECT_EQ(histo1.GetDirectory(), nullptr);
EXPECT_EQ(histo2->GetDirectory(), nullptr);

if (ownershipEnabledBefore)
ROOT::Experimental::EnableImplicitObjectOwnership();
}

TEST(TH1, RegistrationToTDirectory_ImplicitOwnershipOn)
{
const bool ownershipDisabledBefore = ! ROOT::Experimental::IsImplicitObjectOwnershipEnabled();
ROOT::Experimental::EnableImplicitObjectOwnership();

TH1D histo1("histo1", "Test Histogram", 10, 0, 10);
auto histo2 = std::make_unique<TH1D>("histo2", "Test Histogram", 10, 0, 10);
TH1D *histo3 = new TH1D("histo3", "Test Histogram", 10, 0, 10);
Expand Down Expand Up @@ -213,6 +259,8 @@ TEST(TH1, RegistrationToTDirectory_ImplicitOwnershipOn)

EXPECT_EQ(histo1.GetDirectory(), gROOT);
EXPECT_EQ(histo2->GetDirectory(), gROOT);

if (ownershipDisabledBefore) ROOT::Experimental::DisableImplicitObjectOwnership();
}

TEST(TAxis, BinComputation_FPAccuracy)
Expand Down
2 changes: 2 additions & 0 deletions io/io/test/TFileMergerTests.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ TEST(TFileMerger, MergeSingleOnlyListed)
auto hist2 = new TH1F("hist2", "hist2", 1 , 0 , 2);
auto hist3 = new TH1F("hist3", "hist3", 1 , 0 , 2);
auto hist4 = new TH1F("hist4", "hist4", 1 , 0 , 2);
for (auto hist : {hist1, hist2, hist3, hist4})
hist->SetDirectory(&a);
hist1->Fill(1);
hist2->Fill(1); hist2->Fill(2);
hist3->Fill(1); hist3->Fill(1); hist3->Fill(1);
Expand Down
5 changes: 3 additions & 2 deletions io/io/test/rfile.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,10 @@ TEST(RFile, CheckNoAutoRegistrationWrite)
EXPECT_EQ(gDirectory, gROOT);
auto hist = std::make_unique<TH1D>("hist", "", 100, -10, 10);
file->Put("hist", *hist);
EXPECT_EQ(hist->GetDirectory(), gROOT);
TDirectory const *expectedDir = ROOT::Experimental::IsImplicitObjectOwnershipEnabled() ? gROOT : nullptr;
EXPECT_EQ(hist->GetDirectory(), expectedDir);
file->Close();
EXPECT_EQ(hist->GetDirectory(), gROOT);
EXPECT_EQ(hist->GetDirectory(), expectedDir);
hist.reset();
// no double free should happen when ROOT exits
}
Expand Down
6 changes: 5 additions & 1 deletion roofit/roofitcore/src/RooPlot.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ object onto a one-dimensional plot.
#include "TH1D.h"
#include "TBrowser.h"
#include "TVirtualPad.h"
#include "TROOT.h"

#include "TAttLine.h"
#include "TAttFill.h"
Expand All @@ -73,7 +74,10 @@ object onto a one-dimensional plot.

bool RooPlot::_addDirStatus = true ;

bool RooPlot::addDirectoryStatus() { return _addDirStatus; }
bool RooPlot::addDirectoryStatus()
{
return ROOT::Experimental::IsImplicitObjectOwnershipEnabled() && _addDirStatus;
}
bool RooPlot::setAddDirectoryStatus(bool flag) { bool ret = flag ; _addDirStatus = flag ; return ret ; }


Expand Down
1 change: 1 addition & 0 deletions roottest/scripts/custom_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def __init__(self):
r"^Info in <\w+::ACLiC>: creating shared library", # Compiled macros
r"^In file included from input_line", # Wrapper input line
r"^[:space:]*$", # Lines which are empty apart from spaces
r'^Info in <TROOT>: Implicit object ownership', # ROOT 7 mode
]
]

Expand Down
Loading
Loading