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