Skip to main content

rd_rds/
decode.rs

1use crate::wire::{
2    BASEENV_SXP, BASENAMESPACE_SXP, BUILTINSXP, CHARSXP, CPLXSXP, EMPTYENV_SXP, ENVSXP, EXPRSXP,
3    GLOBALENV_SXP, INTSXP, ItemFlags, LGLSXP, LISTSXP, MISSINGARG_SXP, NA_INTEGER, NA_REAL_BITS,
4    NAMESPACESXP, NILSXP, NILVALUE_SXP, PACKAGESXP, PERSISTSXP, RAWSXP, REALSXP, REFSXP, RefEntry,
5    S4SXP, SPECIALSXP, STRSXP, SYMSXP, UNBOUNDVALUE_SXP, VECSXP, WireState, is_dotted_pair, is_nil,
6};
7use crate::{
8    Attribute, Attributes, ByteCursor, EnvHandle, Error, Header, Limits, Persisted, RObject, RStr,
9    RValue, SexpKind, Symbol,
10};
11
12pub fn parse(bytes: &[u8]) -> Result<RObject, Error> {
13    parse_with_options(bytes, ParseOptions::default())
14}
15
16pub fn parse_with_limits(bytes: &[u8], limits: Limits) -> Result<RObject, Error> {
17    parse_with_options(bytes, ParseOptions::default().limits(limits))
18}
19
20/// Options controlling decompressed RDS parsing.
21#[derive(Debug, Clone, Copy, Default)]
22#[must_use]
23pub struct ParseOptions {
24    limits: Limits,
25    native_encoding_policy: NativeEncodingPolicy,
26}
27
28impl ParseOptions {
29    /// Sets the resource limits used while decoding.
30    pub fn limits(mut self, limits: Limits) -> Self {
31        self.limits = limits;
32        self
33    }
34
35    /// Sets the policy for native strings when the header field is absent,
36    /// which means format 2; retained `RStr` values are validated when
37    /// converted, while `SYMSXP` print names are converted during parsing.
38    pub fn native_encoding_policy(mut self, policy: NativeEncodingPolicy) -> Self {
39        self.native_encoding_policy = policy;
40        self
41    }
42
43    pub(crate) fn limits_value(self) -> Limits {
44        self.limits
45    }
46
47    pub(crate) fn native_encoding_policy_value(self) -> NativeEncodingPolicy {
48        self.native_encoding_policy
49    }
50}
51
52/// Controls how a native CHARSXP is interpreted when the RDS header field is
53/// absent, which means format 2. Parsing retains bytes lazily for `RStr` values:
54/// conversion by [`crate::RStr::as_str`] or a typed view then performs validation
55/// or rejection. A `SYMSXP` print name is converted during parsing instead, so a
56/// symbol name that cannot be decoded fails immediately with
57/// [`crate::Error::InvalidSymbolName`] under either policy.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
59#[non_exhaustive]
60pub enum NativeEncodingPolicy {
61    /// Preserve bytes for retained `RStr` values without assuming an encoding;
62    /// conversion later rejects non-ASCII native strings in format 2 when no
63    /// header encoding is available. `SYMSXP` print names are decoded during
64    /// parsing.
65    #[default]
66    RejectUnknown,
67    /// Treat native strings as UTF-8 in format 2 when the header has no
68    /// encoding, for callers with an external UTF-8 contract. Conversion later
69    /// validates retained `RStr` bytes without lossy replacement; `SYMSXP`
70    /// print names are decoded during parsing.
71    AssumeUtf8,
72}
73
74/// Parses a decompressed XDR stream with explicit options.
75pub fn parse_with_options(bytes: &[u8], options: ParseOptions) -> Result<RObject, Error> {
76    let mut cursor = ByteCursor::new(bytes);
77    let header = Header::parse(&mut cursor)?;
78    Decoder::new(
79        options.limits_value(),
80        header.native_encoding,
81        options.native_encoding_policy_value(),
82    )
83    .decode_root(&mut cursor)
84}
85
86/// Traversal mode for the core decoder.
87///
88/// `Strict` is the public entry point's behavior: any SEXP type outside the
89/// modeled [`RValue`] set is rejected. `Discard` is used only while walking
90/// the item fields of a non-singleton environment (`enclos`/`frame`/
91/// `hashtab`/`attrib`): it performs the same structural walk (with the same
92/// reference-table side effects) but additionally tolerates SEXP types whose
93/// layout is verified but not otherwise modeled, discarding their decoded
94/// value.
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96enum Mode {
97    Strict,
98    Discard,
99}
100
101struct Decoder {
102    state: WireState,
103}
104
105impl Decoder {
106    fn new(
107        limits: Limits,
108        native_encoding: Option<String>,
109        native_encoding_policy: NativeEncodingPolicy,
110    ) -> Self {
111        Self {
112            state: WireState::new(limits, native_encoding, native_encoding_policy),
113        }
114    }
115
116    fn decode_root(&mut self, cursor: &mut ByteCursor<'_>) -> Result<RObject, Error> {
117        self.decode_object(cursor, 0, Mode::Strict)
118    }
119
120    fn decode_object(
121        &mut self,
122        cursor: &mut ByteCursor<'_>,
123        depth: u32,
124        mode: Mode,
125    ) -> Result<RObject, Error> {
126        self.check_depth(depth)?;
127        let flags = self.read_flags(cursor)?;
128        self.decode_object_with_flags(cursor, flags, depth, mode)
129    }
130
131    fn decode_object_with_flags(
132        &mut self,
133        cursor: &mut ByteCursor<'_>,
134        flags: ItemFlags,
135        depth: u32,
136        mode: Mode,
137    ) -> Result<RObject, Error> {
138        // Types with wire layouts that don't fit the generic
139        // "value, then optionally-gated attributes" shape below (bare
140        // singleton tags, or an environment whose attrib field is
141        // unconditional rather than flag-gated) are dispatched here and
142        // return directly, regardless of mode.
143        match flags.type_code() {
144            REFSXP => return self.decode_ref(cursor, flags),
145            ENVSXP => return self.decode_env(cursor, depth),
146            GLOBALENV_SXP => return Ok(env_object(EnvHandle::Global)),
147            BASEENV_SXP | BASENAMESPACE_SXP => return Ok(env_object(EnvHandle::Base)),
148            EMPTYENV_SXP => return Ok(env_object(EnvHandle::Empty)),
149            _ => {}
150        }
151
152        let value = match flags.type_code() {
153            NILSXP | NILVALUE_SXP => RValue::Null,
154            CHARSXP => RValue::Character(vec![self.decode_char_with_flags(cursor, flags)?]),
155            STRSXP => RValue::Character(self.decode_character_vector(cursor)?),
156            LGLSXP => RValue::Logical(self.decode_logical_vector(cursor)?),
157            INTSXP => RValue::Integer(self.decode_integer_vector(cursor)?),
158            REALSXP => RValue::Real(self.decode_real_vector(cursor)?),
159            VECSXP => RValue::List(self.decode_list(cursor, depth, mode)?),
160            SYMSXP => RValue::Symbol(self.decode_symbol_with_flags(cursor, flags)?),
161            PERSISTSXP => RValue::Persisted(self.decode_persisted(cursor)?),
162            PACKAGESXP | NAMESPACESXP => {
163                RValue::Environment(self.decode_package_or_namespace(cursor)?)
164            }
165            other => {
166                if mode == Mode::Discard {
167                    return self.decode_discard(cursor, flags, depth);
168                }
169                return Err(Error::UnsupportedSexp {
170                    kind: SexpKind::from_type_code(other),
171                    type_code: other,
172                    offset: cursor.position().saturating_sub(4),
173                });
174            }
175        };
176
177        let attributes = if flags.has_attributes() {
178            self.decode_attributes(cursor, depth + 1, mode)?
179        } else {
180            Attributes::default()
181        };
182
183        Ok(RObject::from_parts(value, attributes))
184    }
185
186    /// Decodes a non-singleton `ENVSXP`: `locked` (raw i32, registered
187    /// immediately after), then `enclos`/`frame`/`hashtab`/`attrib`, all
188    /// unconditionally present and all decoded (and discarded) in
189    /// [`Mode::Discard`]. Environments are opaque by design: only their
190    /// wire bytes and reference-table side effects matter.
191    fn decode_env(&mut self, cursor: &mut ByteCursor<'_>, depth: u32) -> Result<RObject, Error> {
192        let _locked = cursor.read_be_i32()?;
193        self.state
194            .register(RefEntry::Env(EnvHandle::Other), cursor.position())?;
195        for _ in 0..4 {
196            let _ = self.decode_object(cursor, depth + 1, Mode::Discard)?;
197        }
198        Ok(env_object(EnvHandle::Other))
199    }
200
201    /// Decodes the shared `PACKAGESXP`/`NAMESPACESXP` payload: the same
202    /// "string vec" format used by `PERSISTSXP`, registered in the
203    /// reference table after the payload is read.
204    fn decode_package_or_namespace(
205        &mut self,
206        cursor: &mut ByteCursor<'_>,
207    ) -> Result<EnvHandle, Error> {
208        let _ = self.decode_string_vec(cursor)?;
209        self.state
210            .register(RefEntry::Env(EnvHandle::Other), cursor.position())?;
211        Ok(EnvHandle::Other)
212    }
213
214    /// Handles the SEXP types that are only tolerated in [`Mode::Discard`]:
215    /// their wire layout is verified but they have no [`RValue`]
216    /// representation, so the decoded value is always discarded in favor of
217    /// [`RValue::Null`]. Types whose layout is not verified still fail with
218    /// [`Error::UnsupportedSexp`].
219    fn decode_discard(
220        &mut self,
221        cursor: &mut ByteCursor<'_>,
222        flags: ItemFlags,
223        depth: u32,
224    ) -> Result<RObject, Error> {
225        if is_dotted_pair(flags) {
226            // Dotted pairs handle their own (optional) attributes and tag
227            // internally, so they never fall through to the generic
228            // trailing-attributes handling below.
229            self.discard_pairlist_chain(cursor, flags, depth)?;
230            return Ok(RObject::from_parts(RValue::Null, Attributes::default()));
231        }
232
233        match flags.type_code() {
234            UNBOUNDVALUE_SXP | MISSINGARG_SXP => {
235                return Ok(RObject::from_parts(RValue::Null, Attributes::default()));
236            }
237            SPECIALSXP | BUILTINSXP => {
238                let len = self.read_vector_len(cursor)?;
239                let _ = cursor.read_exact(len)?;
240            }
241            CPLXSXP => {
242                let len = self.read_vector_len(cursor)?;
243                for _ in 0..len {
244                    let _ = cursor.read_exact(16)?;
245                }
246            }
247            RAWSXP => {
248                let len = self.read_vector_len(cursor)?;
249                let _ = cursor.read_exact(len)?;
250            }
251            EXPRSXP => {
252                // Same framing as VECSXP: a length followed by that many items.
253                let _ = self.decode_list(cursor, depth, Mode::Discard)?;
254            }
255            S4SXP => {
256                // No body content beyond the generic trailing attributes.
257            }
258            other => {
259                return Err(Error::UnsupportedSexp {
260                    kind: SexpKind::from_type_code(other),
261                    type_code: other,
262                    offset: cursor.position().saturating_sub(4),
263                });
264            }
265        }
266
267        let attributes = if flags.has_attributes() {
268            self.decode_attributes(cursor, depth + 1, Mode::Discard)?
269        } else {
270            Attributes::default()
271        };
272
273        Ok(RObject::from_parts(RValue::Null, attributes))
274    }
275
276    /// Discards a dotted-pair chain (`LISTSXP`/`LANGSXP`/`CLOSXP`/
277    /// `PROMSXP`/`DOTSXP`) iteratively over the CDR links, so long
278    /// pairlists don't add stack depth. Each link decodes an optional
279    /// attributes item, an optional tag item, and the CAR, all generically
280    /// in [`Mode::Discard`]; the CDR either continues the loop (another
281    /// dotted-pair link), stops (NIL), or is decoded once more as an
282    /// improper-list tail.
283    fn discard_pairlist_chain(
284        &mut self,
285        cursor: &mut ByteCursor<'_>,
286        flags: ItemFlags,
287        depth: u32,
288    ) -> Result<(), Error> {
289        let mut flags = flags;
290        loop {
291            self.account_elements(1, cursor.position())?;
292            if flags.has_attributes() {
293                let _ = self.decode_attributes(cursor, depth + 1, Mode::Discard)?;
294            }
295            if flags.has_tag() {
296                let _ = self.decode_object(cursor, depth + 1, Mode::Discard)?;
297            }
298            let _ = self.decode_object(cursor, depth + 1, Mode::Discard)?;
299
300            let cdr_flags = self.read_flags(cursor)?;
301            if is_dotted_pair(cdr_flags) {
302                flags = cdr_flags;
303                continue;
304            }
305            if is_nil(cdr_flags) {
306                return Ok(());
307            }
308            let _ = self.decode_object_with_flags(cursor, cdr_flags, depth + 1, Mode::Discard)?;
309            return Ok(());
310        }
311    }
312
313    fn read_flags(&mut self, cursor: &mut ByteCursor<'_>) -> Result<ItemFlags, Error> {
314        self.state.read_flags(cursor)
315    }
316
317    fn decode_ref(
318        &mut self,
319        cursor: &mut ByteCursor<'_>,
320        flags: ItemFlags,
321    ) -> Result<RObject, Error> {
322        let index = self.state.read_ref_index(cursor, flags)?;
323
324        match self
325            .state
326            .resolve(index, cursor.position().saturating_sub(4))?
327        {
328            RefEntry::Symbol(symbol) => Ok(RObject::from_parts(
329                RValue::Symbol(symbol.clone()),
330                Attributes::default(),
331            )),
332            RefEntry::Persisted(persisted) => Ok(RObject::from_parts(
333                RValue::Persisted(persisted.clone()),
334                Attributes::default(),
335            )),
336            RefEntry::Env(handle) => Ok(env_object(*handle)),
337        }
338    }
339
340    fn decode_list(
341        &mut self,
342        cursor: &mut ByteCursor<'_>,
343        depth: u32,
344        mode: Mode,
345    ) -> Result<Vec<RObject>, Error> {
346        let len = self.read_vector_len(cursor)?;
347        (0..len)
348            .map(|_| self.decode_object(cursor, depth + 1, mode))
349            .collect()
350    }
351
352    fn decode_logical_vector(
353        &mut self,
354        cursor: &mut ByteCursor<'_>,
355    ) -> Result<Vec<Option<bool>>, Error> {
356        let len = self.read_vector_len(cursor)?;
357        (0..len)
358            .map(|_| {
359                Ok(match cursor.read_be_i32()? {
360                    NA_INTEGER => None,
361                    0 => Some(false),
362                    _ => Some(true),
363                })
364            })
365            .collect()
366    }
367
368    fn decode_integer_vector(
369        &mut self,
370        cursor: &mut ByteCursor<'_>,
371    ) -> Result<Vec<Option<i32>>, Error> {
372        let len = self.read_vector_len(cursor)?;
373        (0..len)
374            .map(|_| {
375                let value = cursor.read_be_i32()?;
376                Ok((value != NA_INTEGER).then_some(value))
377            })
378            .collect()
379    }
380
381    fn decode_real_vector(
382        &mut self,
383        cursor: &mut ByteCursor<'_>,
384    ) -> Result<Vec<Option<f64>>, Error> {
385        let len = self.read_vector_len(cursor)?;
386        (0..len)
387            .map(|_| {
388                let bits = cursor.read_be_u64()?;
389                Ok((bits != NA_REAL_BITS).then_some(f64::from_bits(bits)))
390            })
391            .collect()
392    }
393
394    fn decode_character_vector(&mut self, cursor: &mut ByteCursor<'_>) -> Result<Vec<RStr>, Error> {
395        let len = self.read_vector_len(cursor)?;
396        (0..len).map(|_| self.decode_char_item(cursor)).collect()
397    }
398
399    fn decode_char_item(&mut self, cursor: &mut ByteCursor<'_>) -> Result<RStr, Error> {
400        self.state.decode_char_item(cursor)
401    }
402
403    fn decode_char_with_flags(
404        &mut self,
405        cursor: &mut ByteCursor<'_>,
406        flags: ItemFlags,
407    ) -> Result<RStr, Error> {
408        self.state.decode_char_with_flags(cursor, flags)
409    }
410
411    fn decode_symbol_with_flags(
412        &mut self,
413        cursor: &mut ByteCursor<'_>,
414        _flags: ItemFlags,
415    ) -> Result<Symbol, Error> {
416        self.state.decode_symbol(cursor)
417    }
418
419    fn decode_persisted(&mut self, cursor: &mut ByteCursor<'_>) -> Result<Persisted, Error> {
420        let values = self.decode_string_vec(cursor)?;
421        let persisted = Persisted::new(values);
422        self.state
423            .register(RefEntry::Persisted(persisted.clone()), cursor.position())?;
424        Ok(persisted)
425    }
426
427    /// Decodes the "string vec" payload shared by `PERSISTSXP` and
428    /// `PACKAGESXP`/`NAMESPACESXP`: a discarded i32 placeholder, then an i32
429    /// count (with the usual -1 long-vector escape), then that many
430    /// `CHARSXP` items.
431    fn decode_string_vec(&mut self, cursor: &mut ByteCursor<'_>) -> Result<Vec<RStr>, Error> {
432        let len = self.state.read_string_vec_len(cursor)?;
433        (0..len)
434            .map(|_| self.decode_char_item(cursor))
435            .collect::<Result<Vec<_>, _>>()
436    }
437
438    fn decode_attributes(
439        &mut self,
440        cursor: &mut ByteCursor<'_>,
441        depth: u32,
442        mode: Mode,
443    ) -> Result<Attributes, Error> {
444        self.check_depth(depth)?;
445        let flags = self.read_flags(cursor)?;
446        if is_nil(flags) {
447            return Ok(Attributes::default());
448        }
449        let attributes = self.decode_attribute_pairlist_with_flags(cursor, flags, depth, mode)?;
450        Ok(Attributes::new(attributes))
451    }
452
453    fn decode_attribute_pairlist_with_flags(
454        &mut self,
455        cursor: &mut ByteCursor<'_>,
456        flags: ItemFlags,
457        depth: u32,
458        mode: Mode,
459    ) -> Result<Vec<Attribute>, Error> {
460        // The cell's flags were consumed by the caller immediately before
461        // this call; keep their offset so tag errors point at the offending
462        // pairlist cell even after nested attributes advance the cursor.
463        let flags_offset = cursor.position().saturating_sub(4);
464        if flags.type_code() != LISTSXP {
465            return Err(Error::UnsupportedSexp {
466                kind: flags.kind(),
467                type_code: flags.type_code(),
468                offset: flags_offset,
469            });
470        }
471
472        self.account_elements(1, cursor.position())?;
473
474        if flags.has_attributes() {
475            let _ = self.decode_attributes(cursor, depth + 1, mode)?;
476        }
477
478        let name = if flags.has_tag() {
479            self.decode_attribute_tag(cursor, depth + 1)?
480        } else {
481            return Err(Error::InvalidAttributeTag {
482                offset: flags_offset,
483            });
484        };
485
486        let value = self.decode_object(cursor, depth + 1, mode)?;
487        let cdr_flags = self.read_flags(cursor)?;
488        let mut attributes = vec![Attribute::new(name, value)];
489
490        if !is_nil(cdr_flags) {
491            attributes.extend(self.decode_attribute_pairlist_with_flags(
492                cursor,
493                cdr_flags,
494                depth + 1,
495                mode,
496            )?);
497        }
498
499        Ok(attributes)
500    }
501
502    fn decode_attribute_tag(
503        &mut self,
504        cursor: &mut ByteCursor<'_>,
505        depth: u32,
506    ) -> Result<Symbol, Error> {
507        self.check_depth(depth)?;
508        let flags = self.read_flags(cursor)?;
509        match flags.type_code() {
510            SYMSXP => self.decode_symbol_with_flags(cursor, flags),
511            REFSXP => {
512                let index = self.state.read_ref_index(cursor, flags)?;
513                match self
514                    .state
515                    .resolve(index, cursor.position().saturating_sub(4))?
516                {
517                    RefEntry::Symbol(symbol) => Ok(symbol.clone()),
518                    RefEntry::Persisted(_) | RefEntry::Env(_) => Err(Error::InvalidAttributeTag {
519                        offset: cursor.position().saturating_sub(4),
520                    }),
521                }
522            }
523            _ => Err(Error::InvalidAttributeTag {
524                offset: cursor.position().saturating_sub(4),
525            }),
526        }
527    }
528
529    fn read_vector_len(&mut self, cursor: &mut ByteCursor<'_>) -> Result<usize, Error> {
530        self.state.read_vector_len(cursor)
531    }
532
533    fn check_depth(&self, depth: u32) -> Result<(), Error> {
534        self.state.check_depth(depth)
535    }
536
537    fn account_elements(&mut self, count: usize, offset: usize) -> Result<(), Error> {
538        self.state.account_elements(count, offset)
539    }
540}
541
542/// Decodes a `CHARSXP` encoding from the `levels` mask bits per R's
543/// `InCharSXP`: UTF-8 (bit 3) takes priority, then Latin-1 (bit 2), then
544/// bytes (bit 1), else native. The ASCII marker (bit 6) is a non-exclusive
545/// hint, not a distinct encoding, so it naturally falls through to Native.
546fn env_object(handle: EnvHandle) -> RObject {
547    RObject::from_parts(RValue::Environment(handle), Attributes::default())
548}
549
550#[cfg(test)]
551mod tests {
552    use super::*;
553    use crate::REncoding;
554    use crate::wire::{ATTRIBUTES_BIT, CLOSXP, EXTPTRSXP, TAG_BIT};
555    use std::{fs, io::Read, path::PathBuf};
556
557    use flate2::read::GzDecoder;
558
559    fn item(bytes: &[u8]) -> Result<RObject, Error> {
560        item_with_limits(bytes, Limits::default())
561    }
562
563    fn item_with_limits(bytes: &[u8], limits: Limits) -> Result<RObject, Error> {
564        let mut cursor = ByteCursor::new(bytes);
565        Decoder::new(limits, None, NativeEncodingPolicy::RejectUnknown).decode_root(&mut cursor)
566    }
567
568    fn fixture_dir() -> PathBuf {
569        PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/data")
570    }
571
572    fn fixture(name: &str) -> RObject {
573        let bytes = fs::read(fixture_dir().join(name)).expect("fixture bytes");
574        let mut decoder = GzDecoder::new(bytes.as_slice());
575        let mut decompressed = Vec::new();
576        decoder
577            .read_to_end(&mut decompressed)
578            .expect("fixture gzip stream");
579        parse(&decompressed).expect(name)
580    }
581
582    fn rstr(value: &RStr) -> String {
583        value.as_str().unwrap().unwrap().into_owned()
584    }
585
586    fn strings(value: &RObject) -> Vec<String> {
587        let RValue::Character(values) = value.value() else {
588            panic!("expected character vector, got {value:?}");
589        };
590        values.iter().map(rstr).collect()
591    }
592
593    fn list(value: &RObject) -> &[RObject] {
594        let RValue::List(values) = value.value() else {
595            panic!("expected list, got {value:?}");
596        };
597        values
598    }
599
600    fn persisted(value: &RObject) -> &Persisted {
601        let RValue::Persisted(value) = value.value() else {
602            panic!("expected persisted value, got {value:?}");
603        };
604        value
605    }
606
607    fn env_handle(value: &RObject) -> EnvHandle {
608        let RValue::Environment(handle) = value.value() else {
609            panic!("expected environment, got {value:?}");
610        };
611        *handle
612    }
613
614    fn symbol_name(value: &RObject) -> &str {
615        let RValue::Symbol(symbol) = value.value() else {
616            panic!("expected symbol, got {value:?}");
617        };
618        symbol.as_str()
619    }
620
621    #[test]
622    fn decodes_flags_word() {
623        let flags = ItemFlags::from_raw(0x0004_0713);
624        assert_eq!(flags.type_code(), VECSXP);
625        assert!(flags.is_object());
626        assert!(flags.has_attributes());
627        assert!(flags.has_tag());
628        assert_eq!(flags.levels(), 0x40);
629
630        let ref_flags = ItemFlags::from_raw(0x0000_05ff);
631        assert_eq!(ref_flags.type_code(), REFSXP);
632        assert_eq!(ref_flags.ref_index_inline(), 5);
633    }
634
635    #[test]
636    fn decodes_na_string_integer_and_logical() {
637        let charsxp_na = [0, 0, 0, CHARSXP, 0xff, 0xff, 0xff, 0xff];
638        let value = item(&charsxp_na).unwrap();
639        assert_eq!(value.value(), &RValue::Character(vec![RStr::Na]));
640
641        let int_vec = [0, 0, 0, INTSXP, 0, 0, 0, 1, 0x80, 0, 0, 0];
642        let value = item(&int_vec).unwrap();
643        assert_eq!(value.value(), &RValue::Integer(vec![None]));
644
645        let logical_vec = [0, 0, 0, LGLSXP, 0, 0, 0, 2, 0x80, 0, 0, 0, 0, 0, 0, 1];
646        let value = item(&logical_vec).unwrap();
647        assert_eq!(value.value(), &RValue::Logical(vec![None, Some(true)]));
648    }
649
650    #[test]
651    fn charsxp_encoding_levels_bits() {
652        // UTF-8 levels bit (1 << 3) takes priority.
653        let flags: u32 = 9 | (8 << 12);
654        let mut bytes = flags.to_be_bytes().to_vec();
655        bytes.extend_from_slice(&1i32.to_be_bytes());
656        bytes.push(b'a');
657        let value = item(&bytes).unwrap();
658        let RValue::Character(strs) = value.value() else {
659            panic!("expected character vector, got {value:?}");
660        };
661        assert_eq!(strs[0].encoding(), Some(REncoding::Utf8));
662
663        // The ASCII marker bit (1 << 6) is a non-exclusive hint, not a
664        // distinct encoding, so it falls through to Native.
665        let flags: u32 = 9 | (64 << 12);
666        let mut bytes = flags.to_be_bytes().to_vec();
667        bytes.extend_from_slice(&1i32.to_be_bytes());
668        bytes.push(b'a');
669        let value = item(&bytes).unwrap();
670        let RValue::Character(strs) = value.value() else {
671            panic!("expected character vector, got {value:?}");
672        };
673        assert_eq!(strs[0].encoding(), Some(REncoding::Native));
674        assert_eq!(strs[0].as_str().unwrap().unwrap().as_ref(), "a");
675    }
676
677    #[test]
678    fn native_symbol_names_are_decoded_during_format_v2_parsing() {
679        // Handwritten because R cannot easily serialize a non-ASCII Native
680        // symbol deterministically for a fixture.
681        fn stream(print_name: &[u8]) -> Vec<u8> {
682            let mut bytes = vec![b'X', b'\n', 0, 0, 0, 2, 0, 4, 6, 1, 0, 3, 5, 0];
683            bytes.extend_from_slice(&u32::from(SYMSXP).to_be_bytes());
684            bytes.extend_from_slice(&u32::from(CHARSXP).to_be_bytes());
685            bytes.extend_from_slice(&(print_name.len() as i32).to_be_bytes());
686            bytes.extend_from_slice(print_name);
687            bytes
688        }
689
690        let valid_utf8 = stream("é".as_bytes());
691        assert_eq!(parse(&valid_utf8), Err(Error::InvalidSymbolName));
692
693        let symbol = parse_with_options(
694            &valid_utf8,
695            ParseOptions::default().native_encoding_policy(NativeEncodingPolicy::AssumeUtf8),
696        )
697        .expect("AssumeUtf8 should decode a valid Native symbol name");
698        assert_eq!(symbol_name(&symbol), "é");
699
700        let invalid_utf8 = stream(&[0xff]);
701        assert_eq!(
702            parse_with_options(
703                &invalid_utf8,
704                ParseOptions::default().native_encoding_policy(NativeEncodingPolicy::AssumeUtf8),
705            ),
706            Err(Error::InvalidSymbolName)
707        );
708    }
709
710    #[test]
711    fn untagged_attribute_cell_with_nested_attributes_reports_cell_offset() {
712        let mut bytes = Vec::new();
713        // Root: logical vector carrying an attribute pairlist.
714        bytes.extend_from_slice(&(u32::from(LGLSXP) | ATTRIBUTES_BIT).to_be_bytes());
715        bytes.extend_from_slice(&1i32.to_be_bytes());
716        bytes.extend_from_slice(&1i32.to_be_bytes());
717        let cell_flags_offset = bytes.len();
718        // Attribute cell with nested attributes but no tag: decoding the
719        // nested attributes advances the cursor well past the cell's flags.
720        bytes.extend_from_slice(&(u32::from(LISTSXP) | ATTRIBUTES_BIT).to_be_bytes());
721        bytes.extend_from_slice(&(u32::from(LISTSXP) | TAG_BIT).to_be_bytes());
722        bytes.extend_from_slice(&u32::from(SYMSXP).to_be_bytes());
723        bytes.extend_from_slice(&(u32::from(CHARSXP) | (8 << 12)).to_be_bytes());
724        bytes.extend_from_slice(&1i32.to_be_bytes());
725        bytes.push(b'x');
726        bytes.extend_from_slice(&u32::from(NILVALUE_SXP).to_be_bytes());
727        bytes.extend_from_slice(&u32::from(NILVALUE_SXP).to_be_bytes());
728
729        let err = item(&bytes).unwrap_err();
730        assert!(
731            matches!(err, Error::InvalidAttributeTag { offset } if offset == cell_flags_offset),
732            "expected InvalidAttributeTag at {cell_flags_offset}, got {err:?}"
733        );
734    }
735
736    #[test]
737    fn singleton_env_byte_level_decoding() {
738        for (byte, expected) in [
739            (253u8, EnvHandle::Global),
740            (241u8, EnvHandle::Base),
741            (242u8, EnvHandle::Empty),
742            (250u8, EnvHandle::Base),
743        ] {
744            let bytes = [0, 0, 0, byte];
745            let mut cursor = ByteCursor::new(&bytes);
746            let value = Decoder::new(Limits::default(), None, NativeEncodingPolicy::RejectUnknown)
747                .decode_root(&mut cursor)
748                .unwrap();
749            assert_eq!(value.value(), &RValue::Environment(expected));
750            assert_eq!(cursor.remaining(), 0);
751        }
752    }
753
754    #[test]
755    fn environment_frame_pairlist_cells_count_toward_total_elements_limit() {
756        let mut bytes = Vec::new();
757        bytes.extend_from_slice(&u32::from(ENVSXP).to_be_bytes());
758        bytes.extend_from_slice(&0i32.to_be_bytes());
759        bytes.extend_from_slice(&u32::from(0u8).to_be_bytes()); // enclos
760
761        bytes.extend_from_slice(&u32::from(LISTSXP).to_be_bytes());
762        for index in 0..3 {
763            bytes.extend_from_slice(&u32::from(0u8).to_be_bytes()); // CAR
764            let cdr = if index == 2 { 0 } else { u32::from(LISTSXP) };
765            bytes.extend_from_slice(&cdr.to_be_bytes());
766        }
767        bytes.extend_from_slice(&u32::from(0u8).to_be_bytes()); // hashtab
768        bytes.extend_from_slice(&u32::from(0u8).to_be_bytes()); // attrib
769
770        let error = item_with_limits(&bytes, Limits::default().max_total_elements(2))
771            .expect_err("frame pairlist should exceed the element limit");
772        assert!(matches!(
773            error,
774            Error::TotalElementsLimitExceeded { limit: 2, .. }
775        ));
776    }
777
778    #[test]
779    fn compliant_environment_frame_pairlist_decodes_to_other() {
780        let mut bytes = Vec::new();
781        bytes.extend_from_slice(&u32::from(ENVSXP).to_be_bytes());
782        bytes.extend_from_slice(&0i32.to_be_bytes());
783        bytes.extend_from_slice(&u32::from(0u8).to_be_bytes()); // enclos
784        bytes.extend_from_slice(&u32::from(LISTSXP).to_be_bytes());
785        bytes.extend_from_slice(&u32::from(0u8).to_be_bytes()); // CAR
786        bytes.extend_from_slice(&u32::from(0u8).to_be_bytes()); // CDR
787        bytes.extend_from_slice(&u32::from(0u8).to_be_bytes()); // hashtab
788        bytes.extend_from_slice(&u32::from(0u8).to_be_bytes()); // attrib
789
790        let value = item_with_limits(&bytes, Limits::default().max_total_elements(1))
791            .expect("compliant environment should decode");
792        assert_eq!(env_handle(&value), EnvHandle::Other);
793    }
794
795    #[test]
796    fn refsxp_out_of_range_index_is_reported() {
797        let err = item(&[0, 0, 1, REFSXP]).unwrap_err();
798        assert_eq!(
799            err,
800            Error::RefIndexOutOfRange {
801                index: 1,
802                len: 0,
803                offset: 0
804            }
805        );
806    }
807
808    #[test]
809    fn reference_limit_is_enforced_before_symbol_registration() {
810        let bytes = [
811            0, 0, 0, SYMSXP, // symbol
812            0, 0, 0, CHARSXP, // print name
813            0, 0, 0, 1, b'a',
814        ];
815        let error = item_with_limits(&bytes, Limits::default().max_references(0))
816            .expect_err("zero reference limit should reject the symbol");
817        assert!(matches!(
818            error,
819            Error::ReferenceLimitExceeded { limit: 0, .. }
820        ));
821    }
822
823    #[test]
824    fn persistsxp_long_vector_escape_is_reported() {
825        let err = item(&[
826            0, 0, 0, PERSISTSXP, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 1, 0, 0, 0, 2,
827        ])
828        .unwrap_err();
829        assert_eq!(
830            err,
831            Error::PersistedLongVectorUnsupported {
832                len: 0x1_0000_0002,
833                offset: 8
834            }
835        );
836    }
837
838    #[test]
839    fn unsupported_type_is_reported() {
840        let err = item(&[0, 0, 0, EXTPTRSXP]).unwrap_err();
841        assert_eq!(
842            err,
843            Error::UnsupportedSexp {
844                kind: SexpKind::ExtPtr,
845                type_code: EXTPTRSXP,
846                offset: 0
847            }
848        );
849    }
850
851    #[test]
852    fn strict_mode_rejects_dotted_pair_at_top_level() {
853        let err = item(&[0, 0, 0, CLOSXP]).unwrap_err();
854        assert_eq!(
855            err,
856            Error::UnsupportedSexp {
857                kind: SexpKind::Closure,
858                type_code: CLOSXP,
859                offset: 0
860            }
861        );
862    }
863
864    #[test]
865    fn decodes_aliases_vector_fixtures() {
866        for version in [2, 3] {
867            let root = fixture(&format!("aliases_vector_v{version}.rds"));
868            assert_eq!(
869                strings(&root),
870                vec![
871                    "minimal",
872                    "multialias",
873                    "multialias",
874                    "multialias",
875                    "multialias"
876                ]
877            );
878            assert_eq!(
879                root.names().unwrap().iter().map(rstr).collect::<Vec<_>>(),
880                vec![
881                    "minimal",
882                    "multialias",
883                    "multialias-method",
884                    "multialias.default",
885                    "print.multialias"
886                ]
887            );
888        }
889    }
890
891    #[test]
892    fn decodes_shared_symbols_fixtures() {
893        for version in [2, 3] {
894            let root = fixture(&format!("shared_symbols_v{version}.rds"));
895            assert_eq!(
896                root.names().unwrap().iter().map(rstr).collect::<Vec<_>>(),
897                vec!["a", "b", "d"]
898            );
899            let items = list(&root);
900            assert_eq!(items.len(), 3);
901
902            assert_eq!(
903                items[0]
904                    .class()
905                    .unwrap()
906                    .iter()
907                    .map(rstr)
908                    .collect::<Vec<_>>(),
909                vec!["widget"]
910            );
911            assert_eq!(
912                strings(items[0].attributes().get("note").unwrap()),
913                vec!["first"]
914            );
915
916            let b_items = list(&items[1]);
917            assert_eq!(
918                items[1]
919                    .class()
920                    .unwrap()
921                    .iter()
922                    .map(rstr)
923                    .collect::<Vec<_>>(),
924                vec!["widget"]
925            );
926            assert_eq!(
927                strings(items[1].attributes().get("note").unwrap()),
928                vec!["third"]
929            );
930            assert_eq!(
931                items[1]
932                    .names()
933                    .unwrap()
934                    .iter()
935                    .map(rstr)
936                    .collect::<Vec<_>>(),
937                vec!["c"]
938            );
939            assert_eq!(
940                b_items[0]
941                    .class()
942                    .unwrap()
943                    .iter()
944                    .map(rstr)
945                    .collect::<Vec<_>>(),
946                vec!["widget"]
947            );
948            assert_eq!(
949                strings(b_items[0].attributes().get("note").unwrap()),
950                vec!["second"]
951            );
952
953            assert_eq!(strings(&items[2]), vec!["x"]);
954            assert_eq!(
955                items[2]
956                    .class()
957                    .unwrap()
958                    .iter()
959                    .map(rstr)
960                    .collect::<Vec<_>>(),
961                vec!["widget"]
962            );
963            assert_eq!(
964                strings(items[2].attributes().get("note").unwrap()),
965                vec!["fourth"]
966            );
967        }
968    }
969
970    #[test]
971    fn decodes_persistsxp_basic_fixtures() {
972        for version in [2, 3] {
973            let root = fixture(&format!("persistsxp_basic_v{version}.rds"));
974            assert_eq!(
975                root.names().unwrap().iter().map(rstr).collect::<Vec<_>>(),
976                vec!["env", "tail"]
977            );
978            let items = list(&root);
979            assert_eq!(
980                persisted(&items[0])
981                    .as_slice()
982                    .iter()
983                    .map(rstr)
984                    .collect::<Vec<_>>(),
985                vec!["srcref-env"]
986            );
987            assert_eq!(strings(&items[1]), vec!["tail-marker"]);
988        }
989    }
990
991    #[test]
992    fn decodes_persistsxp_twice_fixtures() {
993        for version in [2, 3] {
994            let root = fixture(&format!("persistsxp_twice_v{version}.rds"));
995            assert_eq!(
996                root.names().unwrap().iter().map(rstr).collect::<Vec<_>>(),
997                vec!["first", "second", "after"]
998            );
999            let items = list(&root);
1000            let first = persisted(&items[0]);
1001            let second = persisted(&items[1]);
1002            assert_eq!(
1003                first.as_slice().iter().map(rstr).collect::<Vec<_>>(),
1004                vec!["srcref-env"]
1005            );
1006            assert_eq!(
1007                second.as_slice().iter().map(rstr).collect::<Vec<_>>(),
1008                vec!["srcref-env"]
1009            );
1010            assert!(!first.ptr_eq(second));
1011            assert_eq!(strings(&items[2]), vec!["tail-marker"]);
1012        }
1013    }
1014
1015    #[test]
1016    fn decodes_persistsxp_multi_fixtures() {
1017        for version in [2, 3] {
1018            let root = fixture(&format!("persistsxp_multi_v{version}.rds"));
1019            let items = list(&root);
1020            assert_eq!(
1021                persisted(&items[0])
1022                    .as_slice()
1023                    .iter()
1024                    .map(rstr)
1025                    .collect::<Vec<_>>(),
1026                vec!["a", "b", "c"]
1027            );
1028            assert_eq!(strings(&items[1]), vec!["tail-marker"]);
1029        }
1030    }
1031
1032    #[test]
1033    fn decodes_singleton_envs_fixtures() {
1034        for version in [2, 3] {
1035            let root = fixture(&format!("singleton_envs_v{version}.rds"));
1036            assert_eq!(
1037                root.names().unwrap().iter().map(rstr).collect::<Vec<_>>(),
1038                vec!["global", "base", "empty"]
1039            );
1040            let items = list(&root);
1041            assert_eq!(env_handle(&items[0]), EnvHandle::Global);
1042            assert_eq!(env_handle(&items[1]), EnvHandle::Base);
1043            assert_eq!(env_handle(&items[2]), EnvHandle::Empty);
1044        }
1045    }
1046
1047    #[test]
1048    fn decodes_plain_env_fixtures() {
1049        for version in [2, 3] {
1050            let root = fixture(&format!("plain_env_v{version}.rds"));
1051            assert_eq!(env_handle(&root), EnvHandle::Other);
1052            assert!(root.attributes().is_empty());
1053        }
1054    }
1055
1056    #[test]
1057    fn decodes_env_with_closure_fixtures() {
1058        for version in [2, 3] {
1059            let root = fixture(&format!("env_with_closure_v{version}.rds"));
1060            assert_eq!(
1061                root.names().unwrap().iter().map(rstr).collect::<Vec<_>>(),
1062                vec!["env", "tail"]
1063            );
1064            let items = list(&root);
1065            assert_eq!(items[0].value(), &RValue::Environment(EnvHandle::Other));
1066            assert_eq!(strings(&items[1]), vec!["tail-marker"]);
1067        }
1068    }
1069
1070    #[test]
1071    fn decodes_shared_env_refs_fixtures() {
1072        for version in [2, 3] {
1073            let root = fixture(&format!("shared_env_refs_v{version}.rds"));
1074            assert_eq!(
1075                root.names().unwrap().iter().map(rstr).collect::<Vec<_>>(),
1076                vec!["vec", "sym_a", "env_first", "sym_b", "env_second"]
1077            );
1078            let items = list(&root);
1079            assert_eq!(
1080                items[0].value(),
1081                &RValue::Integer(vec![Some(4), Some(2), Some(7)])
1082            );
1083            assert_eq!(symbol_name(&items[1]), "dup_sym");
1084            assert_eq!(env_handle(&items[2]), EnvHandle::Other);
1085            assert_eq!(symbol_name(&items[3]), "dup_sym");
1086            assert_eq!(env_handle(&items[4]), EnvHandle::Other);
1087        }
1088    }
1089
1090    /// ALTREP is deliberately out of scope for this decoder: real help DBs
1091    /// never contain it in value trees, so it fails with
1092    /// `Error::UnsupportedSexp` rather than being modeled.
1093    #[test]
1094    fn altrep_is_rejected() {
1095        let bytes = fs::read(fixture_dir().join("altrep_intseq_v3.rds")).expect("fixture bytes");
1096        let mut decoder = GzDecoder::new(bytes.as_slice());
1097        let mut decompressed = Vec::new();
1098        decoder
1099            .read_to_end(&mut decompressed)
1100            .expect("fixture gzip stream");
1101        let err = parse(&decompressed).unwrap_err();
1102        assert_eq!(
1103            err,
1104            Error::UnsupportedSexp {
1105                kind: SexpKind::Other(238),
1106                type_code: 238,
1107                offset: 23
1108            }
1109        );
1110    }
1111
1112    #[test]
1113    fn decodes_namespace_refs_fixtures() {
1114        for version in [2, 3] {
1115            let root = fixture(&format!("namespace_refs_v{version}.rds"));
1116            assert_eq!(
1117                root.names().unwrap().iter().map(rstr).collect::<Vec<_>>(),
1118                vec!["ns_first", "ns_second", "tail"]
1119            );
1120            let items = list(&root);
1121            assert_eq!(items[0].value(), &RValue::Environment(EnvHandle::Other));
1122            assert_eq!(items[1].value(), &RValue::Environment(EnvHandle::Other));
1123            assert_eq!(strings(&items[2]), vec!["tail-marker"]);
1124        }
1125    }
1126
1127    #[test]
1128    fn decodes_rd_fixtures_as_rd_class_lists() {
1129        for name in ["rd_minimal", "rd_aliases", "rd_arguments", "rd_seealso"] {
1130            for version in [2, 3] {
1131                let root = fixture(&format!("{name}_v{version}.rds"));
1132                assert!(matches!(root.value(), &RValue::List(_)));
1133                assert_eq!(
1134                    root.class().unwrap().iter().map(rstr).collect::<Vec<_>>(),
1135                    vec!["Rd"]
1136                );
1137                if name == "rd_seealso" {
1138                    assert!(root.attributes().get("srcref").is_some());
1139                }
1140
1141                // parse_Rd invariant: every list node carries an "Rd_tag"
1142                // attribute (text leaves are Character vectors with Rd_tag
1143                // too). Checked robustly rather than exhaustively: at least
1144                // the first element has it, and at least one element among
1145                // the root's children has it.
1146                let items = list(&root);
1147                assert!(!items.is_empty(), "{name}_v{version}: empty root list");
1148                let mut tagged_count = 0usize;
1149                for (index, item) in items.iter().enumerate() {
1150                    if matches!(item.value(), &RValue::List(_)) {
1151                        let rd_tag = item.attributes().get("Rd_tag");
1152                        if index == 0 {
1153                            assert!(
1154                                rd_tag.is_some(),
1155                                "{name}_v{version}: first element missing Rd_tag"
1156                            );
1157                        }
1158                        if let Some(rd_tag) = rd_tag {
1159                            assert!(
1160                                matches!(rd_tag.value(), &RValue::Character(_)),
1161                                "{name}_v{version}: Rd_tag value is not a character vector"
1162                            );
1163                            tagged_count += 1;
1164                        }
1165                    }
1166                }
1167                assert!(
1168                    tagged_count >= 1,
1169                    "{name}_v{version}: no list element carries Rd_tag"
1170                );
1171            }
1172        }
1173    }
1174}