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