Skip to main content

rd_rds/
decode.rs

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