Skip to content

Commit d4af421

Browse files
committed
io: Extend large collection test
1 parent f25828f commit d4af421

2 files changed

Lines changed: 99 additions & 6 deletions

File tree

io/io/test/testLargeCollection.cxx

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -444,19 +444,21 @@ int testAsPartOfObject()
444444
{
445445
int errors = 0;
446446

447-
std::vector<char> raw;
448-
raw.reserve(6 * 1024 * 1024 * 1024ll);
449-
TBufferFile b(TBuffer::kWrite, 6 * 1024 * 1024 * 1024ll - 100, raw.data(), false /* don't adopt */,
450-
DoNothingAllocator);
447+
// Let TBufferFile own and manage its own buffer so it can auto-expand freely.
448+
// Start small; it will grow as needed (up to ~8+ GB for the 2G-float large object).
449+
TBufferFile b(TBuffer::kWrite, 1024);
451450

452-
LargeCollectionFixture fixture;
451+
{
452+
LargeCollectionFixture fixture;
453453

454454
// --- small object (well within 2 GB region) ---
455455
fixture.fFloats.assign(1000, 1.0f);
456456
fixture.fPoints.assign(500, {1.f, 2.f, 3.f});
457457
auto startSmall = b.GetCurrent() - b.Buffer();
458458
b.WriteObject(&fixture, false /* cacheReuse */);
459459
errors += readAndCheckFixture("small object", b, startSmall, 1000, 500);
460+
}
461+
LargeCollectionFixture fixture;
460462

461463
// --- large object (floats cross 2 GB, written in regular section) ---
462464
b.SetWriteMode();
@@ -528,10 +530,99 @@ int testNested()
528530
return errors;
529531
}
530532

