1use std::cell::RefCell;
8use std::collections::{HashMap, HashSet};
9
10use crate::error::PdfError;
11use crate::filters;
12use crate::lexer::{Lexer, Token, parse_object};
13use crate::objects::{PdfDict, PdfObj};
14use crate::xref::{XrefEntry, XrefTable};
15
16struct ObjStmCache {
18 data: Vec<u8>,
19 first: usize,
20 offsets: Vec<(u32, usize)>,
21}
22
23pub struct Resolver<'a> {
25 data: &'a [u8],
26 xref: XrefTable,
27 cache: RefCell<HashMap<u32, PdfObj>>,
29 resolving: RefCell<HashSet<u32>>,
31 encryption: Option<crate::crypto::EncryptionState>,
33 stream_cache: RefCell<HashMap<u32, Vec<u8>>>,
38 stream_seen: RefCell<HashSet<u32>>,
40 objstm_cache: RefCell<HashMap<u32, ObjStmCache>>,
43 scan_map: RefCell<Option<HashMap<u32, usize>>>,
46}
47
48impl<'a> Resolver<'a> {
49 pub(crate) fn new(data: &'a [u8], xref: &XrefTable) -> Self {
52 Self {
53 data,
54 xref: xref.clone(),
55 cache: RefCell::new(HashMap::new()),
56 resolving: RefCell::new(HashSet::new()),
57 encryption: None,
58 stream_cache: RefCell::new(HashMap::new()),
59 stream_seen: RefCell::new(HashSet::new()),
60 objstm_cache: RefCell::new(HashMap::new()),
61 scan_map: RefCell::new(None),
62 }
63 }
64
65 pub fn with_encryption(
67 data: &'a [u8],
68 xref: XrefTable,
69 encryption: Option<crate::crypto::EncryptionState>,
70 ) -> Self {
71 Self {
72 data,
73 xref,
74 cache: RefCell::new(HashMap::new()),
75 resolving: RefCell::new(HashSet::new()),
76 encryption,
77 stream_cache: RefCell::new(HashMap::new()),
78 stream_seen: RefCell::new(HashSet::new()),
79 objstm_cache: RefCell::new(HashMap::new()),
80 scan_map: RefCell::new(None),
81 }
82 }
83
84 pub fn preload_object_streams(&self) {
88 let mut stream_nums: HashSet<u32> = HashSet::new();
89 for entry in self.xref.entries() {
90 if let Some(XrefEntry::InStream { stream_obj_num, .. }) = entry {
91 stream_nums.insert(*stream_obj_num);
92 }
93 }
94 for &stm_num in &stream_nums {
95 let _ = self.ensure_objstm_cached(stm_num);
96 let cache = self.objstm_cache.borrow();
98 if let Some(cached) = cache.get(&stm_num) {
99 let offsets = cached.offsets.clone();
100 let first = cached.first;
101 drop(cache);
102 for &(num, off) in &offsets {
103 if !self.cache.borrow().contains_key(&num) {
104 let abs = first + off;
105 let stm = self.objstm_cache.borrow();
106 if let Some(c) = stm.get(&stm_num) {
107 if abs < c.data.len() {
108 let mut l = Lexer::new(&c.data[abs..]);
109 if let Ok(o) = parse_object(&mut l) {
110 drop(stm);
111 self.cache.borrow_mut().insert(num, o);
112 }
113 }
114 }
115 }
116 }
117 }
118 }
119 }
120
121 pub fn resolve(&self, obj_num: u32, _gen_num: u16) -> Result<PdfObj, PdfError> {
123 if let Some(obj) = self.cache.borrow().get(&obj_num) {
125 return Ok(obj.clone());
126 }
127
128 if !self.resolving.borrow_mut().insert(obj_num) {
130 return Err(PdfError::CircularReference(obj_num, _gen_num));
131 }
132
133 let result = self.resolve_uncached(obj_num, _gen_num);
134
135 self.resolving.borrow_mut().remove(&obj_num);
136
137 let obj = result?;
138 self.cache.borrow_mut().insert(obj_num, obj.clone());
139 Ok(obj)
140 }
141
142 fn resolve_uncached(&self, obj_num: u32, gen_num: u16) -> Result<PdfObj, PdfError> {
143 let entry = self
144 .xref
145 .get(obj_num)
146 .ok_or(PdfError::ObjectNotFound { obj_num, gen_num })?;
147
148 let obj = match *entry {
149 XrefEntry::InFile { offset, .. } => {
150 match self.parse_object_at(offset, Some(obj_num)) {
154 Ok(obj) => obj,
155 Err(_) => self.scan_for_object(obj_num)?,
156 }
157 }
158 XrefEntry::InStream {
159 stream_obj_num,
160 index_within,
161 } => {
162 return self.parse_object_from_stream(stream_obj_num, index_within);
164 }
165 XrefEntry::Free => return Err(PdfError::ObjectNotFound { obj_num, gen_num }),
166 };
167
168 Ok(self.decrypt_object(obj, obj_num, gen_num))
170 }
171
172 pub fn deref(&self, obj: &PdfObj) -> Result<PdfObj, PdfError> {
174 match obj {
175 PdfObj::Ref(n, g) => self.resolve(*n, *g),
176 other => Ok(other.clone()),
177 }
178 }
179
180 pub fn stream_data(&self, obj_num: u32, gen_num: u16) -> Result<Vec<u8>, PdfError> {
182 if let Some(cached) = self.stream_cache.borrow().get(&obj_num) {
184 return Ok(cached.clone());
185 }
186
187 let obj = self.resolve(obj_num, gen_num)?;
188 match obj {
189 PdfObj::Stream {
190 dict,
191 data_offset,
192 data_len,
193 } => {
194 let raw_slice = &self.data[data_offset..data_offset + data_len];
195 let raw = if let Some(ref enc) = self.encryption {
197 enc.decrypt_stream(raw_slice, obj_num, gen_num)
198 } else {
199 raw_slice.to_vec()
200 };
201 let (filter_list, parms) = filters::parse_filters(&dict, Some(self))?;
202 let result = if filter_list.is_empty() {
203 Ok(raw)
204 } else {
205 let jbig2_globals = self.resolve_jbig2_globals(&filter_list, &parms)?;
206 filters::decode_stream_bounded(
207 &raw,
208 &filter_list,
209 &parms,
210 jbig2_globals.as_deref(),
211 filters::DecodeBudget::for_stream(&dict),
212 )
213 }?;
214
215 if result.len() <= 1_048_576 {
220 self.stream_cache
221 .borrow_mut()
222 .insert(obj_num, result.clone());
223 } else if self.stream_seen.borrow().contains(&obj_num) {
224 self.stream_cache
225 .borrow_mut()
226 .insert(obj_num, result.clone());
227 } else {
228 self.stream_seen.borrow_mut().insert(obj_num);
229 }
230 Ok(result)
231 }
232 _ => Err(PdfError::Other(format!(
233 "object {obj_num} {gen_num} is not a stream"
234 ))),
235 }
236 }
237
238 pub fn raw_stream_and_filters(
241 &self,
242 obj: &PdfObj,
243 ) -> Result<(Vec<u8>, Vec<filters::Filter>), PdfError> {
244 let (obj_num, gen_num) = match obj {
245 PdfObj::Ref(n, g) => (*n, *g),
246 _ => return Err(PdfError::Other("expected Ref".into())),
247 };
248 let resolved = self.resolve(obj_num, gen_num)?;
249 match resolved {
250 PdfObj::Stream {
251 dict,
252 data_offset,
253 data_len,
254 } => {
255 let raw_slice = &self.data[data_offset..data_offset + data_len];
256 let raw = if let Some(ref enc) = self.encryption {
257 enc.decrypt_stream(raw_slice, obj_num, gen_num)
258 } else {
259 raw_slice.to_vec()
260 };
261 let (filter_list, _parms) = filters::parse_filters(&dict, Some(self))?;
262 Ok((raw, filter_list))
263 }
264 _ => Err(PdfError::Other("not a stream".into())),
265 }
266 }
267
268 pub fn stream_data_from_obj(&self, obj: &PdfObj) -> Result<Vec<u8>, PdfError> {
270 if let PdfObj::Ref(n, g) = obj {
272 return self.stream_data(*n, *g);
273 }
274 let obj = self.deref(obj)?;
275 match obj {
276 PdfObj::Stream {
277 dict,
278 data_offset,
279 data_len,
280 } => {
281 let raw = &self.data[data_offset..data_offset + data_len];
283 let (filter_list, parms) = filters::parse_filters(&dict, Some(self))?;
284 if filter_list.is_empty() {
285 Ok(raw.to_vec())
286 } else {
287 let jbig2_globals = self.resolve_jbig2_globals(&filter_list, &parms)?;
288 filters::decode_stream_bounded(
289 raw,
290 &filter_list,
291 &parms,
292 jbig2_globals.as_deref(),
293 filters::DecodeBudget::for_stream(&dict),
294 )
295 }
296 }
297 _ => Err(PdfError::Other("expected a stream object".into())),
298 }
299 }
300
301 pub fn raw_stream_bytes(&self, obj: &PdfObj) -> Option<&[u8]> {
305 let (obj_num, gen_num) = match obj {
306 PdfObj::Ref(n, g) => (*n, *g),
307 _ => return None,
308 };
309 let resolved = self.resolve(obj_num, gen_num).ok()?;
310 if let PdfObj::Stream {
311 data_offset,
312 data_len,
313 ..
314 } = resolved
315 {
316 Some(&self.data[data_offset..data_offset + data_len])
317 } else {
318 None
319 }
320 }
321
322 pub fn trailer(&self) -> &PdfDict {
324 &self.xref.trailer
325 }
326
327 pub fn data(&self) -> &'a [u8] {
329 self.data
330 }
331
332 pub fn xref_len(&self) -> usize {
334 self.xref.len()
335 }
336
337 fn decrypt_object(&self, obj: PdfObj, obj_num: u32, gen_num: u16) -> PdfObj {
340 let enc = match &self.encryption {
341 Some(e) => e,
342 None => return obj,
343 };
344 match obj {
345 PdfObj::Str(s) => PdfObj::Str(enc.decrypt_string(&s, obj_num, gen_num)),
346 PdfObj::Array(arr) => PdfObj::Array(
347 arr.into_iter()
348 .map(|o| self.decrypt_object(o, obj_num, gen_num))
349 .collect(),
350 ),
351 PdfObj::Dict(dict) => {
352 let entries: Vec<_> = dict
353 .into_entries()
354 .into_iter()
355 .map(|(k, v)| (k, self.decrypt_object(v, obj_num, gen_num)))
356 .collect();
357 PdfObj::Dict(PdfDict::from_entries(entries))
358 }
359 PdfObj::Stream {
360 dict,
361 data_offset,
362 data_len,
363 } => {
364 let entries: Vec<_> = dict
366 .into_entries()
367 .into_iter()
368 .map(|(k, v)| (k, self.decrypt_object(v, obj_num, gen_num)))
369 .collect();
370 PdfObj::Stream {
371 dict: PdfDict::from_entries(entries),
372 data_offset,
373 data_len,
374 }
375 }
376 other => other,
377 }
378 }
379
380 fn resolve_jbig2_globals(
383 &self,
384 filters: &[filters::Filter],
385 parms: &[Option<PdfDict>],
386 ) -> Result<Option<Vec<u8>>, PdfError> {
387 for (i, f) in filters.iter().enumerate() {
388 if *f == filters::Filter::JBIG2Decode
389 && let Some(Some(dp)) = parms.get(i)
390 && let Some(globals_ref) = dp.get(b"JBIG2Globals")
391 {
392 return self.stream_data_from_obj(globals_ref).map(Some);
393 }
394 }
395 Ok(None)
396 }
397
398 fn scan_for_object(&self, obj_num: u32) -> Result<PdfObj, PdfError> {
403 {
405 let mut map_ref = self.scan_map.borrow_mut();
406 if map_ref.is_none() {
407 *map_ref = Some(self.build_scan_map());
408 }
409 }
410
411 let offset = {
414 let map = self.scan_map.borrow();
415 map.as_ref().unwrap().get(&obj_num).copied()
416 };
417 match offset {
418 Some(offset) => self.parse_object_at(offset, None),
419 None => Err(PdfError::ObjectNotFound {
420 obj_num,
421 gen_num: 0,
422 }),
423 }
424 }
425
426 fn build_scan_map(&self) -> HashMap<u32, usize> {
430 let mut map = HashMap::new();
431 let data = self.data;
432 let len = data.len();
433 let mut pos = 0;
434
435 while pos < len {
436 if !data[pos].is_ascii_digit() {
438 pos += 1;
439 continue;
440 }
441
442 if pos > 0 && data[pos - 1] != b'\n' && data[pos - 1] != b'\r' && data[pos - 1] != b' '
444 {
445 pos += 1;
446 continue;
447 }
448
449 let start = pos;
451 while pos < len && data[pos].is_ascii_digit() {
453 pos += 1;
454 }
455 if pos == start || pos >= len || data[pos] != b' ' {
456 continue;
457 }
458 let num_end = pos;
459 pos += 1; let gen_start = pos;
463 while pos < len && data[pos].is_ascii_digit() {
464 pos += 1;
465 }
466 if pos == gen_start || pos >= len || data[pos] != b' ' {
467 continue;
468 }
469 pos += 1; if pos + 3 <= len
473 && &data[pos..pos + 3] == b"obj"
474 && (pos + 3 == len
475 || data[pos + 3].is_ascii_whitespace()
476 || matches!(data[pos + 3], b'<' | b'[' | b'(' | b'/'))
477 {
478 if let Ok(n) = std::str::from_utf8(&data[start..num_end])
479 .unwrap_or("")
480 .parse::<u32>()
481 {
482 map.insert(n, start);
484 }
485 pos += 3;
486 }
487 }
488
489 map
490 }
491
492 fn parse_object_at(
498 &self,
499 offset: usize,
500 expected_obj_num: Option<u32>,
501 ) -> Result<PdfObj, PdfError> {
502 if offset >= self.data.len() {
503 return Err(PdfError::InvalidObject(offset));
504 }
505
506 let actual_offset = self
510 .try_parse_obj_header_relaxed(offset)
511 .or_else(|| {
512 let start = offset.saturating_sub(20);
514 (start..offset)
515 .rev()
516 .find_map(|off| self.try_parse_obj_header(off))
517 })
518 .ok_or(PdfError::InvalidObject(offset))?;
519
520 let mut lexer = Lexer::at(self.data, actual_offset);
521
522 let parsed_num = match lexer.next_token()? {
524 Token::Int(n) => n as u32,
525 _ => return Err(PdfError::InvalidObject(offset)),
526 };
527 let _gen_num = lexer.next_token()?; let _obj_kw = lexer.next_token()?; if let Some(expected) = expected_obj_num {
531 if parsed_num != expected {
532 return Err(PdfError::InvalidObject(offset));
533 }
534 }
535
536 let obj = parse_object(&mut lexer)?;
538
539 if let PdfObj::Dict(dict) = obj {
541 let saved = lexer.pos();
542 let tok = lexer.next_token()?;
543 if matches!(tok, Token::Keyword(ref kw) if kw == b"stream") {
544 let mut data_start = lexer.pos();
546 if data_start < self.data.len() && self.data[data_start] == b'\r' {
547 data_start += 1;
548 }
549 if data_start < self.data.len() && self.data[data_start] == b'\n' {
550 data_start += 1;
551 }
552
553 let length = match self.resolve_stream_length(&dict) {
557 Ok(len) => {
558 let expected_end = data_start + len;
560 let valid = self.check_endstream_at(expected_end);
561 if valid {
562 len
563 } else {
564 self.recover_stream_length(data_start).unwrap_or(len)
566 }
567 }
568 Err(_) => self.recover_stream_length(data_start)?,
569 };
570 let data_end = std::cmp::min(data_start + length, self.data.len());
571
572 return Ok(PdfObj::Stream {
573 dict,
574 data_offset: data_start,
575 data_len: data_end - data_start,
576 });
577 } else {
578 lexer.set_pos(saved);
579 }
580 Ok(PdfObj::Dict(dict))
582 } else {
583 Ok(obj)
585 }
586 }
587
588 fn try_parse_obj_header_relaxed(&self, offset: usize) -> Option<usize> {
593 if offset >= self.data.len() {
594 return None;
595 }
596 let mut lexer = Lexer::at(self.data, offset);
597 if !matches!(lexer.next_token().ok()?, Token::Int(_)) {
598 return None;
599 }
600 if !matches!(lexer.next_token().ok()?, Token::Int(_)) {
601 return None;
602 }
603 match lexer.next_token().ok()? {
604 Token::Keyword(ref kw) if kw == b"obj" => Some(offset),
605 _ => None,
606 }
607 }
608
609 fn try_parse_obj_header(&self, offset: usize) -> Option<usize> {
610 if offset >= self.data.len() {
611 return None;
612 }
613 if offset > 0 && !matches!(self.data[offset - 1], b' ' | b'\t' | b'\r' | b'\n') {
615 return None;
616 }
617 self.try_parse_obj_header_relaxed(offset)
618 }
619
620 fn check_endstream_at(&self, offset: usize) -> bool {
622 let needles: &[&[u8]] = &[b"endstream", b"endsteam"];
627 for skip in 0..=2 {
629 let pos = offset + skip;
630 for needle in needles {
631 if pos + needle.len() <= self.data.len()
632 && &self.data[pos..pos + needle.len()] == *needle
633 {
634 return true;
635 }
636 }
637 }
638 false
639 }
640
641 fn recover_stream_length(&self, data_start: usize) -> Result<usize, PdfError> {
644 let needle = b"endstream";
645 let search_end = self.data.len().saturating_sub(needle.len());
646 let mut pos = data_start;
647 while pos <= search_end {
648 if &self.data[pos..pos + needle.len()] == needle {
649 let mut end = pos;
651 while end > data_start && matches!(self.data[end - 1], b' ' | b'\r' | b'\n') {
652 end -= 1;
653 }
654 return Ok(end - data_start);
655 }
656 pos += 1;
657 }
658 Err(PdfError::StreamMissingLength)
659 }
660
661 fn resolve_stream_length(&self, dict: &PdfDict) -> Result<usize, PdfError> {
663 match dict.get(b"Length") {
664 Some(PdfObj::Int(n)) => Ok(*n as usize),
665 Some(PdfObj::Ref(n, g)) => {
666 let len_obj = self.resolve(*n, *g)?;
667 match len_obj {
668 PdfObj::Int(n) => Ok(n as usize),
669 _ => Err(PdfError::StreamMissingLength),
670 }
671 }
672 _ => Err(PdfError::StreamMissingLength),
673 }
674 }
675
676 fn ensure_objstm_cached(&self, stream_obj_num: u32) -> Result<(), PdfError> {
679 if self.objstm_cache.borrow().contains_key(&stream_obj_num) {
680 return Ok(());
681 }
682
683 let stream_obj = self.resolve(stream_obj_num, 0)?;
684 let (dict, data_offset, data_len) = match stream_obj {
685 PdfObj::Stream {
686 dict,
687 data_offset,
688 data_len,
689 } => (dict, data_offset, data_len),
690 _ => {
691 return Err(PdfError::Other(format!(
692 "object stream {stream_obj_num} is not a stream"
693 )));
694 }
695 };
696
697 let raw_slice = &self.data[data_offset..data_offset + data_len];
698 let raw = if let Some(ref enc) = self.encryption {
699 enc.decrypt_stream(raw_slice, stream_obj_num, 0)
700 } else {
701 raw_slice.to_vec()
702 };
703 let (filter_list, parms) = filters::parse_filters(&dict, Some(self))?;
704 let stream_data = if filter_list.is_empty() {
705 raw
706 } else {
707 filters::decode_stream_bounded(
708 &raw,
709 &filter_list,
710 &parms,
711 None,
712 filters::DecodeBudget::for_stream(&dict),
713 )?
714 };
715
716 let n = dict.get_int(b"N").ok_or(PdfError::MissingKey("N"))? as usize;
717 let first = dict
718 .get_int(b"First")
719 .ok_or(PdfError::MissingKey("First"))? as usize;
720
721 let mut header_lexer = Lexer::new(&stream_data[..first.min(stream_data.len())]);
722 let mut obj_offsets: Vec<(u32, usize)> = Vec::with_capacity(n);
723 for _ in 0..n {
724 let num = match header_lexer.next_token()? {
725 Token::Int(v) => v as u32,
726 _ => break,
727 };
728 let off = match header_lexer.next_token()? {
729 Token::Int(v) => v as usize,
730 _ => break,
731 };
732 obj_offsets.push((num, off));
733 }
734
735 self.objstm_cache.borrow_mut().insert(
736 stream_obj_num,
737 ObjStmCache {
738 data: stream_data,
739 first,
740 offsets: obj_offsets,
741 },
742 );
743 Ok(())
744 }
745
746 fn parse_object_from_stream(
748 &self,
749 stream_obj_num: u32,
750 index_within: u16,
751 ) -> Result<PdfObj, PdfError> {
752 self.ensure_objstm_cached(stream_obj_num)?;
754
755 let cache = self.objstm_cache.borrow();
756 let cached = cache
757 .get(&stream_obj_num)
758 .ok_or_else(|| PdfError::Other(format!("ObjStm {stream_obj_num} not in cache")))?;
759
760 let idx = index_within as usize;
762 if idx >= cached.offsets.len() {
763 return Err(PdfError::Other(format!(
764 "index {idx} out of range in object stream {stream_obj_num}"
765 )));
766 }
767
768 let (_target_num, target_offset) = cached.offsets[idx];
769 let abs_offset = cached.first + target_offset;
770 if abs_offset >= cached.data.len() {
771 return Err(PdfError::Other(format!(
772 "object offset {abs_offset} out of range in stream {stream_obj_num}"
773 )));
774 }
775
776 let mut obj_lexer = Lexer::new(&cached.data[abs_offset..]);
777 let obj = parse_object(&mut obj_lexer)?;
778
779 let target_num = cached.offsets[idx].0;
781 drop(cache);
782 self.cache.borrow_mut().insert(target_num, obj.clone());
783
784 Ok(obj)
785 }
786}
787
788#[cfg(test)]
789mod tests {
790 use super::*;
791
792 fn minimal_pdf() -> Vec<u8> {
794 let mut pdf = Vec::new();
795 pdf.extend(b"%PDF-1.4\n");
796
797 let obj1_offset = pdf.len();
799 pdf.extend(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
800
801 let obj2_offset = pdf.len();
803 pdf.extend(b"2 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n");
804
805 let xref_offset = pdf.len();
807 pdf.extend(b"xref\n");
808 pdf.extend(b"0 3\n");
809 pdf.extend(b"0000000000 65535 f\r\n");
810 pdf.extend(format!("{:010} 00000 n\r\n", obj1_offset).as_bytes());
811 pdf.extend(format!("{:010} 00000 n\r\n", obj2_offset).as_bytes());
812 pdf.extend(b"trailer\n");
813 pdf.extend(b"<< /Size 3 /Root 1 0 R >>\n");
814 pdf.extend(format!("startxref\n{xref_offset}\n%%EOF\n").as_bytes());
815
816 pdf
817 }
818
819 #[test]
820 fn resolve_simple_objects() {
821 let data = minimal_pdf();
822 let xref = crate::xref::parse_xref(&data).unwrap();
823 let resolver = Resolver::with_encryption(&data, xref, None);
824
825 let obj1 = resolver.resolve(1, 0).unwrap();
826 let dict = obj1.as_dict().unwrap();
827 assert_eq!(dict.get_name(b"Type"), Some(b"Catalog".as_slice()));
828
829 let obj2 = resolver.resolve(2, 0).unwrap();
830 let dict = obj2.as_dict().unwrap();
831 assert_eq!(dict.get_name(b"Type"), Some(b"Pages".as_slice()));
832 }
833
834 #[test]
835 fn deref_passes_through_non_ref() {
836 let data = minimal_pdf();
837 let xref = crate::xref::parse_xref(&data).unwrap();
838 let resolver = Resolver::with_encryption(&data, xref, None);
839
840 let obj = PdfObj::Int(42);
841 let result = resolver.deref(&obj).unwrap();
842 assert_eq!(result, PdfObj::Int(42));
843 }
844
845 #[test]
846 fn deref_resolves_ref() {
847 let data = minimal_pdf();
848 let xref = crate::xref::parse_xref(&data).unwrap();
849 let resolver = Resolver::with_encryption(&data, xref, None);
850
851 let obj = PdfObj::Ref(1, 0);
852 let result = resolver.deref(&obj).unwrap();
853 assert!(result.as_dict().is_some());
854 }
855
856 #[test]
857 fn object_not_found() {
858 let data = minimal_pdf();
859 let xref = crate::xref::parse_xref(&data).unwrap();
860 let resolver = Resolver::with_encryption(&data, xref, None);
861
862 let result = resolver.resolve(999, 0);
863 assert!(result.is_err());
864 }
865}