@@ -561,4 +561,85 @@ final class FlatbuffersVerifierTests {
561561 let offset : UOffset = try verifier. getValue ( at: value)
562562 return Int ( clamping: ( Int ( offset) &+ 0 ) . magnitude)
563563 }
564+
565+ @Test
566+ func testHighVTableSlotIsReadUnsignedLikeTheVerifier( ) throws {
567+ // Crafted buffer whose `array` vtable slot is 0x8000. The verifier reads
568+ // the slot as an unsigned offset and accepts the buffer (the vector at
569+ // table + 32768 is in bounds). Table.offset must observe the same
570+ // displacement; re-reading the slot as a signed value would send the
571+ // accessor before the start of the buffer.
572+ // `array` is the 3rd field in tests/vector_has_test.fbs (VT.array = 8).
573+ // The object size of 4 is deliberate: field data may live outside the
574+ // table's inline region; the verifier only checks target bounds.
575+ var bytes = [ UInt8] ( repeating: 0 , count: 32792 )
576+ bytes [ 0 ..< 4 ] = [ 4 , 0 , 0 , 0 ] // root offset -> table at 4
577+ bytes [ 4 ..< 8 ] = [ 0xFC , 0xFF , 0xFF , 0xFF ] // soffset -4 -> vtable at 8
578+ bytes [ 8 ..< 10 ] = [ 10 , 0 ] // vtable size
579+ bytes [ 10 ..< 12 ] = [ 4 , 0 ] // object size
580+ bytes [ 16 ..< 18 ] = [ 0 , 0x80 ] // `array` slot (vtable + 8) = 0x8000
581+ // Field lives at table (4) + 0x8000 and holds a uoffset to the vector.
582+ bytes [ 32772 ..< 32776 ] = [ 8 , 0 , 0 , 0 ] // uoffset -> vector header at 32780
583+ bytes [ 32780 ..< 32784 ] = [ 1 , 0 , 0 , 0 ] // vector length = 1
584+ bytes [ 32784 ..< 32792 ] = [ 0x2A , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] // element 42
585+
586+ var byteBuffer = ByteBuffer ( bytes: bytes)
587+ let vectors : Swift_Tests_Vectors = try getCheckedRoot ( byteBuffer: & byteBuffer)
588+ #expect( vectors. array. count == 1 )
589+ #expect( vectors. array [ 0 ] == 42 )
590+ }
591+
592+ @Test
593+ func testHighVTableSlotMutateUsesVerifiedPosition( ) throws {
594+ // Same crafted-slot shape through the write path: `mutate` reuses
595+ // Table.offset, so the patched unsigned read must also steer writes to
596+ // the verified field position. `count` is the 3rd field of
597+ // MyGame.Example.Stat (VT.count = 8).
598+ var bytes = [ UInt8] ( repeating: 0 , count: 32776 )
599+ bytes [ 0 ..< 4 ] = [ 4 , 0 , 0 , 0 ] // root offset -> table at 4
600+ bytes [ 4 ..< 8 ] = [ 0xFC , 0xFF , 0xFF , 0xFF ] // soffset -4 -> vtable at 8
601+ bytes [ 8 ..< 10 ] = [ 10 , 0 ] // vtable size
602+ bytes [ 10 ..< 12 ] = [ 4 , 0 ] // object size
603+ bytes [ 16 ..< 18 ] = [ 0 , 0x80 ] // `count` slot (vtable + 8) = 0x8000
604+ bytes [ 32772 ..< 32774 ] = [ 0x2A , 0 ] // count = 42 at table (4) + 0x8000
605+
606+ var byteBuffer = ByteBuffer ( bytes: bytes)
607+ var stat : MyGame_Example_Stat = try getCheckedRoot ( byteBuffer: & byteBuffer)
608+ #expect( stat. count == 42 )
609+ #expect( stat. mutate ( count: 0x7777 ) )
610+ #expect( stat. count == 0x7777 )
611+ }
612+
613+ @Test
614+ func testHighVTableSlotLookupByKeyUsesVerifiedPosition( ) throws {
615+ // Generated `lookupByKey` resolves keyed-table slots through the static
616+ // Table.offset(_:vOffset:fbb:) overload; the slot read there must match
617+ // the verifier too. Monster carries a sorted Stat vector
618+ // (VT.scalarKeySortedTables = 104) whose Stat `count` slot
619+ // (VT.count = 8) is 0x8000.
620+ var bytes = [ UInt8] ( repeating: 0 , count: 32928 )
621+ bytes [ 0 ..< 4 ] = [ 4 , 0 , 0 , 0 ] // root offset -> Monster table at 4
622+ bytes [ 4 ..< 8 ] = [ 0xFC , 0xFF , 0xFF , 0xFF ] // soffset -4 -> vtable at 8
623+ bytes [ 8 ..< 10 ] = [ 106 , 0 ] // Monster vtable size (covers slot 104)
624+ bytes [ 10 ..< 12 ] = [ 4 , 0 ] // object size
625+ bytes [ 18 ..< 20 ] = [ 112 , 0 ] // `name` slot (vtable + 10) -> field at 116
626+ bytes [ 112 ..< 114 ] = [ 128 , 0 ] // `scalarKeySortedTables` slot -> field at 132
627+ bytes [ 116 ..< 120 ] = [ 8 , 0 , 0 , 0 ] // name uoffset -> string at 124
628+ bytes [ 124 ..< 128 ] = [ 2 , 0 , 0 , 0 ] // string length
629+ bytes [ 128 ..< 130 ] = [ 0x41 , 0x42 ] // "AB"
630+ bytes [ 132 ..< 136 ] = [ 8 , 0 , 0 , 0 ] // vector-field uoffset -> header at 140
631+ bytes [ 140 ..< 144 ] = [ 1 , 0 , 0 , 0 ] // vector length = 1
632+ bytes [ 144 ..< 148 ] = [ 8 , 0 , 0 , 0 ] // element uoffset -> Stat table at 152
633+ bytes [ 152 ..< 156 ] = [ 0xF8 , 0xFF , 0xFF , 0xFF ] // soffset -8 -> vtable at 160
634+ bytes [ 160 ..< 162 ] = [ 10 , 0 ] // Stat vtable size
635+ bytes [ 162 ..< 164 ] = [ 4 , 0 ] // object size
636+ bytes [ 168 ..< 170 ] = [ 0 , 0x80 ] // `count` slot (vtable + 8) = 0x8000
637+ bytes [ 32920 ..< 32922 ] = [ 0 , 0 ] // count = 0 at Stat (152) + 0x8000
638+
639+ var byteBuffer = ByteBuffer ( bytes: bytes)
640+ let monster : MyGame_Example_Monster = try getCheckedRoot ( byteBuffer: & byteBuffer)
641+ let stat = monster. scalarKeySortedTablesBy ( key: 0 )
642+ #expect( stat != nil )
643+ #expect( stat? . count == 0 )
644+ }
564645}
0 commit comments