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