[io] Fix crash reading emulated collections of SSO strings - #22925
Open
guitargeek wants to merge 2 commits into
Open
[io] Fix crash reading emulated collections of SSO strings#22925guitargeek wants to merge 2 commits into
guitargeek wants to merge 2 commits into
Conversation
TEmulatedCollectionProxy stores the collection in a std::vector of raw aligned bytes. When that buffer grows past its capacity, Expand() lets it relocate the existing elements with a raw memory copy. That is correct for trivially relocatable content, but corrupts emulated objects that keep a pointer into themselves -- most notably a libstdc++ std::string (and TString) using the small-string optimization, whose internal data pointer keeps pointing into the old, now freed, buffer. The corrupted object then crashes when it is destroyed, freeing an invalid pointer. TClass::Move(), invoked by Expand(), does not actually move the data for emulated classes, so it cannot fix up the relocated objects. Expand() is however only ever reached while preparing the collection to be entirely overwritten by a member-wise read, so when the buffer would reallocate and the value/key is a non-pointer class or string, destroy the (still valid) elements first and reconstruct all of them at the new location instead of relocating them. This fixes the abort observed when reading e.g. a std::vector<std::pair<std::string,double>> without its dictionary. Closes root-project#20882 🤖 Done with the help of AI.
The nolib test reads the STL containers back without the dictionary library, exercising the emulated collection proxy. It was flagged WILLFAIL because it crashed ROOT (and, before that, silently failed to compile) and its reference file dated back to the CINT/Makefile era, so it never matched. Now that the underlying crash is fixed (see previous commit), regenerate the reference from the current, deterministic output and drop the WILLFAIL flag, turning nolib into a real regression test that guards against the crash reappearing.
guitargeek
requested review from
bellenot,
dpiparo,
jblomer and
pcanal
as code owners
July 27, 2026 03:46
Test Results 23 files 23 suites 3d 17h 22m 1s ⏱️ For more details on these failures, see this check. Results for commit 6226f3a. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TEmulatedCollectionProxy stores the collection in a std::vector of raw aligned bytes. When that buffer grows past its capacity, Expand() lets it relocate the existing elements with a raw memory copy. That is correct for trivially relocatable content, but corrupts emulated objects that keep a pointer into themselves -- most notably a libstdc++ std::string (and TString) using the small-string optimization, whose internal data pointer keeps pointing into the old, now freed, buffer. The corrupted object then crashes when it is destroyed, freeing an invalid pointer.
TClass::Move(), invoked by Expand(), does not actually move the data for emulated classes, so it cannot fix up the relocated objects. Expand() is however only ever reached while preparing the collection to be entirely overwritten by a member-wise read, so when the buffer would reallocate and the value/key is a non-pointer class or string, destroy the (still valid) elements first and reconstruct all of them at the new location instead of relocating them.
This fixes the abort observed when reading e.g. a
std::vector<std::pair<std::string,double>>without its dictionary.Closes #20882