Skip to content

[core] Make TClass decl-name registry an immortal singleton - #22929

Open
guitargeek wants to merge 1 commit into
root-project:masterfrom
guitargeek:issue-13200
Open

[core] Make TClass decl-name registry an immortal singleton#22929
guitargeek wants to merge 1 commit into
root-project:masterfrom
guitargeek:issue-13200

Conversation

@guitargeek

Copy link
Copy Markdown
Contributor

The static TClass::fNoInfoOrEmuOrFwdDeclNameRegistry object was destroyed during static teardown at exit(). The TGenericClassInfo destructors run later, in __cxa_finalize, and reach the registry through

~TGenericClassInfo -> ROOT::RemoveClass -> TClass::SetUnloaded
  -> ~InsertTClassInRegistryRAII -> TDeclNameRegistry::AddQualifiedName
  -> std::unordered_set::insert

By then the registry's unordered_set has already been torn down, so the insert dereferences freed hash-table storage. This is a static destruction-order fiasco: the order between libCore's file-scope statics and the finalizer-registered TGenericClassInfo destructors is unspecified across translation units and shared libraries. On Linux/libstdc++ it happens to be benign; on FreeBSD/libc++ it segfaults at exit (e.g. the stressroofit tests), matching the valgrind "Invalid read" in __emplace_unique_key_args reported in the issue.

Replace the static data member with a construct-on-first-use accessor holding an intentionally leaked object, so the registry outlives every other static and stays valid during any teardown-time access.

Because the registry is now never freed, everything it owns stays reachable until process death. Existing "Anything allocated by TClass::Init" suppressions already cover the construction path; add two caller-independent entries (keyed on GetNoInfoOrEmuOrFwdDeclNameRegistry and AddQualifiedName, match-leak-kinds: reachable) so the SetUnloaded teardown path is covered too and valgrind output gains no new lines.

Fixes #13200

🤖 Done with the help of AI.

The static TClass::fNoInfoOrEmuOrFwdDeclNameRegistry object was destroyed
during static teardown at exit(). The TGenericClassInfo destructors run
later, in __cxa_finalize, and reach the registry through

    ~TGenericClassInfo -> ROOT::RemoveClass -> TClass::SetUnloaded
      -> ~InsertTClassInRegistryRAII -> TDeclNameRegistry::AddQualifiedName
      -> std::unordered_set::insert

By then the registry's unordered_set has already been torn down, so the
insert dereferences freed hash-table storage. This is a static
destruction-order fiasco: the order between libCore's file-scope statics
and the finalizer-registered TGenericClassInfo destructors is unspecified
across translation units and shared libraries. On Linux/libstdc++ it
happens to be benign; on FreeBSD/libc++ it segfaults at exit (e.g. the
stressroofit tests), matching the valgrind "Invalid read" in
__emplace_unique_key_args reported in the issue.

Replace the static data member with a construct-on-first-use accessor
holding an intentionally leaked object, so the registry outlives every
other static and stays valid during any teardown-time access.

Because the registry is now never freed, everything it owns stays
reachable until process death. Existing "Anything allocated by
TClass::Init" suppressions already cover the construction path; add two
caller-independent entries (keyed on GetNoInfoOrEmuOrFwdDeclNameRegistry
and AddQualifiedName, match-leak-kinds: reachable) so the SetUnloaded
teardown path is covered too and valgrind output gains no new lines.

Fixes root-project#13200

🤖 Done with the help of AI.
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

Test Results

    23 files      23 suites   3d 14h 23m 11s ⏱️
 3 877 tests  3 877 ✅ 0 💤 0 ❌
78 960 runs  78 960 ✅ 0 💤 0 ❌

Results for commit a1a72a9.

♻️ This comment has been updated with latest results.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FreeBSD] roofit tests segfault

1 participant