Skip to main content

charon_lib/
export.rs

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