Valgrind reports memory leak from TObject::Write and TTree::Branch


ROOT Version: 6.32.10
Platform: Ubuntu 22.04.5
Compiler: g++ 11.4.0


I created a small example of a simple class, that is written to a tree. When I check the program with valgrind using

valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --errors-for-leak-kinds=all -s --num-callers=30 --suppressions=$ROOTSYS/etc/valgrind-root.supp ./branchTest

I get a lot of messages of definitely lost bytes from creating the branch (line 12), writing the tree (line 19), and creating or closing the file (line 8 and 20).

I’ve attached the example program (branchTest.cxx), the header for the class (MyLeaf.h), and the LinkDef.h file. The program can be compiled using

rootcling -f MyLeafDict.cxx -rml MyLeaf.so -rmf MyLeaf.rootmap MyLeaf.h LinkDef.h
g++ -fPIC -c MyLeafDict.cxx -o MyLeafDict.o -g -O3 -Wall -Wextra -pedantic -Wno-unknown-pragmas -Wno-unused-function -Wshadow $(root-config --cflags)
g++ -fPIC branchTest.cxx -o branchTest -g -O3 -Wall -Wextra -pedantic -Wno-unknown-pragmas -Wno-unused-function -Wshadow $(root-config --cflags --libs) MyLeafDict.o -I.

branchTest.cxx (465 Bytes)

LinkDef.h (190 Bytes)

MyLeaf.h (321 Bytes)

I can’t attach the full output here as it is too big, but these are excerpts of it:

