Skip to content
Open
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
31 changes: 23 additions & 8 deletions core/base/src/TDirectory.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -202,20 +202,35 @@ void TDirectory::Append(TObject *obj, Bool_t replace /* = kFALSE */)
{
if (!obj || !fList) return;

bool alreadyPresent = false;
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());
ROOT::DirAutoAdd_t func = old->IsA()->GetDirectoryAutoAdd();
if (func) {
func(old,nullptr);
std::vector<TObject *> toRemoveList;
for (TObject *old : *GetList()) {
if (obj == old) {
if (alreadyPresent)
toRemoveList.push_back(obj);
else
alreadyPresent = true;
} else if (old->GetName() && strcmp(obj->GetName(), old->GetName()) == 0) {
toRemoveList.push_back(old);
Warning("Append", "Replacing existing %s: %s (Potential memory leak).", old->IsA()->GetName(),
old->GetName());
}
}

for (auto toRemove : toRemoveList) {
ROOT::DirAutoAdd_t func = toRemove->IsA()->GetDirectoryAutoAdd();
if (func && toRemove != obj) {
func(toRemove, nullptr);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last problem (which I don't think we can fix) is that if an histogram (that is not obj but has the same name as obj) is both in the current directory and is attached to a completely distinct TDirectory, this will result in that second histogram being removed from the other directory but not this one ....

On the other hand this would mean the user did:

TFile f1(...);
auto h1 = new TH1F("histo", ....);
TFile f2(...);
auto h2 = new TH1F("histo", ....);
f2.Append(h1);
f2.Append(h2, true);

If I read the code correctly, this would result in f1 being empty and f2 containing both histograms.

Copy link
Member

@pcanal pcanal Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also related, if TH1::AddDirectoryStatus() is false, the function is a no-op :(
i.e. this 'fails':

TH1::AddDirectory(false);
TFile f1(...);
auto h1 = new TH1F("histo", ....);
h1.SetDirectory(&f1);
auto h2 = new TH1F("histo", ....);
f1.Append(h2, true);

} else {
Remove(old);
Remove(toRemove);
}
}
}

if (alreadyPresent)
return;

fList->Add(obj);
// A priori, a `TDirectory` object is assumed to not have shared ownership.
// If it is, let's rely on the user to update the bit.
Expand Down
28 changes: 28 additions & 0 deletions hist/hist/test/test_TH1.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
#include "TH1F.h"
#include "THLimitsFinder.h"

#include "TDirectory.h"
#include "TList.h"

#include <cmath>
#include <cstddef>
#include <random>
Expand Down Expand Up @@ -333,3 +336,28 @@ TEST(TH1L, SetBinContent)
h.SetBinContent(1, Large);
EXPECT_EQ(h.GetBinContent(1), Large);
}

TEST(TH1, InteractionWithTDirectoryAdd)
{
auto histo = std::make_unique<TH1F>("h", "h", 2, 0, 1);
auto tNamed = std::make_unique<TNamed>("h", "name clash");
auto tNamed2 = std::make_unique<TNamed>("h", "name clash 2");

auto dir = std::make_unique<TDirectory>("dir", "dir");
histo->SetDirectory(dir.get());
dir->Add(histo.get());
dir->Add(histo.get());
dir->Add(tNamed.get());
dir->Add(tNamed.get());
dir->Add(tNamed2.get());

EXPECT_EQ(histo->GetDirectory(), dir.get());

ROOT_EXPECT_WARNING_PARTIAL(dir->Add(histo.get(), /*replace=*/true), "Append", "Replacing existing TNamed");
EXPECT_EQ(histo->GetDirectory(), dir.get());
EXPECT_EQ(dir->GetList()->size(), 1);
EXPECT_EQ(dir->GetList()->First(), histo.get());

// Leave the ownership to the unique_ptrs
histo->SetDirectory(nullptr);
}
Loading