Project
Loading...
Searching...
No Matches
Objects.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12#include "AODNative/Objects.h"
13#include "AODNative/Errors.h"
14#include "Cursor.h"
15#include <algorithm>
16#include <memory>
17#include <variant>
18
19namespace o2::aod::native
20{
21
22namespace
23{
24constexpr uint32_t kByteCountMask = 0x40000000;
25constexpr uint32_t kClassMask = 0x80000000;
26constexpr uint32_t kNewClassTag = 0xFFFFFFFF;
27constexpr uint16_t kStreamedMemberWise = 0x4000;
28constexpr uint32_t kIsReferenced = 1 << 4;
29constexpr int64_t kMapOffset = 2;
30// Fixed part of a TBasket key, after which come the class name, name and title.
31constexpr size_t kBasketHeaderTail = 19;
32
33struct LeafInfo {
34 std::string name;
35 std::string className;
36 int32_t len = 0;
37 bool isUnsigned = false;
38 std::string countLeaf;
39};
40
41struct EmbeddedBasket {
42 int32_t nEntries = 0;
43 std::vector<uint8_t> data;
44};
45
46using Object = std::variant<std::monostate,
47 std::shared_ptr<BranchInfo>,
48 std::shared_ptr<LeafInfo>,
49 std::shared_ptr<EmbeddedBasket>,
50 std::shared_ptr<std::string>>;
51
52template <typename T>
53std::shared_ptr<T> as(Object const& o)
54{
55 auto* p = std::get_if<std::shared_ptr<T>>(&o);
56 return p ? *p : nullptr;
57}
58
62class ObjectReader
63{
64 public:
65 explicit ObjectReader(Cursor& c) : mC(c) {}
66
67 TreeInfo tree();
68 std::map<std::string, std::string> stringMap();
69
70 private:
71 struct Versioned {
72 size_t end;
74 };
75
76 Versioned header(char const* className);
77 void checkVersion(char const* className, int version);
78 void tobject();
79 std::string tnamed();
80 void skipObject(char const* className, bool checkVersion = true);
81 std::vector<Object> objArray();
82 Object anyObject();
83 Object readByClass(std::string const& className, int64_t mapAt);
84 std::shared_ptr<BranchInfo> tbranch(int64_t mapAt);
85 std::shared_ptr<LeafInfo> tleaf(std::string const& className, int64_t mapAt);
86 std::shared_ptr<EmbeddedBasket> tbasket(int64_t mapAt);
87 std::shared_ptr<std::string> tobjstring(int64_t mapAt);
88
89 Cursor& mC;
90 std::map<int64_t, std::string> mClasses;
91 std::map<int64_t, Object> mObjects;
92};
93
94void ObjectReader::checkVersion(char const* className, int version)
95{
96 auto& table = supportedClassVersions();
97 auto it = table.find(className);
98 if (it == table.end() || std::find(it->second.begin(), it->second.end(), version) == it->second.end()) {
99 throw UnsupportedVersion(className, version, mC.file());
100 }
101}
102
103ObjectReader::Versioned ObjectReader::header(char const* className)
104{
105 auto start = mC.pos();
106 auto bcnt = mC.u32();
107 if (!(bcnt & kByteCountMask)) {
108 throw UnsupportedLayout(std::string("object of class ") + className + " without byte count");
109 }
110 size_t end = start + 4 + (bcnt & ~kByteCountMask);
111 if (end > mC.buffer().size()) {
112 mC.fail(std::string("byte count of ") + className + " past the end of the buffer");
113 }
114 auto version = mC.u16();
115 if (version & kStreamedMemberWise) {
116 throw UnsupportedLayout(std::string("memberwise streaming of ") + className);
117 }
118 checkVersion(className, version);
119 return {end, version};
120}
121
122void ObjectReader::tobject()
123{
124 auto version = mC.u16();
125 if (version & kStreamedMemberWise) {
126 // Written with a byte count: those were its high bytes. Skip the low ones.
127 mC.u16();
128 version = mC.u16();
129 }
130 checkVersion("TObject", version);
131 mC.u32(); // fUniqueID
132 auto bits = mC.u32();
133 if (bits & kIsReferenced) {
134 mC.u16(); // pidf
135 }
136}
137
138std::string ObjectReader::tnamed()
139{
140 auto v = header("TNamed");
141 tobject();
142 auto name = mC.string();
143 mC.string(); // fTitle
144 mC.seek(v.end);
145 return name;
146}
147
148void ObjectReader::skipObject(char const* className, bool check)
149{
150 auto start = mC.pos();
151 auto bcnt = mC.u32();
152 if (!(bcnt & kByteCountMask)) {
153 throw UnsupportedLayout(std::string("object of class ") + className + " without byte count");
154 }
155 auto version = mC.u16();
156 if (check) {
157 checkVersion(className, version);
158 }
159 mC.seek(start + 4);
160 mC.skip(bcnt & ~kByteCountMask);
161}
162
163std::vector<Object> ObjectReader::objArray()
164{
165 auto v = header("TObjArray");
166 tobject();
167 mC.string(); // fName
168 auto size = mC.i32();
169 mC.i32(); // fLowerBound
170 if (size < 0) {
171 mC.fail("negative TObjArray size");
172 }
173 std::vector<Object> result;
174 for (int i = 0; i < size; ++i) {
175 result.push_back(anyObject());
176 }
177 mC.seek(v.end);
178 return result;
179}
180
181// ROOT's read_object_any scheme. Recorded offsets are buffer displacements
182// (relative to the key start) plus kMapOffset.
183Object ObjectReader::anyObject()
184{
185 auto beg = mC.displacement();
186 auto begPos = mC.pos();
187 auto bcnt = mC.u32();
188 if (!(bcnt & kByteCountMask) || bcnt == kNewClassTag) {
189 // No byte count: the word is the tag itself. Only references to objects
190 // already read are written like this; new objects without a byte count
191 // come from very old files.
192 auto tag = bcnt;
193 if (tag & kClassMask) {
194 throw UnsupportedLayout("new object without byte count");
195 }
196 if (tag == 0) {
197 return {};
198 }
199 if (tag == 1) {
200 throw UnsupportedLayout("reference to the parent object");
201 }
202 auto it = mObjects.find(tag);
203 if (it == mObjects.end()) {
204 mC.fail("dangling object reference " + std::to_string(tag));
205 }
206 return it->second;
207 }
208 size_t end = begPos + 4 + (bcnt & ~kByteCountMask);
209 if (end > mC.buffer().size()) {
210 mC.fail("object byte count past the end of the buffer");
211 }
212 auto tagAt = mC.displacement();
213 auto tag = mC.u32();
214 Object result;
215 if (!(tag & kClassMask)) {
216 throw UnsupportedLayout("object reference with byte count");
217 } else if (tag == kNewClassTag) {
218 auto className = mC.cstring();
219 mClasses[tagAt + kMapOffset] = className;
220 result = readByClass(className, beg + kMapOffset);
221 } else {
222 auto it = mClasses.find(tag & ~kClassMask);
223 if (it == mClasses.end()) {
224 mC.fail("dangling class reference " + std::to_string(tag & ~kClassMask));
225 }
226 result = readByClass(it->second, beg + kMapOffset);
227 }
228 mC.seek(end);
229 return result;
230}
231
232Object ObjectReader::readByClass(std::string const& className, int64_t mapAt)
233{
234 if (className == "TBranch") {
235 return tbranch(mapAt);
236 }
237 if (className.size() == 6 && className.starts_with("TLeaf") && std::string_view("OBSILFD").find(className[5]) != std::string_view::npos) {
238 return tleaf(className, mapAt);
239 }
240 if (className == "TBasket") {
241 return tbasket(mapAt);
242 }
243 if (className == "TObjString") {
244 return tobjstring(mapAt);
245 }
246 throw UnsupportedLayout("objects of class " + className);
247}
248
249std::shared_ptr<LeafInfo> ObjectReader::tleaf(std::string const& className, int64_t mapAt)
250{
251 // ROOT maps an object before streaming its members.
252 auto leaf = std::make_shared<LeafInfo>();
253 mObjects[mapAt] = leaf;
254 auto vx = header(className.c_str());
255 auto vl = header("TLeaf");
256 leaf->className = className;
257 leaf->name = tnamed();
258 leaf->len = mC.i32();
259 mC.i32(); // fLenType
260 mC.i32(); // fOffset
261 mC.u8(); // fIsRange
262 leaf->isUnsigned = mC.u8() != 0;
263 auto count = anyObject();
264 if (auto countLeaf = as<LeafInfo>(count)) {
265 leaf->countLeaf = countLeaf->name;
266 } else if (!std::holds_alternative<std::monostate>(count)) {
267 throw UnsupportedLayout("fLeafCount of " + leaf->name + " is not a leaf");
268 }
269 mC.seek(vl.end);
270 // fMinimum and fMaximum, in the type of the leaf.
271 mC.seek(vx.end);
272 return leaf;
273}
274
275std::shared_ptr<EmbeddedBasket> ObjectReader::tbasket(int64_t mapAt)
276{
277 // TBasket has no versioned header of its own: it starts with its TKey.
278 auto basket = std::make_shared<EmbeddedBasket>();
279 mObjects[mapAt] = basket;
280 auto start = mC.pos();
281 mC.i32(); // fNbytes
282 mC.i16(); // key fVersion
283 mC.i32(); // fObjlen
284 mC.u32(); // fDatime
285 auto keylen = mC.u16();
286 mC.u16(); // fCycle
287 if (keylen < kBasketHeaderTail) {
288 mC.fail("embedded basket with key length " + std::to_string(keylen));
289 }
290 mC.seek(start + keylen - kBasketHeaderTail);
291 checkVersion("TBasket", mC.u16() % 1000);
292 mC.i32(); // fBufferSize
293 mC.i32(); // fNevBufSize
294 auto nevbuf = mC.i32();
295 auto last = mC.i32();
296 int flag = mC.u8();
297 if (nevbuf < 0 || last < keylen) {
298 mC.fail("inconsistent embedded basket header");
299 }
300 basket->nEntries = nevbuf;
301 if (flag == 99) {
302 // Header only, no data.
303 return basket;
304 }
305 bool displacements = flag > 40;
306 if (displacements) {
307 flag -= 40;
308 }
309 bool hasBuffer = flag > 10;
310 if (hasBuffer) {
311 flag -= 10;
312 }
313 if (flag == 1 && nevbuf > 0) {
314 // Entry offsets (and displacements), each a count followed by fNevBuf ints.
315 mC.skip(4 + 4 * (size_t)nevbuf);
316 if (displacements) {
317 mC.skip(4 + 4 * (size_t)nevbuf);
318 }
319 }
320 if (hasBuffer) {
321 // The buffer starts with a copy of the key, then the values.
322 mC.skip(keylen);
323 auto data = mC.bytes(last - keylen);
324 basket->data.assign(data.begin(), data.end());
325 }
326 return basket;
327}
328
329std::shared_ptr<std::string> ObjectReader::tobjstring(int64_t mapAt)
330{
331 auto s = std::make_shared<std::string>();
332 mObjects[mapAt] = s;
333 auto v = header("TObjString");
334 tobject();
335 *s = mC.string();
336 mC.seek(v.end);
337 return s;
338}
339
340std::shared_ptr<BranchInfo> ObjectReader::tbranch(int64_t mapAt)
341{
342 auto b = std::make_shared<BranchInfo>();
343 mObjects[mapAt] = b;
344 auto v = header("TBranch");
345 b->name = tnamed();
346 skipObject("TAttFill", false);
347 mC.i32(); // fCompress
348 mC.i32(); // fBasketSize
349 mC.i32(); // fEntryOffsetLen
350 auto writeBasket = mC.i32();
351 mC.i64(); // fEntryNumber
352 skipObject("ROOT::TIOFeatures", false);
353 mC.i32(); // fOffset
354 auto maxBaskets = mC.i32();
355 mC.i32(); // fSplitLevel
356 b->entries = mC.i64();
357 mC.i64(); // fFirstEntry
358 b->totBytes = mC.i64();
359 b->zipBytes = mC.i64();
360 if (maxBaskets < 0 || writeBasket < 0 || b->entries < 0) {
361 mC.fail("inconsistent TBranch header for " + b->name);
362 }
363 // The three basket arrays take 20 bytes per basket: check before allocating them.
364 if ((size_t)maxBaskets > mC.remaining() / 20) {
365 mC.fail("fMaxBaskets " + std::to_string(maxBaskets) + " of branch " + b->name + " exceeds the buffer");
366 }
367
368 if (!objArray().empty()) {
369 throw UnsupportedLayout("branch " + b->name + " has sub-branches");
370 }
371 auto leaves = objArray();
372 if (leaves.size() != 1 || !as<LeafInfo>(leaves[0])) {
373 throw UnsupportedLayout("branch " + b->name + " does not have exactly one leaf");
374 }
375 auto baskets = objArray();
376
377 auto readArray = [this, maxBaskets](auto reader) {
378 mC.u8(); // speedbump
379 std::vector<int64_t> values(maxBaskets);
380 for (auto& value : values) {
381 value = reader();
382 }
383 return values;
384 };
385 auto basketBytes = readArray([this] { return (int64_t)mC.i32(); });
386 auto basketEntry = readArray([this] { return mC.i64(); });
387 auto basketSeek = readArray([this] { return mC.i64(); });
388 if (!mC.string().empty()) {
389 throw UnsupportedLayout("branch " + b->name + " stores its baskets in another file");
390 }
391 mC.seek(v.end);
392
393 auto leaf = as<LeafInfo>(leaves[0]);
394 b->leafClass = leaf->className[5];
395 b->isUnsigned = leaf->isUnsigned;
396 b->fixedLen = leaf->len;
397 b->countBranch = leaf->countLeaf;
398
399 // Normal baskets end at the first empty seek, or at fWriteBasket.
400 int nNormal = 0;
401 while (nNormal < maxBaskets && nNormal < writeBasket && basketSeek[nNormal] != 0) {
402 ++nNormal;
403 }
404 for (int i = 0; i < nNormal; ++i) {
405 if (basketBytes[i] <= 0 || basketSeek[i] < 0) {
406 mC.fail("invalid basket location in branch " + b->name);
407 }
408 // When fBasketEntry has no slot after the last basket, the entry count
409 // is only in the basket header. The container layer fills it in (-1).
410 bool hasNext = i + 1 < maxBaskets;
411 if (basketEntry[i] < 0 || (hasNext && basketEntry[i + 1] < basketEntry[i])) {
412 mC.fail("invalid basket entries in branch " + b->name);
413 }
414 int64_t n = hasNext ? basketEntry[i + 1] - basketEntry[i] : -1;
415 if (n > INT32_MAX) {
416 mC.fail("invalid basket entries in branch " + b->name);
417 }
418 b->baskets.push_back(BasketRef{(uint64_t)basketSeek[i], (uint32_t)basketBytes[i], basketEntry[i], (int32_t)n, false, {}});
419 }
420 // Embedded baskets exist only if the normal ones do not hold all the entries.
421 bool knownNormal = nNormal < maxBaskets;
422 int64_t normalEntries = nNormal == 0 ? 0 : (knownNormal ? basketEntry[nNormal] : -1);
423 if (normalEntries != b->entries) {
424 int64_t first = knownNormal ? normalEntries : 0;
425 for (auto& o : baskets) {
426 auto e = as<EmbeddedBasket>(o);
427 if (!e || (!knownNormal && e->nEntries == 0)) {
428 continue;
429 }
430 b->baskets.push_back(BasketRef{0, 0, first, e->nEntries, true, std::move(e->data)});
431 first += e->nEntries;
432 }
433 }
434 // The last normal basket holds what the others do not: computing it saves
435 // reading its header, a round trip for a remote file. The decoder checks it
436 // against the header anyway. If it does not add up, the container layer
437 // reads the header.
438 if (nNormal > 0 && b->baskets[nNormal - 1].nEntries < 0) {
439 int64_t rest = b->entries - b->baskets[nNormal - 1].firstEntry;
440 for (size_t i = nNormal; i < b->baskets.size(); ++i) {
441 rest -= b->baskets[i].nEntries;
442 }
443 if (rest >= 0 && rest <= INT32_MAX) {
444 b->baskets[nNormal - 1].nEntries = (int32_t)rest;
445 }
446 }
447 return b;
448}
449
450TreeInfo ObjectReader::tree()
451{
452 TreeInfo t;
453 auto v = header("TTree");
454 t.name = tnamed();
455 skipObject("TAttLine", false);
456 skipObject("TAttFill", false);
457 skipObject("TAttMarker", false);
458 t.entries = mC.i64();
459 t.totBytes = mC.i64();
460 t.zipBytes = mC.i64();
461 mC.skip(2 * 8); // fSavedBytes, fFlushedBytes
462 mC.f64(); // fWeight
463 mC.skip(4 * 4); // fTimerInterval, fScanField, fUpdate, fDefaultEntryOffsetLen
464 auto nClusterRange = mC.u32();
465 mC.skip(6 * 8); // fMaxEntries ... fEstimate
466 mC.u8(); // speedbump
467 mC.skip(8 * (size_t)nClusterRange);
468 mC.u8(); // speedbump
469 mC.skip(8 * (size_t)nClusterRange);
470 skipObject("ROOT::TIOFeatures", false);
471 for (auto& o : objArray()) {
472 auto b = as<BranchInfo>(o);
473 if (!b) {
474 throw UnsupportedLayout("tree " + t.name + " has a branch which is not a TBranch");
475 }
476 t.branches.push_back(std::move(*b));
477 }
478 // fLeaves, fAliases, fIndexValues, fIndex, fTreeIndex, fFriends, fUserInfo, fBranchRef
479 mC.seek(v.end);
480 return t;
481}
482
483std::map<std::string, std::string> ObjectReader::stringMap()
484{
485 std::map<std::string, std::string> result;
486 auto v = header("TMap");
487 tobject();
488 mC.string(); // fName
489 auto n = mC.i32();
490 for (int i = 0; i < n; ++i) {
491 auto key = as<std::string>(anyObject());
492 auto value = as<std::string>(anyObject());
493 if (!key || !value) {
494 throw UnsupportedLayout("TMap entry which is not a TObjString pair");
495 }
496 result[*key] = *value;
497 }
498 mC.seek(v.end);
499 return result;
500}
501} // namespace
502
503size_t leafElementSize(char leafClass)
504{
505 switch (leafClass) {
506 case 'O':
507 case 'B':
508 return 1;
509 case 'S':
510 return 2;
511 case 'I':
512 case 'F':
513 return 4;
514 case 'L':
515 case 'D':
516 return 8;
517 }
518 throw UnsupportedLayout(std::string("leaf class TLeaf") + leafClass);
519}
520
521std::map<std::string, std::vector<int>> const& supportedClassVersions()
522{
523 static const std::map<std::string, std::vector<int>> table{
524 {"TObject", {1}},
525 {"TNamed", {1}},
526 {"TObjString", {1}},
527 {"TObjArray", {3}},
528 {"TMap", {3}},
529 {"TBasket", {3}},
530 {"TTree", {20}},
531 {"TBranch", {13}},
532 {"TLeaf", {2}},
533 {"TLeafO", {1}},
534 {"TLeafB", {1}},
535 {"TLeafS", {1}},
536 {"TLeafI", {1}},
537 {"TLeafL", {1}},
538 {"TLeafF", {1}},
539 {"TLeafD", {1}},
540 };
541 return table;
542}
543
544TreeInfo readTreePayload(std::span<const uint8_t> payload, uint16_t keylen, std::string const& file, uint64_t fileOffset)
545{
546 Cursor c{payload, file, fileOffset, keylen};
547 return ObjectReader{c}.tree();
548}
549
550std::map<std::string, std::string> readStringMapPayload(std::span<const uint8_t> payload, uint16_t keylen, std::string const& file, uint64_t fileOffset)
551{
552 Cursor c{payload, file, fileOffset, keylen};
553 return ObjectReader{c}.stringMap();
554}
555
556} // namespace o2::aod::native
std::vector< std::string > header
std::vector< long > entries
int32_t i
std::string countLeaf
Definition Objects.cxx:38
int32_t nEntries
Definition Objects.cxx:42
std::string className
Definition Objects.cxx:35
bool isUnsigned
Definition Objects.cxx:37
uint32_t c
Definition RawData.h:2
uint32_t version
Definition RawData.h:8
StringRef key
std::span< const uint8_t > buffer() const
Definition Cursor.h:42
std::span< const uint8_t > bytes(size_t n)
Definition Cursor.h:60
std::string const & file() const
Definition Cursor.h:101
size_t remaining() const
Definition Cursor.h:41
size_t pos() const
Definition Cursor.h:39
int64_t displacement() const
Definition Cursor.h:40
std::string string()
TString: 1-byte length, or 0xFF followed by a 4-byte length.
Definition Cursor.h:77
void fail(std::string const &what) const
Definition Cursor.h:96
std::string cstring()
NUL-terminated string, as used for class names after kNewClassTag.
Definition Cursor.h:88
void seek(size_t p)
Definition Cursor.h:44
void skip(size_t n)
Definition Cursor.h:51
GLdouble n
Definition glcorearb.h:1982
GLint GLsizei count
Definition glcorearb.h:399
GLuint64EXT * result
Definition glcorearb.h:5662
GLsizeiptr size
Definition glcorearb.h:659
GLuint GLuint end
Definition glcorearb.h:469
const GLdouble * v
Definition glcorearb.h:832
GLuint const GLchar * name
Definition glcorearb.h:781
GLint first
Definition glcorearb.h:399
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLenum GLsizei GLsizei GLint * values
Definition glcorearb.h:1576
GLboolean * data
Definition glcorearb.h:298
GLenum GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const void * bits
Definition glcorearb.h:4150
GLuint start
Definition glcorearb.h:469
GLenum GLenum GLsizei len
Definition glcorearb.h:4232
TreeInfo readTreePayload(std::span< const uint8_t > payload, uint16_t keylen, std::string const &file, uint64_t fileOffset)
Definition Objects.cxx:544
size_t leafElementSize(char leafClass)
Size in bytes of one value of a leaf class on disk.
Definition Objects.cxx:503
std::map< std::string, std::vector< int > > const & supportedClassVersions()
Definition Objects.cxx:521
std::map< std::string, std::string > readStringMapPayload(std::span< const uint8_t > payload, uint16_t keylen, std::string const &file, uint64_t fileOffset)
Deserializes a TMap of TObjString to TObjString (metaData, parentFiles).
Definition Objects.cxx:550
PlaceholderNode as(Configurable< T > const &v)
void check(const std::vector< std::string > &arguments, const std::vector< ConfigParamSpec > &workflowOptions, const std::vector< DeviceSpec > &deviceSpecs, CheckMatrix &matrix)
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
void empty(int)
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))