charon_lib/
errors.rs

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