Skip to main content

compiler/
snapshot.rs

1//! Binary serialization for compiled [`Bytecode`] (`.mbc` files).
2//!
3//! Format and safety model: docs/bytecode-snapshot-design.md. This module is
4//! defense layer L1: structural validation plus a linear scan over every
5//! instruction stream, so bytecode that reaches the VM never indexes out of
6//! range and never jumps into the middle of an instruction. Stack discipline
7//! and runtime types are deliberately left to the VM's own checks (L3).
8
9use std::collections::HashMap;
10use std::convert::{TryFrom, TryInto};
11use std::rc::Rc;
12
13use object::builtins::BuiltIns;
14use object::{CompiledFunction, Object};
15use parser::lexer::token::Span;
16use strum::IntoEnumIterator;
17
18use crate::compiler::{BindingDebugInfo, Bytecode, DebugInfo, PcSpan};
19use crate::op_code::{read_operands, Instructions, Opcode, DEFINITIONS};
20
21/// Bump when the container layout changes (header, sections, tags, varint
22/// rules). Bytecode ABI changes are covered by the fingerprint instead.
23/// v2: debug info carries local bindings and free names per function.
24pub const FORMAT_VERSION: u8 = 2;
25
26pub(crate) const MAGIC: [u8; 4] = *b"MBC\0";
27pub(crate) const FLAG_HAS_DEBUG_INFO: u8 = 0b0000_0001;
28
29pub(crate) const TAG_INTEGER: u8 = 1;
30pub(crate) const TAG_STRING: u8 = 2;
31pub(crate) const TAG_FUNCTION: u8 = 3;
32
33#[derive(Debug, PartialEq)]
34pub enum SnapshotWriteError {
35    /// `Bytecode.constants` is a public field, so the writer cannot assume it
36    /// only holds the three variants the compiler emits.
37    UnsupportedConstant { index: usize, kind: String },
38}
39
40#[derive(Debug, PartialEq)]
41pub enum SnapshotError {
42    BadMagic,
43    UnsupportedVersion {
44        found: u8,
45        expected: u8,
46    },
47    AbiFingerprintMismatch {
48        found: u32,
49        expected: u32,
50    },
51    UnexpectedEof,
52    InvalidLeb128,
53    IntegerOverflow,
54    /// A declared size exceeds the remaining input bytes.
55    LimitExceeded,
56    BadTag(u8),
57    BadUtf8,
58    BadFlags(u8),
59    TrailingBytes,
60    /// Instruction-stream validation failure, with stream and offset.
61    InvalidInstruction(String),
62    DuplicateDebugEntry(usize),
63    DebugPcNotIncreasing {
64        pc: usize,
65    },
66    /// The debug entry's constant index does not name a function constant.
67    DebugIndexNotFunction(usize),
68    DebugPcOutOfRange {
69        pc: usize,
70        len: usize,
71    },
72    /// Local binding slots must be strictly increasing (hence unique).
73    DebugSlotNotIncreasing {
74        slot: usize,
75    },
76    /// A local binding names a slot the function does not have.
77    DebugSlotOutOfRange {
78        slot: usize,
79        num_locals: usize,
80    },
81    /// Main's debug info must not carry local bindings or free names; its
82    /// bindings are the globals, which live outside the container.
83    DebugMainBindingsNotEmpty,
84    /// A debug-bearing snapshot must describe every function targeted by an
85    /// `OpClosure`, otherwise its capture count cannot be validated.
86    MissingFunctionDebugInfo(usize),
87    /// An `OpClosure`'s free count disagrees with the target function's
88    /// `free_names` metadata.
89    DebugFreeCountMismatch {
90        constant_index: usize,
91        operand: usize,
92        free_names: usize,
93    },
94}
95
96lazy_static! {
97    static ref ABI_FINGERPRINT: u32 = compute_abi_fingerprint();
98}
99
100/// Fingerprint of the bytecode ABI: every opcode (discriminant, name,
101/// operand widths, in enum order) and every builtin (index, name, in table
102/// order — `OpGetBuiltin` operands are indexes into that table). This is a
103/// compatibility sentinel, not integrity protection: safety against forged
104/// headers rests on the L1/L2/L3 checks, not on this value.
105pub fn bytecode_abi_fingerprint() -> u32 {
106    *ABI_FINGERPRINT
107}
108
109fn compute_abi_fingerprint() -> u32 {
110    let mut hash = Fnv1a::new();
111    for opcode in Opcode::iter() {
112        let definition = DEFINITIONS
113            .get(&opcode)
114            .unwrap_or_else(|| panic!("opcode {:?} missing from DEFINITIONS", opcode));
115        hash.absorb_u64(opcode as u64);
116        hash.absorb_bytes(definition.name().as_bytes());
117        for &width in definition.operand_widths() {
118            hash.absorb_u64(width as u64);
119        }
120    }
121    for (index, builtin) in BuiltIns.iter().enumerate() {
122        hash.absorb_u64(index as u64);
123        hash.absorb_bytes(builtin.name.as_bytes());
124    }
125    hash.finish()
126}
127
128/// FNV-1a, 32-bit.
129struct Fnv1a(u32);
130
131impl Fnv1a {
132    fn new() -> Self {
133        Fnv1a(0x811c_9dc5)
134    }
135
136    fn write(&mut self, bytes: &[u8]) {
137        for &byte in bytes {
138            self.0 ^= u32::from(byte);
139            self.0 = self.0.wrapping_mul(0x0100_0193);
140        }
141    }
142
143    /// Absorb one field as (ULEB length, content) so adjacent fields cannot
144    /// be reinterpreted across their boundary.
145    fn absorb_bytes(&mut self, bytes: &[u8]) {
146        let mut length = Vec::new();
147        write_uleb128(&mut length, bytes.len() as u64);
148        self.write(&length);
149        self.write(bytes);
150    }
151
152    fn absorb_u64(&mut self, value: u64) {
153        let mut encoded = Vec::new();
154        write_uleb128(&mut encoded, value);
155        self.absorb_bytes(&encoded);
156    }
157
158    fn finish(&self) -> u32 {
159        self.0
160    }
161}
162
163/// Serialize `bytecode` into the `.mbc` container. With `strip_debug` the
164/// debug section is omitted entirely (flags bit 0 cleared).
165///
166/// Output is deterministic: `function_debug_info` entries are written in
167/// ascending constant-index order.
168pub fn write_bytecode(
169    bytecode: &Bytecode,
170    strip_debug: bool,
171) -> Result<Vec<u8>, SnapshotWriteError> {
172    let mut out = Vec::new();
173    out.extend_from_slice(&MAGIC);
174    out.push(FORMAT_VERSION);
175    out.extend_from_slice(&bytecode_abi_fingerprint().to_le_bytes());
176    out.push(if strip_debug { 0 } else { FLAG_HAS_DEBUG_INFO });
177
178    write_bytes(&mut out, &bytecode.instructions.data);
179    write_uleb128(&mut out, bytecode.constants.len() as u64);
180    for (index, constant) in bytecode.constants.iter().enumerate() {
181        write_constant(&mut out, index, constant)?;
182    }
183
184    if !strip_debug {
185        write_debug_info(&mut out, &bytecode.debug_info);
186        let mut entries: Vec<_> = bytecode.function_debug_info.iter().collect();
187        entries.sort_by_key(|(index, _)| **index);
188        write_uleb128(&mut out, entries.len() as u64);
189        for (index, debug_info) in entries {
190            write_uleb128(&mut out, *index as u64);
191            write_debug_info(&mut out, debug_info);
192        }
193    }
194    Ok(out)
195}
196
197fn write_constant(
198    out: &mut Vec<u8>,
199    index: usize,
200    constant: &Object,
201) -> Result<(), SnapshotWriteError> {
202    match constant {
203        Object::Integer(value) => {
204            out.push(TAG_INTEGER);
205            write_sleb128(out, *value);
206        }
207        Object::String(value) => {
208            out.push(TAG_STRING);
209            write_string(out, value);
210        }
211        Object::CompiledFunction(function) => {
212            out.push(TAG_FUNCTION);
213            write_string(out, &function.name);
214            write_uleb128(out, function.num_locals as u64);
215            write_uleb128(out, function.num_parameters as u64);
216            write_bytes(out, &function.instructions);
217        }
218        other => {
219            return Err(SnapshotWriteError::UnsupportedConstant {
220                index,
221                kind: object_kind(other).to_string(),
222            })
223        }
224    }
225    Ok(())
226}
227
228fn write_debug_info(out: &mut Vec<u8>, debug_info: &DebugInfo) {
229    write_uleb128(out, debug_info.pc_spans.len() as u64);
230    for pc_span in &debug_info.pc_spans {
231        write_uleb128(out, pc_span.pc as u64);
232        write_uleb128(out, pc_span.span.start as u64);
233        write_uleb128(out, pc_span.span.end as u64);
234    }
235    write_uleb128(out, debug_info.local_bindings.len() as u64);
236    for binding in &debug_info.local_bindings {
237        write_uleb128(out, binding.slot as u64);
238        write_string(out, &binding.name);
239    }
240    write_uleb128(out, debug_info.free_names.len() as u64);
241    for name in &debug_info.free_names {
242        write_string(out, name);
243    }
244}
245
246fn write_string(out: &mut Vec<u8>, value: &str) {
247    write_bytes(out, value.as_bytes());
248}
249
250fn write_bytes(out: &mut Vec<u8>, bytes: &[u8]) {
251    write_uleb128(out, bytes.len() as u64);
252    out.extend_from_slice(bytes);
253}
254
255pub(crate) fn write_uleb128(out: &mut Vec<u8>, mut value: u64) {
256    loop {
257        let byte = (value & 0x7f) as u8;
258        value >>= 7;
259        if value == 0 {
260            out.push(byte);
261            return;
262        }
263        out.push(byte | 0x80);
264    }
265}
266
267pub(crate) fn write_sleb128(out: &mut Vec<u8>, mut value: i64) {
268    loop {
269        let byte = (value & 0x7f) as u8;
270        value >>= 7;
271        let sign_bit_clear = byte & 0x40 == 0;
272        if (value == 0 && sign_bit_clear) || (value == -1 && !sign_bit_clear) {
273            out.push(byte);
274            return;
275        }
276        out.push(byte | 0x80);
277    }
278}
279
280fn object_kind(object: &Object) -> &'static str {
281    match object {
282        Object::Integer(_) => "Integer",
283        Object::Boolean(_) => "Boolean",
284        Object::String(_) => "String",
285        Object::Array(_) => "Array",
286        Object::Hash(_) => "Hash",
287        Object::Null => "Null",
288        Object::ReturnValue(_) => "ReturnValue",
289        Object::Function(..) => "Function",
290        Object::Builtin(_) => "Builtin",
291        Object::Error(_) => "Error",
292        Object::CompiledFunction(_) => "CompiledFunction",
293        Object::ClosureObj(_) => "Closure",
294        Object::Class(_) => "Class",
295        Object::Instance(_) => "Instance",
296        Object::BoundMethod(_) => "BoundMethod",
297    }
298}
299
300/// Deserialize and validate an `.mbc` buffer. The input is untrusted: every
301/// malformed input returns `Err`, and anything returned `Ok` has passed the
302/// L1 checks (§6 of the design doc).
303pub fn read_bytecode(buf: &[u8]) -> Result<Bytecode, SnapshotError> {
304    let mut reader = Reader::new(buf);
305
306    let magic = reader.read_exact(MAGIC.len())?;
307    if magic != MAGIC {
308        return Err(SnapshotError::BadMagic);
309    }
310    let version = reader.read_u8()?;
311    if version != FORMAT_VERSION {
312        return Err(SnapshotError::UnsupportedVersion {
313            found: version,
314            expected: FORMAT_VERSION,
315        });
316    }
317    let found = u32::from_le_bytes(reader.read_exact(4)?.try_into().unwrap());
318    let expected = bytecode_abi_fingerprint();
319    if found != expected {
320        return Err(SnapshotError::AbiFingerprintMismatch {
321            found,
322            expected,
323        });
324    }
325    let flags = reader.read_u8()?;
326    if flags & !FLAG_HAS_DEBUG_INFO != 0 {
327        return Err(SnapshotError::BadFlags(flags));
328    }
329    let has_debug = flags & FLAG_HAS_DEBUG_INFO != 0;
330
331    let main_instructions = reader.read_length_prefixed_bytes()?.to_vec();
332    let constant_count = reader.read_count()?;
333    let mut constants: Vec<Rc<Object>> = Vec::with_capacity(constant_count);
334    for _ in 0..constant_count {
335        constants.push(Rc::new(read_constant(&mut reader)?));
336    }
337
338    let (debug_info, function_debug_info) = if has_debug {
339        read_debug_section(&mut reader, &constants, main_instructions.len())?
340    } else {
341        (DebugInfo::default(), HashMap::new())
342    };
343
344    if reader.remaining() != 0 {
345        return Err(SnapshotError::TrailingBytes);
346    }
347
348    let closure_debug = has_debug.then_some(&function_debug_info);
349    validate_instruction_stream("main", &main_instructions, &constants, closure_debug)?;
350    for (index, constant) in constants.iter().enumerate() {
351        if let Object::CompiledFunction(function) = constant.as_ref() {
352            validate_instruction_stream(
353                &format!("constant {}", index),
354                &function.instructions,
355                &constants,
356                closure_debug,
357            )?;
358        }
359    }
360
361    Ok(Bytecode {
362        instructions: Instructions {
363            data: main_instructions,
364        },
365        constants,
366        debug_info,
367        function_debug_info,
368    })
369}
370
371fn read_constant(reader: &mut Reader) -> Result<Object, SnapshotError> {
372    let tag = reader.read_u8()?;
373    match tag {
374        TAG_INTEGER => Ok(Object::Integer(reader.read_sleb128()?)),
375        TAG_STRING => Ok(Object::String(reader.read_string()?)),
376        TAG_FUNCTION => {
377            let name = reader.read_string()?;
378            let num_locals = reader.read_usize()?;
379            let num_parameters = reader.read_usize()?;
380            let instructions = reader.read_length_prefixed_bytes()?.to_vec();
381            Ok(Object::CompiledFunction(Rc::new(CompiledFunction {
382                name,
383                instructions,
384                num_locals,
385                num_parameters,
386            })))
387        }
388        other => Err(SnapshotError::BadTag(other)),
389    }
390}
391
392fn read_debug_section(
393    reader: &mut Reader,
394    constants: &[Rc<Object>],
395    main_len: usize,
396) -> Result<(DebugInfo, HashMap<usize, DebugInfo>), SnapshotError> {
397    let main_debug = read_debug_info(reader, main_len)?;
398    if !main_debug.local_bindings.is_empty() || !main_debug.free_names.is_empty() {
399        return Err(SnapshotError::DebugMainBindingsNotEmpty);
400    }
401    let entry_count = reader.read_count()?;
402    let mut function_debug_info = HashMap::with_capacity(entry_count);
403    for _ in 0..entry_count {
404        let constant_index = reader.read_usize()?;
405        let (function_len, num_locals) = match constants.get(constant_index).map(Rc::as_ref) {
406            Some(Object::CompiledFunction(function)) => {
407                (function.instructions.len(), function.num_locals)
408            }
409            _ => return Err(SnapshotError::DebugIndexNotFunction(constant_index)),
410        };
411        let debug_info = read_debug_info(reader, function_len)?;
412        validate_local_bindings(&debug_info.local_bindings, num_locals)?;
413        if function_debug_info
414            .insert(constant_index, debug_info)
415            .is_some()
416        {
417            return Err(SnapshotError::DuplicateDebugEntry(constant_index));
418        }
419    }
420    Ok((main_debug, function_debug_info))
421}
422
423/// Slots must be strictly increasing (hence unique) and inside the frame's
424/// local window. The compiler emits one binding per slot; the reader only
425/// requires what later consumers rely on.
426fn validate_local_bindings(
427    bindings: &[BindingDebugInfo],
428    num_locals: usize,
429) -> Result<(), SnapshotError> {
430    let mut previous: Option<usize> = None;
431    for binding in bindings {
432        if previous.is_some_and(|previous| binding.slot <= previous) {
433            return Err(SnapshotError::DebugSlotNotIncreasing {
434                slot: binding.slot,
435            });
436        }
437        if binding.slot >= num_locals {
438            return Err(SnapshotError::DebugSlotOutOfRange {
439                slot: binding.slot,
440                num_locals,
441            });
442        }
443        previous = Some(binding.slot);
444    }
445    Ok(())
446}
447
448fn read_debug_info(
449    reader: &mut Reader,
450    instruction_len: usize,
451) -> Result<DebugInfo, SnapshotError> {
452    let count = reader.read_count()?;
453    let mut pc_spans = Vec::with_capacity(count);
454    let mut previous: Option<usize> = None;
455    for _ in 0..count {
456        let pc = reader.read_usize()?;
457        if let Some(previous) = previous {
458            if pc <= previous {
459                return Err(SnapshotError::DebugPcNotIncreasing {
460                    pc,
461                });
462            }
463        }
464        if pc > instruction_len {
465            return Err(SnapshotError::DebugPcOutOfRange {
466                pc,
467                len: instruction_len,
468            });
469        }
470        let start = reader.read_usize()?;
471        let end = reader.read_usize()?;
472        pc_spans.push(PcSpan {
473            pc,
474            span: Span {
475                start,
476                end,
477            },
478        });
479        previous = Some(pc);
480    }
481    let binding_count = reader.read_count()?;
482    let mut local_bindings = Vec::with_capacity(binding_count);
483    for _ in 0..binding_count {
484        let slot = reader.read_usize()?;
485        let name = reader.read_string()?;
486        local_bindings.push(BindingDebugInfo {
487            name,
488            slot,
489        });
490    }
491    let free_count = reader.read_count()?;
492    let mut free_names = Vec::with_capacity(free_count);
493    for _ in 0..free_count {
494        free_names.push(reader.read_string()?);
495    }
496    Ok(DebugInfo {
497        pc_spans,
498        local_bindings,
499        free_names,
500    })
501}
502
503/// L1 linear scan of one instruction stream (§6 of the design doc): every
504/// opcode is defined, operands are complete, jumps land on instruction
505/// boundaries (or one past the end), and index operands stay inside the
506/// constant pool / builtin table with the constant kind each opcode needs.
507///
508/// Deliberately not checked here: stack depth, operand runtime types,
509/// local/free index validity. Those depend on execution state and are the
510/// VM's defensive checks (L3).
511///
512/// With `debug` present, every `OpClosure`'s free count is also checked
513/// against the target function's `free_names` metadata, so two sites cannot
514/// construct the same constant inconsistently.
515fn validate_instruction_stream(
516    stream: &str,
517    instructions: &[u8],
518    constants: &[Rc<Object>],
519    debug: Option<&HashMap<usize, DebugInfo>>,
520) -> Result<(), SnapshotError> {
521    let len = instructions.len();
522    let mut is_boundary = vec![false; len + 1];
523    let mut jumps: Vec<(usize, usize)> = Vec::new();
524    let mut offset = 0;
525    while offset < len {
526        is_boundary[offset] = true;
527        let byte = instructions[offset];
528        let opcode = Opcode::from_repr(byte)
529            .ok_or_else(|| invalid(stream, offset, format!("unknown opcode 0x{:02x}", byte)))?;
530        let definition = DEFINITIONS.get(&opcode).expect("missing opcode definition");
531        let operand_len: usize = definition
532            .operand_widths()
533            .iter()
534            .map(|w| *w as usize)
535            .sum();
536        if offset + 1 + operand_len > len {
537            return Err(invalid(
538                stream,
539                offset,
540                format!("truncated operands for {}", definition.name()),
541            ));
542        }
543        let (operands, _) = read_operands(definition, &instructions[offset + 1..]);
544        match opcode {
545            Opcode::OpJump | Opcode::OpJumpNotTruthy => jumps.push((offset, operands[0])),
546            Opcode::OpConst => {
547                if operands[0] >= constants.len() {
548                    return Err(invalid(
549                        stream,
550                        offset,
551                        format!("constant index {} out of range", operands[0]),
552                    ));
553                }
554            }
555            Opcode::OpClosure => {
556                let index = operands[0];
557                if !matches!(
558                    constants.get(index).map(Rc::as_ref),
559                    Some(Object::CompiledFunction(_))
560                ) {
561                    return Err(invalid(
562                        stream,
563                        offset,
564                        format!("OpClosure needs a function constant at index {}", index),
565                    ));
566                }
567                if let Some(debug) = debug {
568                    let info = debug
569                        .get(&index)
570                        .ok_or(SnapshotError::MissingFunctionDebugInfo(index))?;
571                    if operands[1] != info.free_names.len() {
572                        return Err(SnapshotError::DebugFreeCountMismatch {
573                            constant_index: index,
574                            operand: operands[1],
575                            free_names: info.free_names.len(),
576                        });
577                    }
578                }
579            }
580            Opcode::OpClass | Opcode::OpMethod | Opcode::OpGetProperty | Opcode::OpSetProperty => {
581                let index = operands[0];
582                if !matches!(constants.get(index).map(Rc::as_ref), Some(Object::String(_))) {
583                    return Err(invalid(
584                        stream,
585                        offset,
586                        format!("{} needs a string constant at index {}", definition.name(), index),
587                    ));
588                }
589            }
590            Opcode::OpGetBuiltin => {
591                if operands[0] >= BuiltIns.len() {
592                    return Err(invalid(
593                        stream,
594                        offset,
595                        format!("builtin index {} out of range", operands[0]),
596                    ));
597                }
598            }
599            Opcode::OpHash if operands[0] % 2 != 0 => {
600                return Err(invalid(
601                    stream,
602                    offset,
603                    format!("OpHash needs an even element count, got {}", operands[0]),
604                ));
605            }
606            _ => {}
607        }
608        offset += 1 + operand_len;
609    }
610    is_boundary[len] = true;
611    for (offset, target) in jumps {
612        if target > len || !is_boundary[target] {
613            return Err(invalid(
614                stream,
615                offset,
616                format!("jump target {} is not an instruction boundary", target),
617            ));
618        }
619    }
620    Ok(())
621}
622
623fn invalid(stream: &str, offset: usize, message: String) -> SnapshotError {
624    SnapshotError::InvalidInstruction(format!("{} (stream {}, offset {})", message, stream, offset))
625}
626
627pub(crate) struct Reader<'a> {
628    buf: &'a [u8],
629    pos: usize,
630}
631
632impl<'a> Reader<'a> {
633    pub(crate) fn new(buf: &'a [u8]) -> Self {
634        Reader {
635            buf,
636            pos: 0,
637        }
638    }
639
640    /// Cursor offset from the start of the buffer, for byte-range annotation
641    /// (see `snapshot_layout`).
642    pub(crate) fn position(&self) -> usize {
643        self.pos
644    }
645
646    fn remaining(&self) -> usize {
647        self.buf.len() - self.pos
648    }
649
650    pub(crate) fn read_u8(&mut self) -> Result<u8, SnapshotError> {
651        let byte = *self.buf.get(self.pos).ok_or(SnapshotError::UnexpectedEof)?;
652        self.pos += 1;
653        Ok(byte)
654    }
655
656    pub(crate) fn read_exact(&mut self, len: usize) -> Result<&'a [u8], SnapshotError> {
657        if len > self.remaining() {
658            return Err(SnapshotError::UnexpectedEof);
659        }
660        let slice = &self.buf[self.pos..self.pos + len];
661        self.pos += len;
662        Ok(slice)
663    }
664
665    /// Non-canonical encodings are accepted; only length and 64-bit range
666    /// are enforced (§4.1 hard rules).
667    pub(crate) fn read_uleb128(&mut self) -> Result<u64, SnapshotError> {
668        let mut result: u64 = 0;
669        let mut shift = 0u32;
670        for _ in 0..10 {
671            let byte = self.read_u8()?;
672            let bits = u64::from(byte & 0x7f);
673            if shift == 63 && bits > 1 {
674                return Err(SnapshotError::InvalidLeb128);
675            }
676            result |= bits << shift;
677            if byte & 0x80 == 0 {
678                return Ok(result);
679            }
680            shift += 7;
681        }
682        Err(SnapshotError::InvalidLeb128)
683    }
684
685    pub(crate) fn read_sleb128(&mut self) -> Result<i64, SnapshotError> {
686        let mut result: i64 = 0;
687        let mut shift = 0u32;
688        for _ in 0..10 {
689            let byte = self.read_u8()?;
690            let bits = i64::from(byte & 0x7f);
691            if shift == 63 {
692                // Tenth byte: only one value bit is left in an i64, so the
693                // payload must be all sign bits and end the encoding.
694                if byte & 0x80 != 0 || (bits != 0 && bits != 0x7f) {
695                    return Err(SnapshotError::InvalidLeb128);
696                }
697                return Ok(result | bits.wrapping_shl(63));
698            }
699            result |= bits << shift;
700            if byte & 0x80 == 0 {
701                if byte & 0x40 != 0 {
702                    result |= -1i64 << (shift + 7);
703                }
704                return Ok(result);
705            }
706            shift += 7;
707        }
708        Err(SnapshotError::InvalidLeb128)
709    }
710
711    /// ULEB128 checked into `usize` (they differ on wasm32).
712    pub(crate) fn read_usize(&mut self) -> Result<usize, SnapshotError> {
713        let value = self.read_uleb128()?;
714        usize::try_from(value).map_err(|_| SnapshotError::IntegerOverflow)
715    }
716
717    /// Entry count under the resource rule: every entry occupies at least
718    /// one input byte, so a count above the remaining input is rejected and
719    /// `Vec::with_capacity(count)` stays O(input size).
720    fn read_count(&mut self) -> Result<usize, SnapshotError> {
721        let count = self.read_usize()?;
722        if count > self.remaining() {
723            return Err(SnapshotError::LimitExceeded);
724        }
725        Ok(count)
726    }
727
728    fn read_length_prefixed_bytes(&mut self) -> Result<&'a [u8], SnapshotError> {
729        let len = self.read_usize()?;
730        if len > self.remaining() {
731            return Err(SnapshotError::LimitExceeded);
732        }
733        self.read_exact(len)
734    }
735
736    fn read_string(&mut self) -> Result<String, SnapshotError> {
737        let bytes = self.read_length_prefixed_bytes()?;
738        String::from_utf8(bytes.to_vec()).map_err(|_| SnapshotError::BadUtf8)
739    }
740}