533+
// -----------------------------------------------------------------------
534+
// 5. Minimal reproducer for two related >4 GB TBufferFile bugs:
535+
//
536+
// Bug A (write-side): WriteObject on an object whose serialised content
537+
// crosses the 4 GB mark in the buffer. SetByteCount fires an assert
538+
// because cntpos < 4 GB but fBufCur - fBuffer > 4 GB after streaming.
539+
// Minimal trigger: start writing just below 4 GB, payload > remaining space.
540+
//
541+
// Bug B (read-side): ReadObjectAny on an object whose buffer start
542+
// position > kMaxUInt. MapObject fires "offset <= kMaxUInt" assert.
543+
// Minimal trigger: seek to > 4 GB, WriteObject tiny fixture, ReadObjectAny.
544+
// -----------------------------------------------------------------------
545+
int testMapObjectLargeOffset()
546+
{
547+
int errors = 0;
548+
549+
// Each sub-test uses its own fresh TBufferFile so the class map is clean,
550+
// ensuring every read-back sees a genuine "new class" entry in ReadClass
551+
// and exercises the MapObject path at the relevant buffer offset.
552+
553+
// --- Bug A: object START is just below 4 GB but the payload (10 M floats = 40 MB)
554+
// pushes fBufCur past kMaxUInt. SetByteCount / WriteObjectClass must handle
555+
// a byte-count position that was recorded below 4 GB while the current
556+
// position is above it.
557+
{
558+
const Long64_t kBufSize = (4LL * 1024 + 128) * 1024 * 1024; // 4.125 GB
559+
std::vector<char> raw(kBufSize, 0);
560+
TBufferFile b(TBuffer::kWrite, kBufSize, raw.data(), false /* don't adopt */, DoNothingAllocator);
561+
562+
const Long64_t kStartA = 4LL * 1024 * 1024 * 1024 - 256;
563+
b.SetBufferOffset(kStartA);
564+
LargeCollectionFixture fixture;
565+
fixture.fFloats.assign(10 * 1024 * 1024, 1.0f); // 40 MB — crosses the 4 GB boundary
566+
fixture.fPoints.assign(3, {1.f, 2.f, 3.f});
567+
b.WriteObject(&fixture, false /* cacheReuse */);
568+
569+
b.SetReadMode();
570+
b.SetBufferOffset(kStartA);
571+
auto *objA = b.ReadObjectAny(TClass::GetClass(typeid(LargeCollectionFixture)));
572+
if (!objA) {
573+
std::cerr << "testMapObjectLargeOffset BugA: ReadObjectAny returned null\n";
574+
++errors;
575+
} else {
576+
auto *f = static_cast<LargeCollectionFixture *>(objA);
577+
if (f->fFloats.size() != 10u * 1024 * 1024 || f->fPoints.size() != 3) {
578+
std::cerr << "testMapObjectLargeOffset BugA: size mismatch\n";
579+
++errors;
580+
}
581+
delete f;
582+
}
583+
}
584+
585+
// --- Bug B: object written and read at a start position entirely above kMaxUInt.
586+
// ReadObjectAny -> ReadClass -> MapObject(cl, startpos+kMapOffset) where
587+
// startpos+kMapOffset > kMaxUInt, hitting R__ASSERT(offset <= kMaxUInt)
588+
// in the TObject* overload of TBufferIO::MapObject.
589+
// A fresh TBufferFile is required so that ReadClass sees a "new class"
590+
// (not already cached) and calls MapObject with the >4 GB offset.
591+
{
592+
const Long64_t kBufSize = (4LL * 1024 + 128) * 1024 * 1024; // 4.125 GB
593+
std::vector<char> raw(kBufSize, 0);
594+
TBufferFile b(TBuffer::kWrite, kBufSize, raw.data(), false /* don't adopt */, DoNothingAllocator);
595+
596+
const Long64_t kStartB = 4LL * 1024 * 1024 * 1024 + 100; // just above kMaxUInt
597+
b.SetBufferOffset(kStartB);
598+
LargeCollectionFixture fixture;
599+
fixture.fFloats.assign(10, 2.0f);
600+
fixture.fPoints.assign(2, {2.f, 3.f, 4.f});
601+
b.WriteObject(&fixture, false /* cacheReuse */);
602+
603+
b.SetReadMode();
604+
b.SetBufferOffset(kStartB);
605+
auto *objB = b.ReadObjectAny(TClass::GetClass(typeid(LargeCollectionFixture)));
606+
if (!objB) {
607+
std::cerr << "testMapObjectLargeOffset BugB: ReadObjectAny returned null\n";
608+
++errors;
609+
} else {
610+
auto *f = static_cast<LargeCollectionFixture *>(objB);
611+
if (f->fFloats.size() != 10 || f->fPoints.size() != 2) {
612+
std::cerr << "testMapObjectLargeOffset BugB: size mismatch\n";
613+
++errors;
614+
}
615+
delete f;
616+
}
617+
}
618+
619+
return errors;
620+
}
621+
531622
// -----------------------------------------------------------------------
532623
// Entry point
533624
// -----------------------------------------------------------------------
534-
int testLargeCollection(bool timing = true)
625+
int testLargeCollection(bool timing = false)
535626
{
536627
int errors = 0;
537628

@@ -541,6 +632,7 @@ int testLargeCollection(bool timing = true)
541632
TIME_SUBTEST(timing, "testDirectVector", testDirectVector(sharedLargeFloats));
542633
{ std::vector<float>{}.swap(sharedLargeFloats); } // release 2 GB before the large-object test
543634
TIME_SUBTEST(timing, "testAsPartOfObject", testAsPartOfObject());
635+
TIME_SUBTEST(timing, "testMapObjectLargeOffset", testMapObjectLargeOffset());
544636
TIME_SUBTEST(timing, "testNested", testNested());
545637

546638
std::cerr << "Done. errors=" << errors << '\n';

io/io/test/testLargeCollection.ref

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ testDirectNumerical ...
33
testDirectStruct ...
44
testDirectVector ...
55
testAsPartOfObject ...
6+
testMapObjectLargeOffset ...
67
testNested ...
78
Done. errors=0
89
(int) 0

0 commit comments

Comments
 (0)