@@ -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 ' ;
0 commit comments