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