==431734== 112 bytes in 1 blocks are definitely lost in loss record 9,294 of 14,174
==431734==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==431734==    by 0x914B90F: clang::Parser::AnnotateTemplateIdToken(clang::OpaquePtr<clang::TemplateName>, clang::TemplateNameKind, clang::CXXScopeSpec&, clang::SourceLocation, clang::UnqualifiedId&, bool, bool) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x90CAFA1: clang::Parser::ParseOptionalCXXScopeSpecifier(clang::CXXScopeSpec&, clang::OpaquePtr<clang::QualType>, bool, bool, bool*, bool, clang::IdentifierInfo**, bool, bool) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x915B917: clang::Parser::TryAnnotateCXXScopeToken(bool) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x85ED145: cling::LookupHelper::findScope(llvm::StringRef, cling::LookupHelper::DiagSetting, clang::Type const**, bool) const (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x84F92D0: TCling::CheckClassInfo(char const*, bool, bool) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x4B15B41: TClass::Init(char const*, short, std::type_info const*, TVirtualIsAProxy*, char const*, char const*, int, int, ClassInfo_t*, bool) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B1739E: TClass::TClass(char const*, short, std::type_info const&, TVirtualIsAProxy*, char const*, char const*, int, int, bool) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B178EC: ROOT::CreateClass(char const*, short, std::type_info const&, TVirtualIsAProxy*, char const*, char const*, int, int) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B28691: ROOT::TGenericClassInfo::GetClass() (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B0B541: TClass::GetClass(char const*, bool, bool, unsigned long, unsigned long) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4ED7A2D: TStreamerInfo::Build(bool) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4B02050: TClass::GetStreamerInfoImpl(int, bool) const (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B021FD: TClass::GetStreamerInfo(int, bool) const (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x5B9212B: TTree::BuildStreamerInfo(TClass*, void*, bool) (in /opt/cern/root/root_v6.32.10/lib/libTree.so)
==431734==    by 0x5BA4A19: TTree::BronchExec(char const*, char const*, void*, bool, int, int) (in /opt/cern/root/root_v6.32.10/lib/libTree.so)
==431734==    by 0x5B90568: TTree::Branch(char const*, char const*, void*, int, int) (in /opt/cern/root/root_v6.32.10/lib/libTree.so)
==431734==    by 0x10D97B: Branch<MyLeaf> (TTree.h:403)
==431734==    by 0x10D97B: main (branchTest.cxx:15)

==431734== 112 bytes in 1 blocks are definitely lost in loss record 9,297 of 14,174
==431734==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==431734==    by 0x914B90F: clang::Parser::AnnotateTemplateIdToken(clang::OpaquePtr<clang::TemplateName>, clang::TemplateNameKind, clang::CXXScopeSpec&, clang::SourceLocation, clang::UnqualifiedId&, bool, bool) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x90CAFA1: clang::Parser::ParseOptionalCXXScopeSpecifier(clang::CXXScopeSpec&, clang::OpaquePtr<clang::QualType>, bool, bool, bool*, bool, clang::IdentifierInfo**, bool, bool) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x915B917: clang::Parser::TryAnnotateCXXScopeToken(bool) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x85ED145: cling::LookupHelper::findScope(llvm::StringRef, cling::LookupHelper::DiagSetting, clang::Type const**, bool) const (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x84ECCAD: TCling::GetClassSharedLibs(char const*) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x84F8F3B: TClingLookupHelper__ExistingTypeCheck(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x4AEEC16: TClassEdit::GetNormalizedName(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, std::basic_string_view<char, std::char_traits<char> >) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B0B768: TClass::GetClass(char const*, bool, bool, unsigned long, unsigned long) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B1224D: TBuildRealData::Inspect(TClass*, char const*, char const*, void const*, bool) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x84F600F: TCling::InspectMembers(TMemberInspector&, void const*, TClass const*, bool) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x4B03D2D: TClass::CallShowMembers(void const*, TMemberInspector&, bool) const (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B1086F: TClass::BuildRealData(void*, bool) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4ED5BE5: TStreamerInfo::Build(bool) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4B02050: TClass::GetStreamerInfoImpl(int, bool) const (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B021FD: TClass::GetStreamerInfo(int, bool) const (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4E0220B: TBufferFile::WriteClassBuffer(TClass const*, void*) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x5B3DCBA: TBranchElement::Streamer(TBuffer&) (in /opt/cern/root/root_v6.32.10/lib/libTree.so)
==431734==    by 0x4E011FA: TBufferFile::WriteObjectClass(void const*, TClass const*, bool) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4E093B3: TBufferIO::WriteObjectAny(void const*, TClass const*, bool) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4ACB65C: TObjArray::Streamer(TBuffer&) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4DFC7AB: TBufferFile::WriteFastArray(void*, TClass const*, long long, TMemberStreamer*) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x508CA65: int TStreamerInfo::WriteBufferAux<char**>(TBuffer&, char** const&, TStreamerInfo::TCompInfo* const*, int, int, int, int, int) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4ED9C03: TStreamerInfoActions::GenericWriteAction(TBuffer&, void*, TStreamerInfoActions::TConfiguration const*) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4E02144: TBufferFile::WriteClassBuffer(TClass const*, void*) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4EB1ACC: TKey::TKey(TObject const*, char const*, int, TDirectory*) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4E6E2A8: TFile::CreateKey(TDirectory*, TObject const*, char const*, int) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4E5EC20: TDirectoryFile::WriteTObject(TObject const*, char const*, char const*, int) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4A4273C: TObject::Write(char const*, int, int) const (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x10D9FD: main (branchTest.cxx:22)

==431734== 1,344 bytes in 8 blocks are definitely lost in loss record 12,711 of 14,174
==431734==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==431734==    by 0x914B90F: clang::Parser::AnnotateTemplateIdToken(clang::OpaquePtr<clang::TemplateName>, clang::TemplateNameKind, clang::CXXScopeSpec&, clang::SourceLocation, clang::UnqualifiedId&, bool, bool) (in /opt/cern/root/root_v6.32.10/l
ib/libCling.so)
==431734==    by 0x90CAFA1: clang::Parser::ParseOptionalCXXScopeSpecifier(clang::CXXScopeSpec&, clang::OpaquePtr<clang::QualType>, bool, bool, bool*, bool, clang::IdentifierInfo**, bool, bool) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x915B917: clang::Parser::TryAnnotateCXXScopeToken(bool) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x85ED145: cling::LookupHelper::findScope(llvm::StringRef, cling::LookupHelper::DiagSetting, clang::Type const**, bool) const (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x84ECCAD: TCling::GetClassSharedLibs(char const*) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x84DB48A: TCling::ShallowAutoLoadImpl(char const*) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x84F3AF7: TCling::DeepAutoLoadImpl(char const*, std::unordered_set<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocat
or<char> > >, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >&, bool) (in /opt/cern/root/root_v6.
32.10/lib/libCling.so)
==431734==    by 0x84F4073: TCling::AutoLoad(char const*, bool) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x4B02EE8: TClass::LoadClassDefault(char const*, bool) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B0BD62: TClass::GetClass(char const*, bool, bool, unsigned long, unsigned long) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4EA9F34: TGenCollectionProxy::Value::Value(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, bool, unsigned long, unsigned long) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4E5977C: TEmulatedCollectionProxy::InitializeEx(bool) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4E5A447: TEmulatedCollectionProxy::TEmulatedCollectionProxy(char const*, bool) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4E5503B: (anonymous namespace)::GenEmulation(char const*, bool) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4B1524E: TClass::Init(char const*, short, std::type_info const*, TVirtualIsAProxy*, char const*, char const*, int, int, ClassInfo_t*, bool) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B161C8: TClass::TClass(char const*, short, bool) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x84F788D: TCling::GenerateTClass(char const*, bool, bool) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x4B0BDB2: TClass::GetClass(char const*, bool, bool, unsigned long, unsigned long) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B1224D: TBuildRealData::Inspect(TClass*, char const*, char const*, void const*, bool) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x84F600F: TCling::InspectMembers(TMemberInspector&, void const*, TClass const*, bool) (in /opt/cern/root/root_v6.32.10/lib/libCling.so)
==431734==    by 0x4B03D2D: TClass::CallShowMembers(void const*, TMemberInspector&, bool) const (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B1086F: TClass::BuildRealData(void*, bool) (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4ED5BE5: TStreamerInfo::Build(bool) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4B02050: TClass::GetStreamerInfoImpl(int, bool) const (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4B021FD: TClass::GetStreamerInfo(int, bool) const (in /opt/cern/root/root_v6.32.10/lib/libCore.so)
==431734==    by 0x4E0220B: TBufferFile::WriteClassBuffer(TClass const*, void*) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4EB1ACC: TKey::TKey(TObject const*, char const*, int, TDirectory*) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4E6E2A8: TFile::CreateKey(TDirectory*, TObject const*, char const*, int) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)
==431734==    by 0x4E5EC20: TDirectoryFile::WriteTObject(TObject const*, char const*, char const*, int) (in /opt/cern/root/root_v6.32.10/lib/libRIO.so)

Hi,

Thanks a lot for this report: it’s interesting.
It seems like it comes from a clang symbol and not a ROOT symbol. Do you have means to check the very same setup but with any 6.36 release or the main branch (called master, in the case of ROOT)?
I am asking because the 6.32 release series features llvm16, 6.36 llvm18 and, since today, master llvm20.

Cheers,
D

I think this was solved some time ago via [cling] Forcefully clean up `TemplateIdAnnotation`s by hahnjo · Pull Request #16150 · root-project/root · GitHub

That fix was from September 2024, and the version I’m using is from February 2025, surely this fix would be part of 6.32.10?

I will try and test this against 6.36.04 and report the results.

Looks like using 6.36.04 solves this problem, see the two different leak summaries below. No more definitely lost bytes. A few more errors (10585 vs 10116), and the same invalid read from the TFile constructor, but the memory leak seems to be fixed.

valgrind.6.32.10.out:==431828== LEAK SUMMARY:
valgrind.6.32.10.out-==431828==    definitely lost: 31,136 bytes in 238 blocks
valgrind.6.32.10.out-==431828==    indirectly lost: 0 bytes in 0 blocks
valgrind.6.32.10.out-==431828==      possibly lost: 0 bytes in 0 blocks
valgrind.6.32.10.out-==431828==    still reachable: 23,809,778 bytes in 40,500 blocks
valgrind.6.32.10.out-==431828==                       of which reachable via heuristic:
valgrind.6.32.10.out-==431828==                         newarray           : 23,040 bytes in 40 blocks
valgrind.6.32.10.out-==431828==         suppressed: 177,317 bytes in 2,120 blocks
valgrind.6.32.10.out-==431828== 
valgrind.6.32.10.out-==431828== ERROR SUMMARY: 10116 errors from 10116 contexts (suppressed: 2902 from 1269)
valgrind.6.32.10.out-==431828== 
--
valgrind.6.36.04.out:==10596== LEAK SUMMARY:
valgrind.6.36.04.out-==10596==    definitely lost: 0 bytes in 0 blocks
valgrind.6.36.04.out-==10596==    indirectly lost: 0 bytes in 0 blocks
valgrind.6.36.04.out-==10596==      possibly lost: 4,231,680 bytes in 43 blocks
valgrind.6.36.04.out-==10596==    still reachable: 22,651,802 bytes in 47,162 blocks
valgrind.6.36.04.out-==10596==                       of which reachable via heuristic:
valgrind.6.36.04.out-==10596==                         newarray           : 23,040 bytes in 40 blocks
valgrind.6.36.04.out-==10596==                         multipleinheritance: 2,184 bytes in 3 blocks
valgrind.6.36.04.out-==10596==         suppressed: 509,700 bytes in 4,694 blocks
valgrind.6.36.04.out-==10596== 
valgrind.6.36.04.out-==10596== ERROR SUMMARY: 10585 errors from 10585 contexts (suppressed: 3732 from 1672)

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.