Skip to main content

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