Skip to main content

charon_lib/
errors.rs

1//! Utilities to generate error reports about the external dependencies.
2use crate::ast::*;
3use crate::formatter::IntoFormatter;
4use crate::pretty::FmtWithCtx;
5pub use annotate_snippets::Level;
6use itertools::Itertools;
7use macros::VariantIndexArity;
8use petgraph::algo::dijkstra::dijkstra;
9use petgraph::prelude::DiGraphMap;
10use serde::{Deserialize, Serialize};
11use std::cmp::{Ord, PartialOrd};
12use std::collections::{HashMap, HashSet};
13
14const BACKTRACE_ON_ERR: bool = false;
15
16#[macro_export]
17macro_rules! register_error {
18    ($ctx:expr, crate($krate:expr), $span: expr, $($fmt:tt)*) => {{
19        let msg = format!($($fmt)*);
20        $ctx.span_err($krate, $span, &msg, $crate::errors::Level::WARNING)
21    }};
22    ($ctx:expr, $span: expr, $($fmt:tt)*) => {{
23        let msg = format!($($fmt)*);
24        $ctx.span_err($span, &msg, $crate::errors::Level::WARNING)
25    }};
26}
27pub use register_error;
28
29/// Macro to either panic or return on error, depending on the CLI options
30#[macro_export]
31macro_rules! raise_error {
32    ($($tokens:tt)*) => {{
33        return Err(register_error!($($tokens)*));
34    }};
35}
36pub use raise_error;
37
38/// Custom assert to either panic or return an error
39#[macro_export]
40macro_rules! error_assert {
41    ($ctx:expr, $span: expr, $b: expr) => {
42        if !$b {
43            $crate::errors::raise_error!($ctx, $span, "assertion failure: {:?}", stringify!($b));
44        }
45    };
46    ($ctx:expr, $span: expr, $b: expr, $($fmt:tt)*) => {
47        if !$b {
48            $crate::errors::raise_error!($ctx, $span, $($fmt)*);
49        }
50    };
51}
52pub use error_assert;
53
54/// Custom assert to report an error and optionally panic
55#[macro_export]
56macro_rules! sanity_check {
57    ($ctx:expr, $span: expr, $b: expr) => {
58        if !$b {
59            $crate::errors::register_error!(
60                $ctx,
61                $span,
62                "assertion failure: {:?}",
63                stringify!($b)
64            );
65        }
66    };
67    ($ctx:expr, $span: expr, $b: expr, $($fmt:tt)*) => {
68        if !$b {
69            $crate::errors::register_error!($ctx, $span, $($fmt)*);
70        }
71    };
72}
73pub use sanity_check;
74
75/// Common error used during the translation.
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct Error {
78    pub span: Span,
79    pub msg: String,
80}
81
82impl Error {
83    pub fn new(span: Span, msg: String) -> Self {
84        Self { span, msg }
85    }
86    pub fn dummy() -> Self {
87        Self {
88            span: Span::dummy(),
89            msg: String::new(),
90        }
91    }
92
93    pub(crate) fn render(&self, krate: &TranslatedCrate, level: Level) -> String {
94        use annotate_snippets::*;
95        let span = self.span.data;
96
97        let mut group = Group::with_title(level.title(&self.msg));
98        let origin;
99        if let Some(file) = krate.files.get(span.file_id) {
100            origin = format!("{}", file.name);
101            if let Some(source) = &file.contents {
102                let snippet = Snippet::source(source)
103                    .path(&origin)
104                    .fold(true)
105                    .annotation(AnnotationKind::Primary.span(span.to_byte_range(source)));
106                group = group.element(snippet);
107            } else {
108                // Show just the file and line/col.
109                let origin = Origin::path(&origin)
110                    .line(span.beg.line)
111                    .char_column(span.beg.col + 1);
112                group = group.element(origin);
113            }
114        }
115
116        Renderer::styled().render(&[group]).to_string()
117    }
118}
119
120impl<T: ToString> From<T> for Error {
121    fn from(err: T) -> Self {
122        Self {
123            span: Span::dummy(),
124            msg: err.to_string(),
125        }
126    }
127}
128
129/// Display an error without a specific location.
130pub fn display_unspanned_error(level: Level, msg: &str) {
131    use annotate_snippets::*;
132    let title = level.title(msg);
133    let message = Renderer::styled()
134        .render(&[Group::with_title(title)])
135        .to_string();
136    anstream::eprintln!("{message}\n");
137}
138
139/// We use this to save the origin of an id. This is useful for the external
140/// dependencies, especially if some external dependencies don't extract:
141/// we use this information to tell the user what is the code which
142/// (transitively) lead to the extraction of those problematic dependencies.
143#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
144pub struct DepSource {
145    pub src_id: ItemId,
146    /// The location where the id was referred to. We store `None` for external dependencies as we
147    /// don't want to show these to the users.
148    pub span: Option<Span>,
149}
150
151/// For tracing error dependencies.
152#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, VariantIndexArity)]
153enum DepNode {
154    External(ItemId),
155    /// We use the span information only for local references
156    Local(ItemId, Span),
157}
158
159/// Graph of dependencies between erroring definitions and the definitions they came from.
160struct DepGraph {
161    dgraph: DiGraphMap<DepNode, ()>,
162}
163
164impl DepGraph {
165    fn new() -> Self {
166        DepGraph {
167            dgraph: DiGraphMap::new(),
168        }
169    }
170
171    fn insert_node(&mut self, n: DepNode) {
172        // We have to be careful about duplicate nodes
173        if !self.dgraph.contains_node(n) {
174            self.dgraph.add_node(n);
175        }
176    }
177
178    fn insert_edge(&mut self, from: DepNode, to: DepNode) {
179        self.insert_node(from);
180        self.insert_node(to);
181        if !self.dgraph.contains_edge(from, to) {
182            self.dgraph.add_edge(from, to, ());
183        }
184    }
185}
186
187impl std::fmt::Display for DepGraph {
188    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
189        for (from, to, _) in self.dgraph.all_edges() {
190            writeln!(f, "{from:?} -> {to:?}")?
191        }
192        Ok(())
193    }
194}
195
196/// The context for tracking and reporting errors.
197pub struct ErrorCtx {
198    /// If true, do not abort on the first error and attempt to extract as much as possible.
199    pub continue_on_failure: bool,
200    /// If true, print the warnings as errors, and abort if any errors were raised.
201    pub error_on_warnings: bool,
202
203    /// The ids of the external_declarations for which extraction we encountered errors.
204    pub external_decls_with_errors: HashSet<ItemId>,
205    /// Graph of dependencies between items: there is an edge from item `a` to item `b` if `b`
206    /// registered the id for `a` during its translation. Because we only use this to report errors
207    /// on external items, we only record edges where `a` is an external item.
208    external_dep_graph: DepGraph,
209    /// The id of the definition we are exploring, used to track the source of errors.
210    pub def_id: Option<ItemId>,
211    /// Whether the definition being explored is local to the crate or not.
212    pub def_id_is_local: bool,
213    /// The number of errors encountered so far.
214    pub error_count: usize,
215}
216
217impl ErrorCtx {
218    pub fn new(continue_on_failure: bool, error_on_warnings: bool) -> Self {
219        Self {
220            continue_on_failure,
221            error_on_warnings,
222            external_decls_with_errors: HashSet::new(),
223            external_dep_graph: DepGraph::new(),
224            def_id: None,
225            def_id_is_local: false,
226            error_count: 0,
227        }
228    }
229
230    pub fn continue_on_failure(&self) -> bool {
231        self.continue_on_failure
232    }
233    pub fn has_errors(&self) -> bool {
234        self.error_count > 0
235    }
236
237    /// Report an error without registering anything.
238    pub fn display_error(
239        &self,
240        krate: &TranslatedCrate,
241        span: Span,
242        level: Level,
243        msg: String,
244    ) -> Error {
245        let error = Error { span, msg };
246        anstream::eprintln!("{}\n", error.render(krate, level));
247        if BACKTRACE_ON_ERR {
248            let backtrace = std::backtrace::Backtrace::force_capture();
249            eprintln!("{backtrace}\n");
250        }
251        error
252    }
253
254    /// Report and register an error.
255    pub fn span_err(
256        &mut self,
257        krate: &TranslatedCrate,
258        span: Span,
259        msg: &str,
260        level: Level,
261    ) -> Error {
262        let level = if level == Level::WARNING && self.error_on_warnings {
263            Level::ERROR
264        } else {
265            level
266        };
267        let err = self.display_error(krate, span, level, msg.to_string());
268        self.error_count += 1;
269        // If this item comes from an external crate, after the first error for that item we
270        // display where in the local crate that item was reached from.
271        if !self.def_id_is_local
272            && let Some(id) = self.def_id
273            && self.external_decls_with_errors.insert(id)
274        {
275            self.report_external_dep_error(krate, id);
276        }
277        if !self.continue_on_failure() {
278            panic!("{msg}");
279        }
280        err
281    }
282
283    /// Register the fact that `id` is a dependency of `src` (if `src` is not `None`).
284    pub fn register_dep_source(
285        &mut self,
286        src: &Option<DepSource>,
287        item_id: ItemId,
288        is_local: bool,
289    ) {
290        if let Some(src) = src
291            && src.src_id != item_id
292            && !is_local
293        {
294            let src_node = DepNode::External(item_id);
295            self.external_dep_graph.insert_node(src_node);
296
297            let tgt_node = match src.span {
298                Some(span) => DepNode::Local(src.src_id, span),
299                None => DepNode::External(src.src_id),
300            };
301            self.external_dep_graph.insert_edge(src_node, tgt_node)
302        }
303    }
304
305    /// In case errors happened when extracting the definitions coming from the external
306    /// dependencies, print a detailed report to explain to the user which dependencies were
307    /// problematic, and where they are used in the code.
308    pub fn report_external_dep_error(&self, krate: &TranslatedCrate, id: ItemId) {
309        use annotate_snippets::*;
310
311        // Use `Dijkstra's` algorithm to find the local items reachable from the current non-local
312        // item.
313        let graph = &self.external_dep_graph;
314        let reachable = dijkstra(&graph.dgraph, DepNode::External(id), None, |_| 1);
315        trace!("id: {:?}\nreachable:\n{:?}", id, reachable);
316
317        // Collect reachable local spans.
318        let by_file: HashMap<FileId, Vec<Span>> = reachable
319            .iter()
320            .filter_map(|(n, _)| match n {
321                DepNode::External(_) => None,
322                DepNode::Local(_, span) => Some(*span),
323            })
324            .into_group_map_by(|span| span.data.file_id);
325
326        // Collect to a `Vec` to be able to sort it and to borrow `origin` (needed by
327        // `Snippet::source`).
328        let mut by_file: Vec<(FileId, _, _, Vec<Span>)> = by_file
329            .into_iter()
330            .filter_map(|(file_id, mut spans)| {
331                spans.sort(); // Sort spans to display in file order
332                let file = krate.files.get(file_id)?;
333                let source = file.contents.as_ref()?;
334                let file_name = file.name.to_string();
335                Some((file_id, file_name, source, spans))
336            })
337            .collect();
338        // Sort by file id to avoid output instability.
339        by_file.sort_by_key(|(file_id, ..)| *file_id);
340
341        let level = Level::NOTE;
342        let snippets = by_file.iter().map(|(_, origin, source, spans)| {
343            Snippet::source(*source)
344                .path(origin)
345                .fold(true)
346                .annotations(
347                    spans
348                        .iter()
349                        .map(|span| AnnotationKind::Context.span(span.data.to_byte_range(source))),
350                )
351        });
352
353        let msg = format!(
354            "the error occurred when translating `{}`, \
355             which is (transitively) used at the following location(s):",
356            id.with_ctx(&krate.into_fmt())
357        );
358        let message = Group::with_title(level.title(&msg)).elements(snippets);
359        let out = Renderer::styled().render(&[message]).to_string();
360        anstream::eprintln!("{}", out);
361    }
362}