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