fix: don't let uproot.update mess up the TFile fVersion #1212
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.
When
uproot.update
was reading in a file, it used to unconditionally subtract 1000000 from theTFile::fVersion
that it read:uproot5/src/uproot/writing/_cascade.py
Lines 2175 to 2188 in e944099
The offset of 1000000 is how ROOT distinguishes between files with (some) 64-bit seek positions and files that are all 32-bit. Uproot's
FileHeader.version
holds the real version number, without the offset, and puts the offset back in if needed:uproot5/src/uproot/writing/_cascade.py
Lines 2093 to 2100 in e944099
But it was unconditionally subtracting 1000000 from the files it read, even if the file was fully 32-bit, as most are (because it only starts to matter for files bigger than 4 GB). So
uproot.update
was messing up a metadata field of most of the files it touched. (I can see how this mistake was made: I must have thought the line that subtracts 1000000 was still inside theif version >= 1000000:
block, just above.)Uproot doesn't do anything with the
TFile::fVersion
, other than use it as a 64-bit indicator, and I suppose we haven't been updating large enough files for this to be noticed. However, the version number that it wrote was nonsensical, a number near -937600.ROOT doesn't do much with these numbers, either, but it does more than Uproot. This bug was discovered because ROOT was shifting into a mode in which it was handling files as though they were older than ROOT 3. (The last version of ROOT 2.x was in September of the year 2000.) That fallback code did not work for TLeafL and TLeafO (among the TLeaf subclasses that I tested; TLeafD, TLeafI, and TLeafS were fine), and that makes sense because TLeafL and TLeafO were added in 2003 and in 2005, respectively (the others are older, and that's why they're fine).
For @zbilodea: files made with this correction can be read from ROOT with no warnings.
% cp one-tree.root two-trees.root
You were correct in your observation that the TStreamerInfo for the TLeafL class and the bytes of the TLeafL instance were identical between the file that worked without errors and the file that didn't. The "hinge" came from
uproot.update
setting a bad version number and sending ROOT into this pre-version 3 mode: it didn't read the (correct) TStreamerInfo, and because TLeafL was not designed before ROOT version 3, it generated an incorrect TStreamerInfo instead of reading the one in the file. That's also why it didn't matter whether you used the high-leveldel
or the low-levelFreeSegments.release
: either way,uproot.update
set theTFile::fVersion
to a nonsensical value.I'll merge this PR into
main
right away; when you mergemain
into your PR, it should fix the TLeafL error.This bug would have affected any files modified by
uproot.update
, any process that adds or removes an object, because any modification of theTFree
record (map of unused space within the file) would overwrite theFileHeader
to indicate that theTFree
record might have moved. As long as these files were smaller than 4 GB, Uproot would not have noticed, but it could have influenced ROOT in subtle ways, as shown above.