flux_metadata/
decoder.rs

1use std::{
2    fs,
3    io::{self, Read},
4    mem, panic,
5    path::Path,
6    sync::Arc,
7};
8
9use flux_common::bug;
10use flux_errors::FluxSession;
11use rustc_data_structures::{fx::FxHashMap, sync::HashMapExt};
12use rustc_hir::def_id::DefId;
13use rustc_middle::{
14    implement_ty_decoder,
15    ty::{self, TyCtxt, codec::TyDecoder},
16};
17use rustc_serialize::{
18    Decodable, Decoder as _,
19    opaque::{IntEncodedWithFixedSize, MemDecoder},
20};
21use rustc_session::StableCrateId;
22use rustc_span::{
23    BytePos, ByteSymbol, DUMMY_SP, SourceFile, Span, SpanDecoder, Symbol, SyntaxContext,
24    def_id::{CrateNum, DefIndex},
25    hygiene::{HygieneDecodeContext, SyntaxContextKey},
26};
27
28use crate::{
29    AbsoluteBytePos, CrateMetadata, EncodedSourceFileId, Footer, METADATA_HEADER, SYMBOL_OFFSET,
30    SYMBOL_PREDEFINED, SYMBOL_STR, SourceFileIndex, TAG_FULL_SPAN, TAG_PARTIAL_SPAN,
31};
32
33struct DecodeContext<'a, 'tcx> {
34    tcx: TyCtxt<'tcx>,
35    opaque: MemDecoder<'a>,
36    file_index_to_file: FxHashMap<SourceFileIndex, Arc<SourceFile>>,
37    file_index_to_stable_id: FxHashMap<SourceFileIndex, EncodedSourceFileId>,
38    syntax_contexts: &'a FxHashMap<u32, AbsoluteBytePos>,
39    expn_data: FxHashMap<(StableCrateId, u32), AbsoluteBytePos>,
40    hygiene_context: &'a HygieneDecodeContext,
41}
42
43impl<'a, 'tcx> DecodeContext<'a, 'tcx> {
44    fn file_index_to_file(&mut self, index: SourceFileIndex) -> Arc<SourceFile> {
45        self.file_index_to_file
46            .entry(index)
47            .or_insert_with(|| {
48                let source_file_id = &self.file_index_to_stable_id[&index];
49                let source_file_cnum = self
50                    .tcx
51                    .stable_crate_id_to_crate_num(source_file_id.stable_crate_id);
52
53                self.tcx.import_source_files(source_file_cnum);
54                self.tcx
55                    .sess
56                    .source_map()
57                    .source_file_by_stable_id(source_file_id.stable_source_file_id)
58                    .expect("failed to lookup `SourceFile` in new context")
59            })
60            .clone()
61    }
62}
63
64pub(super) fn decode_crate_metadata(
65    tcx: TyCtxt,
66    sess: &FluxSession,
67    path: &Path,
68) -> Option<CrateMetadata> {
69    let mut file = match fs::File::open(path) {
70        Ok(file) => file,
71        Err(err) if let io::ErrorKind::NotFound = err.kind() => return None,
72        Err(err) => sess.emit_fatal(errors::DecodeFileError::new(path, err)),
73    };
74    let mut buf = vec![];
75    file.read_to_end(&mut buf)
76        .unwrap_or_else(|err| sess.emit_fatal(errors::DecodeFileError::new(path, err)));
77
78    // The last byte of the header is `METADATA_VERSION`. A header mismatch means the file was
79    // produced by a version of flux using a different (and thus incompatible) metadata format.
80    if !buf.starts_with(METADATA_HEADER) {
81        sess.emit_fatal(errors::IncompatibleMetadata::new(path));
82    }
83
84    let metadata = catch_decode(|| {
85        let footer = {
86            let mut decoder = MemDecoder::new(&buf, 0).unwrap();
87            let footer_pos = decoder
88                .with_position(decoder.len() - IntEncodedWithFixedSize::ENCODED_SIZE, |d| {
89                    IntEncodedWithFixedSize::decode(d).0 as usize
90                });
91            decoder.with_position(footer_pos, Footer::decode)
92        };
93
94        let mut decoder = DecodeContext {
95            tcx,
96            opaque: MemDecoder::new(&buf, METADATA_HEADER.len()).unwrap(),
97            file_index_to_stable_id: footer.file_index_to_stable_id,
98            file_index_to_file: Default::default(),
99            syntax_contexts: &footer.syntax_contexts,
100            expn_data: footer.expn_data,
101            hygiene_context: &Default::default(),
102        };
103
104        CrateMetadata::decode(&mut decoder)
105    });
106
107    match metadata {
108        Ok(metadata) => Some(metadata),
109        Err(()) => sess.emit_fatal(errors::IncompatibleMetadata::new(path)),
110    }
111}
112
113/// Runs `decode`, catching any panic raised by rustc's opaque decoder on malformed input.
114/// The panic hook is silenced so stale metadata surfaces as a clean diagnostic, not an ICE dump.
115fn catch_decode<R>(decode: impl FnOnce() -> R) -> Result<R, ()> {
116    let prev_hook = panic::take_hook();
117    panic::set_hook(Box::new(|_| {}));
118    let result = panic::catch_unwind(panic::AssertUnwindSafe(decode));
119    panic::set_hook(prev_hook);
120    result.map_err(|_| ())
121}
122
123implement_ty_decoder!(DecodeContext<'a, 'tcx>);
124
125impl SpanDecoder for DecodeContext<'_, '_> {
126    fn decode_attr_id(&mut self) -> rustc_ast::AttrId {
127        self.tcx.sess.psess.attr_id_generator.mk_attr_id()
128    }
129
130    fn decode_crate_num(&mut self) -> CrateNum {
131        let stable_id = StableCrateId::decode(self);
132        self.tcx.stable_crate_id_to_crate_num(stable_id)
133    }
134
135    fn decode_def_index(&mut self) -> DefIndex {
136        DefIndex::from_u32(self.read_u32())
137    }
138
139    fn decode_def_id(&mut self) -> DefId {
140        DefId { krate: Decodable::decode(self), index: Decodable::decode(self) }
141    }
142
143    fn decode_syntax_context(&mut self) -> SyntaxContext {
144        let syntax_contexts = self.syntax_contexts;
145        rustc_span::hygiene::decode_syntax_context(self, self.hygiene_context, |this, id| {
146            // This closure is invoked if we haven't already decoded the data for the `SyntaxContext` we are deserializing.
147            // We look up the position of the associated `SyntaxData` and decode it.
148            let pos = syntax_contexts.get(&id).unwrap();
149            this.with_position(pos.to_usize(), SyntaxContextKey::decode)
150        })
151    }
152
153    fn decode_expn_id(&mut self) -> rustc_span::ExpnId {
154        let stable_id = StableCrateId::decode(self);
155        let cnum = self.tcx.stable_crate_id_to_crate_num(stable_id);
156        let index = u32::decode(self);
157
158        rustc_span::hygiene::decode_expn_id(cnum, index, |_| {
159            let pos = self.expn_data.get(&(stable_id, index)).unwrap();
160            self.with_position(pos.to_usize(), |decoder| {
161                let data = rustc_span::ExpnData::decode(decoder);
162                let hash = rustc_span::ExpnHash::decode(decoder);
163                (data, hash)
164            })
165        })
166    }
167
168    fn decode_span(&mut self) -> rustc_span::Span {
169        let ctxt = SyntaxContext::decode(self);
170        let tag: u8 = Decodable::decode(self);
171
172        if tag == TAG_PARTIAL_SPAN {
173            return DUMMY_SP.with_ctxt(ctxt);
174        }
175
176        debug_assert!(tag == TAG_FULL_SPAN);
177
178        let source_file_index = SourceFileIndex::decode(self);
179        let lo = BytePos::decode(self);
180        let len = BytePos::decode(self);
181        let file = self.file_index_to_file(source_file_index);
182        let lo = file.start_pos + lo;
183        let hi = lo + len;
184
185        Span::new(lo, hi, ctxt, None)
186    }
187
188    fn decode_symbol(&mut self) -> rustc_span::Symbol {
189        let tag = self.read_u8();
190
191        match tag {
192            SYMBOL_STR => {
193                let s = self.read_str();
194                Symbol::intern(s)
195            }
196            SYMBOL_OFFSET => {
197                // read str offset
198                let pos = self.read_usize();
199
200                // move to str offset and read
201                self.opaque.with_position(pos, |d| {
202                    let s = d.read_str();
203                    Symbol::intern(s)
204                })
205            }
206            SYMBOL_PREDEFINED => {
207                let symbol_index = self.read_u32();
208                Symbol::new(symbol_index)
209            }
210            _ => unreachable!(),
211        }
212    }
213
214    fn decode_byte_symbol(&mut self) -> ByteSymbol {
215        ByteSymbol::intern(self.read_byte_str())
216    }
217}
218
219impl<'tcx> TyDecoder<'tcx> for DecodeContext<'_, 'tcx> {
220    const CLEAR_CROSS_CRATE: bool = true;
221
222    fn interner(&self) -> TyCtxt<'tcx> {
223        self.tcx
224    }
225
226    fn cached_ty_for_shorthand<F>(&mut self, shorthand: usize, or_insert_with: F) -> ty::Ty<'tcx>
227    where
228        F: FnOnce(&mut Self) -> ty::Ty<'tcx>,
229    {
230        let tcx = self.tcx;
231
232        let cache_key = ty::CReaderCacheKey { cnum: None, pos: shorthand };
233
234        if let Some(&ty) = tcx.ty_rcache.borrow().get(&cache_key) {
235            return ty;
236        }
237
238        let ty = or_insert_with(self);
239        // This may overwrite the entry, but it should overwrite with the same value.
240        tcx.ty_rcache.borrow_mut().insert_same(cache_key, ty);
241        ty
242    }
243
244    fn with_position<F, R>(&mut self, pos: usize, f: F) -> R
245    where
246        F: FnOnce(&mut Self) -> R,
247    {
248        let new_opaque = self.opaque.split_at(pos);
249        let old_opaque = mem::replace(&mut self.opaque, new_opaque);
250        let r = f(self);
251        self.opaque = old_opaque;
252        r
253    }
254
255    fn decode_alloc_id(&mut self) -> rustc_middle::mir::interpret::AllocId {
256        bug!("Encoding `interpret::AllocId` is not supported")
257    }
258}
259
260mod errors {
261    use std::{io, path::Path};
262
263    use flux_errors::E0999;
264    use flux_macros::Diagnostic;
265
266    #[derive(Diagnostic)]
267    #[diag(metadata_decode_file_error, code = E0999)]
268    pub(super) struct DecodeFileError<'a> {
269        path: &'a Path,
270        err: io::Error,
271    }
272
273    impl<'a> DecodeFileError<'a> {
274        pub(super) fn new(path: &'a Path, err: io::Error) -> Self {
275            Self { path, err }
276        }
277    }
278
279    #[derive(Diagnostic)]
280    #[diag(metadata_incompatible_metadata, code = E0999)]
281    pub(super) struct IncompatibleMetadata<'a> {
282        path: &'a Path,
283    }
284
285    impl<'a> IncompatibleMetadata<'a> {
286        pub(super) fn new(path: &'a Path) -> Self {
287            Self { path }
288        }
289    }
290}