Skip to content

Commit 4095d0c

Browse files
committed
[tree] Fix interaction between GetEntries and GetListOfFriends
Fixes #17820 Full analysis of the issue follows: 1. TChain::GetEntries calls TChain::LoadTree(TTree::kMaxEntries - 1) 2. Internally, this will set fReadEntry == -2, due to how LoadTree works and the fact it's trying to read beyond the last entry in the chain 3. This has an effect here https://github.com/root-project/root/blob/f33985dca2d6bc505e83498f7b4561ddd969be17/tree/tree/src/TChain.cxx#L1675-L1679 where `t` is the current friend being traversed in the list of friends of the *chain*. We call `t->LoadTree(TTree::kMaxEntries - 1)`. 4. Inside the call to LoadTree on the friend, we reach this line https://github.com/root-project/root/blob/f33985dca2d6bc505e83498f7b4561ddd969be17/tree/tree/src/TChain.cxx#L1484 where the fTree data member of the friend is invalidated. 5. Back to the LoadTree of the main chain, these lines https://github.com/root-project/root/blob/f33985dca2d6bc505e83498f7b4561ddd969be17/tree/tree/src/TChain.cxx#L1681-L1684 would normally connect the fTree data member of the main chain to the current friend, so the list of friends is properly populated. But due to 4., `friend->GetTree()` will return nullptr so the connection won't happen. This commit changes the implementation of TChain::GetEntries, so that after computing the total number of entries the cursor on the chain is brought back to its previous entry, or at least 0 if it was in an invalid state before.
1 parent f33985d commit 4095d0c

File tree

3 files changed

+96
-1
lines changed

3 files changed

+96
-1
lines changed

tree/tree/src/TChain.cxx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,14 @@ Long64_t TChain::GetEntries() const
985985
return fProofChain->GetEntries();
986986
}
987987
if (fEntries == TTree::kMaxEntries) {
988-
const_cast<TChain*>(this)->LoadTree(TTree::kMaxEntries-1);
988+
const auto readEntry = GetReadEntry();
989+
auto *thischain = const_cast<TChain *>(this);
990+
thischain->LoadTree(TTree::kMaxEntries - 1);
991+
if (readEntry < 0) {
992+
thischain->fReadEntry = readEntry;
993+
thischain->fTreeNumber = -1;
994+
}
995+
thischain->LoadTree(std::max(readEntry, 0ll));
989996
}
990997
return fEntries;
991998
}

tree/treeplayer/test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ endif()
2323
ROOT_ADD_GTEST(ttreeindex_clone ttreeindex_clone.cxx LIBRARIES TreePlayer)
2424

2525
ROOT_ADD_GTEST(ttreereader_friends ttreereader_friends.cxx LIBRARIES TreePlayer)
26+
27+
ROOT_ADD_GTEST(ttreeindex_getlistoffriends ttreeindex_getlistoffriends.cxx LIBRARIES TreePlayer)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#include "TChain.h"
2+
#include "TFile.h"
3+
#include "TFriendElement.h"
4+
#include "TTree.h"
5+
6+
#include "gtest/gtest.h"
7+
8+
#include <string_view>
9+
10+
void write_data(std::string_view treename, std::string_view filename)
11+
{
12+
TFile f{filename.data(), "update"};
13+
TTree t{treename.data(), treename.data()};
14+
int runNumber{};
15+
int eventNumber{};
16+
float val{};
17+
t.Branch("runNumber", &runNumber, "runNumber/I");
18+
t.Branch("eventNumber", &eventNumber, "eventNumber/I");
19+
t.Branch("val", &val, "val/F");
20+
if (treename == "main") {
21+
for (auto rn = 0; rn < 3; rn++) {
22+
runNumber = rn;
23+
for (auto en = 0; en < 5; en++) {
24+
eventNumber = en;
25+
val = en * rn;
26+
t.Fill();
27+
}
28+
}
29+
} else {
30+
for (auto rn = 0; rn < 3; rn++) {
31+
runNumber = rn;
32+
for (auto en = 4; en >= 0; en--) {
33+
eventNumber = en;
34+
val = en * rn;
35+
t.Fill();
36+
}
37+
}
38+
}
39+
40+
f.Write();
41+
}
42+
43+
struct TTreeIndexGH_17820 : public ::testing::Test {
44+
constexpr static auto fFileName{"ttreeindex_getlistoffriends_gh_17820.root"};
45+
constexpr static auto fMainName{"main"};
46+
constexpr static auto fFriendName{"friend"};
47+
48+
static void SetUpTestCase()
49+
{
50+
write_data(fMainName, fFileName);
51+
write_data(fFriendName, fFileName);
52+
}
53+
54+
static void TearDownTestCase() { std::remove(fFileName); }
55+
};
56+
57+
// Regression test for https://github.com/root-project/root/issues/17820
58+
TEST_F(TTreeIndexGH_17820, RunTest)
59+
{
60+
TChain mainChain{fMainName};
61+
mainChain.AddFile(fFileName);
62+
63+
TChain friendChain{fFriendName};
64+
friendChain.AddFile(fFileName);
65+
friendChain.BuildIndex("runNumber", "eventNumber");
66+
67+
mainChain.AddFriend(&friendChain);
68+
69+
// Calling GetEntries used to mess with the fTree data member of the main
70+
// chain, not connecting it to the friend chain and thus losing the list
71+
// of friends
72+
mainChain.GetEntries();
73+
74+
const auto *listOfFriends = mainChain.GetTree()->GetListOfFriends();
75+
76+
ASSERT_TRUE(listOfFriends);
77+
EXPECT_EQ(listOfFriends->GetEntries(), 1);
78+
79+
const auto *friendTree = dynamic_cast<TTree *>(dynamic_cast<TFriendElement *>(listOfFriends->At(0))->GetTree());
80+
ASSERT_TRUE(friendTree);
81+
82+
EXPECT_STREQ(friendTree->GetName(), fFriendName);
83+
const auto *friendFile = friendTree->GetCurrentFile();
84+
ASSERT_TRUE(friendFile);
85+
EXPECT_STREQ(friendFile->GetName(), fFileName);
86+
}

0 commit comments

Comments
 (0)