Check duplicate issues.
Description
Potentially falls under the umbrella of the title of #19330 but I'd say this is not a duplicate issue
When merging with TFileMerger::PartialMerge (or Merge) using the
kOnlyListed flag together with AddObjectNames(...), the merger keeps not
only the listed objects but also any unlisted top-level key whose name is a
suffix of one of the listed names.
The name test is a plain substring search that requires a trailing delimiter
but no leading delimiter, so "D0ToKmPip" is treated as listed whenever
"DstpToD0Pip_D0ToKmPip" is listed.
example
File keys: k1, k2, ak2, k3, bk3. We list only ak2 and bk3
(fObjectNames == "ak2 bk3 "). The merger tests each file key:
| file key |
test |
result |
outcome |
k1 |
"ak2 bk3 ".Contains("k1 ") |
false |
dropped (correct) |
k2 |
"ak2 bk3 ".Contains("k2 ") |
true |
kept (wrong) — suffix of ak2 |
ak2 |
"ak2 bk3 ".Contains("ak2 ") |
true |
kept (correct) |
k3 |
"ak2 bk3 ".Contains("k3 ") |
true |
kept (wrong) — suffix of bk3 |
bk3 |
"ak2 bk3 ".Contains("bk3 ") |
true |
kept (correct) |
Output: k2, ak2, k3, bk3 instead of ak2, bk3.
Reproducer
#!/usr/bin/env python
"""Minimal reproducer for the TFileMerger kOnlyListed suffix-match bug.
Run with a ROOT-enabled Python, e.g.:
python tfilemerger-onlylisted-repro.py
The script builds a source file with three top-level TDirectories, asks
TFileMerger to keep only ONE of them via kOnlyListed + AddObjectNames, and
then checks what actually ended up in the output.
A non-listed directory whose name is a *suffix* of the listed one leaks into
the output, because the name matching is a substring test with only a
trailing delimiter and no leading delimiter.
"""
import array
import sys
import ROOT
SRC = "repro_src.root"
OUT = "repro_out.root"
KEEP = "DstpToD0Pip_D0ToKmPip" # the only directory we ask to keep
LEAK = "D0ToKmPip" # NOT listed, but a suffix of KEEP -> leaks
CONTROL = "Unrelated" # NOT listed, not a suffix -> correctly dropped
def make_dir_with_tree(rf, dname):
"""Create a non-empty top-level TDirectory holding a tiny TTree."""
d = rf.mkdir(dname)
d.cd()
t = ROOT.TTree("DecayTree", "DecayTree")
val = array.array("i", [0])
t.Branch("val", val, "val/I")
for i in range(3):
val[0] = i
t.Fill()
t.Write()
rf.cd()
def main():
rf = ROOT.TFile.Open(SRC, "RECREATE")
make_dir_with_tree(rf, KEEP)
make_dir_with_tree(rf, LEAK)
make_dir_with_tree(rf, CONTROL)
rf.Close()
merger = ROOT.TFileMerger(False, False)
merger.SetPrintLevel(0)
merger.AddFile(SRC)
merger.AddObjectNames(KEEP) # keep only this one
merge_opts = (
ROOT.TFileMerger.kAll | ROOT.TFileMerger.kRegular | ROOT.TFileMerger.kOnlyListed
)
merger.OutputFile(OUT, "RECREATE")
ok = merger.PartialMerge(merge_opts)
merger.CloseOutputFile()
del merger
of = ROOT.TFile.Open(OUT)
keys = sorted(k.GetName() for k in of.GetListOfKeys())
of.Close()
print(f"ROOT version : {ROOT.gROOT.GetVersion()}")
print(f"PartialMerge ok : {bool(ok)}")
print(f"Listed to keep : [{KEEP!r}]")
print(f"Output top-level : {keys}")
print(f"Expected : [{KEEP!r}]")
leaked = LEAK in keys
control_dropped = CONTROL not in keys
print()
print(f"BUG: suffix dir {LEAK!r} leaked : {leaked}")
print(f"control {CONTROL!r} dropped : {control_dropped}")
return 1 if leaked else 0
if __name__ == "__main__":
sys.exit(main())
Expected output:
[roneil@lblhcbpr20 ~]$ pixi exec -s root python root-issue-22414.py
ROOT version : 6.38.04
PartialMerge ok : True
Listed to keep : ['DstpToD0Pip_D0ToKmPip']
Output top-level : ['D0ToKmPip', 'DstpToD0Pip_D0ToKmPip']
Expected : ['DstpToD0Pip_D0ToKmPip']
BUG: suffix dir 'D0ToKmPip' leaked : True
control 'Unrelated' dropped : True
ROOT version
ROOT version : 6.38.04
$ .pixi/bin/pixi exec -s root root -b -q
------------------------------------------------------------------
| Welcome to ROOT 6.38.04 https://root.cern |
| (c) 1995-2025, The ROOT Team; conception: R. Brun, F. Rademakers |
| Built for linuxx8664gcc on Apr 08 2026, 08:58:32 |
| From tags/6-38-04@6-38-04 |
| With std202302 |
| Try '.help'/'.?', '.demo', '.license', '.credits', '.quit'/'.q' |
------------------------------------------------------------------
Installation method
conda-forge
Operating system
MacOS and alma9 linux
Additional context
No response
Check duplicate issues.
Description
Potentially falls under the umbrella of the title of #19330 but I'd say this is not a duplicate issue
When merging with
TFileMerger::PartialMerge(orMerge) using thekOnlyListedflag together withAddObjectNames(...), the merger keeps notonly the listed objects but also any unlisted top-level key whose name is a
suffix of one of the listed names.
The name test is a plain substring search that requires a trailing delimiter
but no leading delimiter, so
"D0ToKmPip"is treated as listed whenever"DstpToD0Pip_D0ToKmPip"is listed.example
File keys:
k1, k2, ak2, k3, bk3. We list onlyak2andbk3(
fObjectNames == "ak2 bk3 "). The merger tests each file key:k1"ak2 bk3 ".Contains("k1 ")k2"ak2 bk3 ".Contains("k2 ")ak2ak2"ak2 bk3 ".Contains("ak2 ")k3"ak2 bk3 ".Contains("k3 ")bk3bk3"ak2 bk3 ".Contains("bk3 ")Output:
k2, ak2, k3, bk3instead ofak2, bk3.Reproducer
Expected output:
ROOT version
ROOT version : 6.38.04
Installation method
conda-forge
Operating system
MacOS and alma9 linux
Additional context
No response