@@ -1675,20 +1675,27 @@ fn docTapeDims(p: anytype) TapeDims {
16751675 };
16761676}
16771677
1678+ /// Packed buffer total size (overflow-safe).
1679+ fn tapePackedSize (d : TapeDims ) u32 {
1680+ const tape_bytes : u64 = @as (u64 , d .tape_count ) * 8 ;
1681+ const total : u64 = 8 + tape_bytes + @as (u64 , d .input_len );
1682+ if (total > std .math .maxInt (u32 )) return 0 ;
1683+ return @intCast (total );
1684+ }
1685+
16781686/// Returns the size needed for the packed tape buffer.
16791687export fn doc_export_tape_size (doc_id : i32 ) u32 {
16801688 const p = getDocParser (doc_id ) orelse return 0 ;
1681- const d = docTapeDims (p );
1682- return 8 + d .tape_count * 8 + d .input_len ;
1689+ return tapePackedSize (docTapeDims (p ));
16831690}
16841691
16851692/// Write packed tape buffer to out_ptr. Returns bytes written, or 0 on error.
16861693export fn doc_export_tape (doc_id : i32 , out_ptr : [* ]u8 , out_cap : u32 ) u32 {
16871694 const p = getDocParser (doc_id ) orelse return 0 ;
16881695 const d = docTapeDims (p );
1696+ const total = tapePackedSize (d );
1697+ if (total == 0 or out_cap < total ) return 0 ;
16891698 const tape_bytes = d .tape_count * 8 ;
1690- const total : u32 = 8 + tape_bytes + d .input_len ;
1691- if (out_cap < total ) return 0 ;
16921699
16931700 // Header: tape_count, input_len (WASM is always little-endian)
16941701 const header : [* ]align (1 ) u32 = @ptrCast (out_ptr );
@@ -1713,7 +1720,10 @@ export fn doc_import_tape(buf_ptr: [*]const u8, buf_len: u32) i32 {
17131720 const tape_count = header [0 ];
17141721 const input_len = header [1 ];
17151722
1716- // Overflow-safe size validation: check tape_bytes won't overflow u32
1723+ // A valid tape has at least 2 words (root opening + closing)
1724+ if (tape_count < 2 ) return -1 ;
1725+
1726+ // Overflow-safe size validation
17171727 const tape_bytes : u64 = @as (u64 , tape_count ) * 8 ;
17181728 const expected : u64 = 8 + tape_bytes + @as (u64 , input_len );
17191729 if (expected > buf_len ) return -1 ;
@@ -1747,9 +1757,10 @@ export fn doc_import_tape(buf_ptr: [*]const u8, buf_len: u32) i32 {
17471757 // Activate slot
17481758 doc_active [uid ] = true ;
17491759 doc_is_json5 [uid ] = false ;
1750- // Mark src positions as built (empty) — imported tapes lack token data
1751- // needed by buildDocSrcPositions, so doc_get_src_pos returns 0xFFFFFFFF.
1752- doc_src [uid ] = .{ .positions = doc_src [uid ].positions , .cap = doc_src [uid ].cap , .len = 0 , .built = true };
1760+ // Imported tapes lack token data for buildDocSrcPositions.
1761+ // Mark as built with len=0 so doc_get_src_pos returns 0xFFFFFFFF.
1762+ doc_src [uid ].len = 0 ;
1763+ doc_src [uid ].built = true ;
17531764
17541765 return @intCast (uid );
17551766}
0 commit comments