Skip to main content

charon_lib/
export.rs

1pub mod multi_target;
2
3use crate::ast::*;
4use crate::options::SerializationFormat;
5use crate::transform::TransformCtx;
6use serde::{Deserialize, Deserializer, Serialize};
7use serde_state::{DeserializeState, SerializeState};
8use std::fs::File;
9use std::io::{BufReader, BufWriter, Read};
10use std::path::{Path, PathBuf};
11
12/// The data of a generic crate. We serialize this to pass it to `charon-ml`, so this must be as
13/// stable as possible. This is used for both ULLBC and LLBC.
14#[derive(SerializeState, DeserializeState)]
15pub struct CrateData {
16    /// The version of charon currently being used. `charon-ml` inspects this and errors if it is
17    /// trying to read an incompatible version (for now we compare versions for equality).
18    #[serde_state(stateless)]
19    pub charon_version: CharonVersion,
20    pub translated: TranslatedCrate,
21    #[serde_state(stateless)]
22    #[charon::opaque] // Don't change, this would break version detection for old charon-ml
23    /// If there were errors, this contains only a partial description of the input crate.
24    pub has_errors: bool,
25}
26
27impl Serialize for CrateData {
28    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
29    where
30        S: serde::Serializer,
31    {
32        if self.translated.options.no_dedup_serialized_ast {
33            self.serialize_state(&(), serializer)
34        } else {
35            let state = HashConsDedupSerializer::default();
36            self.serialize_state(&state, serializer)
37        }
38    }
39}
40
41impl<'de> Deserialize<'de> for CrateData {
42    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
43    where
44        D: Deserializer<'de>,
45    {
46        // Always provide the state, just in case.
47        let state = HashConsDedupSerializer::default();
48        Self::deserialize_state(&state, deserializer)
49    }
50}
51
52#[derive(Serialize)]
53pub struct CharonVersion(pub String);
54
55impl<'de> Deserialize<'de> for CharonVersion {
56    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
57    where
58        D: Deserializer<'de>,
59    {
60        use serde::de::Error;
61        let version = String::deserialize(deserializer)?;
62        if version != crate::VERSION {
63            return Err(D::Error::custom(format!(
64                "Incompatible version of charon: \
65                this program supports llbc emitted by charon v{} \
66                but attempted to read a file emitted by charon v{}",
67                crate::VERSION,
68                version,
69            )));
70        }
71        Ok(CharonVersion(version))
72    }
73}
74
75impl CrateData {
76    pub fn new(ctx: TransformCtx) -> Self {
77        CrateData {
78            charon_version: CharonVersion(crate::VERSION.to_owned()),
79            has_errors: ctx.has_errors(),
80            translated: ctx.translated,
81        }
82    }
83
84    #[allow(clippy::result_unit_err)]
85    pub fn serialize_to_files(
86        &self,
87        targets: Vec<(PathBuf, SerializationFormat)>,
88    ) -> Result<(), ()> {
89        for (target_filename, format) in targets {
90            self.serialize_to_file(&target_filename, format)?;
91        }
92        Ok(())
93    }
94
95    /// Export the translated definitions to a file.
96    #[allow(clippy::result_unit_err)]
97    pub fn serialize_to_file(
98        &self,
99        target_filename: &Path,
100        format: SerializationFormat,
101    ) -> Result<(), ()> {
102        // Create the directory, if necessary (note that if the target directory
103        // is not specified, there is no need to create it: otherwise we
104        // couldn't have read the input file in the first place).
105        let target_dir = target_filename.parent().unwrap();
106        match std::fs::create_dir_all(target_dir) {
107            Ok(()) => (),
108            Err(_) => {
109                error!("Could not create the directory: {:?}", target_dir);
110                return Err(());
111            }
112        };
113
114        // Create the file.
115        let std::io::Result::Ok(outfile) = File::create(target_filename) else {
116            error!("Could not open: {:?}", target_filename);
117            return Err(());
118        };
119        // Write to the file.
120        let mut writer = BufWriter::new(outfile);
121        match format {
122            SerializationFormat::Json => {
123                if let Err(err) = serde_json::to_writer(&mut writer, self) {
124                    error!("Could not serialize to `{target_filename:?}`: {err:?}");
125                    return Err(());
126                }
127            }
128            SerializationFormat::Postcard => {
129                if let Err(err) = postcard::to_io(self, &mut writer) {
130                    error!("Could not serialize to `{target_filename:?}`: {err:?}");
131                    return Err(());
132                }
133            }
134        }
135
136        // We canonicalize (i.e., make absolute) the path before printing it; this makes it clearer
137        // to the user where to find the file.
138        let target_filename = std::fs::canonicalize(target_filename).unwrap();
139        if self.has_errors {
140            info!(
141                "Generated the partial (because we encountered errors) file: {}",
142                target_filename.to_str().unwrap()
143            );
144        } else {
145            info!("Generated the file: {}", target_filename.to_str().unwrap());
146        }
147        Ok(())
148    }
149
150    pub fn deserialize_from_file(
151        path: &std::path::Path,
152        format: SerializationFormat,
153    ) -> anyhow::Result<Self> {
154        use crate::export::CrateData;
155        use anyhow::Context;
156        use serde::Deserialize;
157        use std::fs::File;
158        let file = File::open(path)
159            .with_context(|| format!("Failed to read llbc file {}", path.display()))?;
160        match format {
161            SerializationFormat::Json => {
162                let reader = BufReader::new(file);
163                let mut deserializer = serde_json::Deserializer::from_reader(reader);
164                // Deserialize without recursion limit.
165                deserializer.disable_recursion_limit();
166                // Grow stack space as needed.
167                let deserializer = serde_stacker::Deserializer::new(&mut deserializer);
168                Ok(CrateData::deserialize(deserializer)?)
169            }
170            SerializationFormat::Postcard => {
171                let mut reader = BufReader::new(file);
172                let mut bytes = Vec::new();
173                reader.read_to_end(&mut bytes)?;
174                let (crate_data, rem) = postcard::take_from_bytes::<CrateData>(&bytes)
175                    .map_err(|err| anyhow::anyhow!("postcard deserialize error: {err:?}"))?;
176                if !rem.is_empty() {
177                    anyhow::bail!("postcard deserialize error: trailing bytes left in input");
178                }
179                Ok(crate_data)
180            }
181        }
182    }
183}