Skip to main content

rustc_incremental/persist/
file_format.rs

1//! This module defines a generic file format that allows to check if a given
2//! file generated by incremental compilation was generated by a compatible
3//! compiler version. This file format is used for the on-disk version of the
4//! dependency graph and the exported metadata hashes.
5//!
6//! In practice "compatible compiler version" means "exactly the same compiler
7//! version", since the header encodes the git commit hash of the compiler.
8//! Since we can always just ignore the incremental compilation cache and
9//! compiler versions don't change frequently for the typical user, being
10//! conservative here practically has no downside.
11
12use std::borrow::Cow;
13use std::io::{self, Read};
14use std::path::{Path, PathBuf};
15use std::{array, env, fs};
16
17use rustc_data_structures::memmap::Mmap;
18use rustc_serialize::Encoder;
19use rustc_serialize::opaque::{FileEncodeResult, FileEncoder};
20use rustc_session::Session;
21use tracing::debug;
22
23use crate::errors;
24
25/// The first few bytes of files generated by incremental compilation.
26const FILE_MAGIC: &[u8] = b"RSIC";
27
28/// Change this if the header format changes.
29const HEADER_FORMAT_VERSION: u16 = 0;
30
31pub(crate) fn write_file_header(stream: &mut FileEncoder, sess: &Session) {
32    stream.emit_raw_bytes(FILE_MAGIC);
33    stream.emit_raw_bytes(&u16::to_le_bytes(HEADER_FORMAT_VERSION));
34
35    let rustc_version = rustc_version(sess);
36    let rustc_version_len =
37        u8::try_from(rustc_version.len()).expect("version string should not exceed 255 bytes");
38    stream.emit_raw_bytes(&[rustc_version_len]);
39    stream.emit_raw_bytes(rustc_version.as_bytes());
40}
41
42pub(crate) fn save_in<F>(sess: &Session, path_buf: PathBuf, name: &str, encode: F)
43where
44    F: FnOnce(FileEncoder) -> FileEncodeResult,
45{
46    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/persist/file_format.rs:46",
                        "rustc_incremental::persist::file_format",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/persist/file_format.rs"),
                        ::tracing_core::__macro_support::Option::Some(46u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::persist::file_format"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("save: storing data in {0}",
                                                    path_buf.display()) as &dyn Value))])
            });
    } else { ; }
};debug!("save: storing data in {}", path_buf.display());
47
48    // Delete the old file, if any.
49    // Note: It's important that we actually delete the old file and not just
50    // truncate and overwrite it, since it might be a shared hard-link, the
51    // underlying data of which we don't want to modify.
52    //
53    // We have to ensure we have dropped the memory maps to this file
54    // before performing this removal.
55    match fs::remove_file(&path_buf) {
56        Ok(()) => {
57            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/persist/file_format.rs:57",
                        "rustc_incremental::persist::file_format",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/persist/file_format.rs"),
                        ::tracing_core::__macro_support::Option::Some(57u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::persist::file_format"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("save: remove old file")
                                            as &dyn Value))])
            });
    } else { ; }
};debug!("save: remove old file");
58        }
59        Err(err) if err.kind() == io::ErrorKind::NotFound => (),
60        Err(err) => sess.dcx().emit_fatal(errors::DeleteOld { name, path: path_buf, err }),
61    }
62
63    let mut encoder = match FileEncoder::new(&path_buf) {
64        Ok(encoder) => encoder,
65        Err(err) => sess.dcx().emit_fatal(errors::CreateNew { name, path: path_buf, err }),
66    };
67
68    write_file_header(&mut encoder, sess);
69
70    match encode(encoder) {
71        Ok(position) => {
72            sess.prof.artifact_size(
73                &name.replace(' ', "_"),
74                path_buf.file_name().unwrap().to_string_lossy(),
75                position as u64,
76            );
77            {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/persist/file_format.rs:77",
                        "rustc_incremental::persist::file_format",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/persist/file_format.rs"),
                        ::tracing_core::__macro_support::Option::Some(77u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::persist::file_format"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("save: data written to disk successfully")
                                            as &dyn Value))])
            });
    } else { ; }
};debug!("save: data written to disk successfully");
78        }
79        Err((path, err)) => sess.dcx().emit_fatal(errors::WriteNew { name, path, err }),
80    }
81}
82
83pub(crate) struct OpenFile {
84    /// A read-only mmap view of the file contents.
85    pub(crate) mmap: Mmap,
86    /// File position to start reading normal data from, just after the end of the file header.
87    pub(crate) start_pos: usize,
88}
89
90pub(crate) enum OpenFileError {
91    /// Either the file was not found, or one of the header checks failed.
92    ///
93    /// These conditions prevent us from reading the file contents, but should
94    /// not trigger an error or even a warning, because they routinely happen
95    /// during normal operation:
96    /// - File-not-found occurs in a fresh build, or after clearing the build directory.
97    /// - Header-mismatch occurs after upgrading or switching compiler versions.
98    NotFoundOrHeaderMismatch,
99
100    /// An unexpected I/O error occurred while opening or checking the file.
101    IoError { err: io::Error },
102}
103
104impl From<io::Error> for OpenFileError {
105    fn from(err: io::Error) -> Self {
106        OpenFileError::IoError { err }
107    }
108}
109
110/// Tries to open a file that was written by the previous incremental-compilation
111/// session, and checks that it was produced by a matching compiler version.
112pub(crate) fn open_incremental_file(
113    sess: &Session,
114    path: &Path,
115) -> Result<OpenFile, OpenFileError> {
116    let file = fs::File::open(path).map_err(|err| {
117        if err.kind() == io::ErrorKind::NotFound {
118            OpenFileError::NotFoundOrHeaderMismatch
119        } else {
120            OpenFileError::IoError { err }
121        }
122    })?;
123
124    // SAFETY: This process must not modify nor remove the backing file while the memory map lives.
125    // For the dep-graph and the work product index, it is as soon as the decoding is done.
126    // For the query result cache, the memory map is dropped in save_dep_graph before calling
127    // save_in and trying to remove the backing file.
128    //
129    // There is no way to prevent another process from modifying this file.
130    let mmap = unsafe { Mmap::map(file) }?;
131
132    let mut file = io::Cursor::new(&*mmap);
133
134    // Check FILE_MAGIC
135    {
136        if true {
    if !(FILE_MAGIC.len() == 4) {
        ::core::panicking::panic("assertion failed: FILE_MAGIC.len() == 4")
    };
};debug_assert!(FILE_MAGIC.len() == 4);
137        let mut file_magic = [0u8; 4];
138        file.read_exact(&mut file_magic)?;
139        if file_magic != FILE_MAGIC {
140            report_format_mismatch(sess, path, "Wrong FILE_MAGIC");
141            return Err(OpenFileError::NotFoundOrHeaderMismatch);
142        }
143    }
144
145    // Check HEADER_FORMAT_VERSION
146    {
147        if true {
    if !(size_of_val(&HEADER_FORMAT_VERSION) == 2) {
        ::core::panicking::panic("assertion failed: size_of_val(&HEADER_FORMAT_VERSION) == 2")
    };
};debug_assert!(size_of_val(&HEADER_FORMAT_VERSION) == 2);
148        let mut header_format_version = [0u8; 2];
149        file.read_exact(&mut header_format_version)?;
150        let header_format_version = u16::from_le_bytes(header_format_version);
151
152        if header_format_version != HEADER_FORMAT_VERSION {
153            report_format_mismatch(sess, path, "Wrong HEADER_FORMAT_VERSION");
154            return Err(OpenFileError::NotFoundOrHeaderMismatch);
155        }
156    }
157
158    // Check RUSTC_VERSION
159    {
160        let mut rustc_version_str_len = 0u8;
161        file.read_exact(array::from_mut(&mut rustc_version_str_len))?;
162        let mut buffer = ::alloc::vec::from_elem(0, usize::from(rustc_version_str_len))vec![0; usize::from(rustc_version_str_len)];
163        file.read_exact(&mut buffer)?;
164
165        if buffer != rustc_version(sess).as_bytes() {
166            report_format_mismatch(sess, path, "Different compiler version");
167            return Err(OpenFileError::NotFoundOrHeaderMismatch);
168        }
169    }
170
171    let start_pos = file.position() as usize;
172    Ok(OpenFile { mmap, start_pos })
173}
174
175fn report_format_mismatch(sess: &Session, file: &Path, message: &str) {
176    {
    use ::tracing::__macro_support::Callsite as _;
    static __CALLSITE: ::tracing::callsite::DefaultCallsite =
        {
            static META: ::tracing::Metadata<'static> =
                {
                    ::tracing_core::metadata::Metadata::new("event compiler/rustc_incremental/src/persist/file_format.rs:176",
                        "rustc_incremental::persist::file_format",
                        ::tracing::Level::DEBUG,
                        ::tracing_core::__macro_support::Option::Some("compiler/rustc_incremental/src/persist/file_format.rs"),
                        ::tracing_core::__macro_support::Option::Some(176u32),
                        ::tracing_core::__macro_support::Option::Some("rustc_incremental::persist::file_format"),
                        ::tracing_core::field::FieldSet::new(&["message"],
                            ::tracing_core::callsite::Identifier(&__CALLSITE)),
                        ::tracing::metadata::Kind::EVENT)
                };
            ::tracing::callsite::DefaultCallsite::new(&META)
        };
    let enabled =
        ::tracing::Level::DEBUG <= ::tracing::level_filters::STATIC_MAX_LEVEL
                &&
                ::tracing::Level::DEBUG <=
                    ::tracing::level_filters::LevelFilter::current() &&
            {
                let interest = __CALLSITE.interest();
                !interest.is_never() &&
                    ::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
                        interest)
            };
    if enabled {
        (|value_set: ::tracing::field::ValueSet|
                    {
                        let meta = __CALLSITE.metadata();
                        ::tracing::Event::dispatch(meta, &value_set);
                        ;
                    })({
                #[allow(unused_imports)]
                use ::tracing::field::{debug, display, Value};
                let mut iter = __CALLSITE.metadata().fields().iter();
                __CALLSITE.metadata().fields().value_set(&[(&::tracing::__macro_support::Iterator::next(&mut iter).expect("FieldSet corrupted (this is a bug)"),
                                    ::tracing::__macro_support::Option::Some(&format_args!("read_file: {0}",
                                                    message) as &dyn Value))])
            });
    } else { ; }
};debug!("read_file: {}", message);
177
178    if sess.opts.unstable_opts.incremental_info {
179        {
    ::std::io::_eprint(format_args!("[incremental] ignoring cache artifact `{0}`: {1}\n",
            file.file_name().unwrap().to_string_lossy(), message));
};eprintln!(
180            "[incremental] ignoring cache artifact `{}`: {}",
181            file.file_name().unwrap().to_string_lossy(),
182            message
183        );
184    }
185}
186
187/// A version string that hopefully is always different for compiler versions
188/// with different encodings of incremental compilation artifacts. Contains
189/// the Git commit hash.
190fn rustc_version(sess: &Session) -> Cow<'static, str> {
191    // Allow version string overrides so that tests can produce a header-mismatch on demand.
192    if sess.is_nightly_build()
193        && let Ok(env_version) = env::var("RUSTC_FORCE_RUSTC_VERSION")
194    {
195        Cow::Owned(env_version)
196    } else {
197        Cow::Borrowed(sess.cfg_version)
198    }
199}