rustc_errors/
json.rs

1//! A JSON emitter for errors.
2//!
3//! This works by converting errors to a simplified structural format (see the
4//! structs at the start of the file) and then serializing them. These should
5//! contain as much information about the error as possible.
6//!
7//! The format of the JSON output should be considered *unstable*. For now the
8//! structs at the end of this file (Diagnostic*) specify the error format.
9
10// FIXME: spec the JSON output properly.
11
12use std::error::Report;
13use std::io::{self, Write};
14use std::path::Path;
15use std::sync::{Arc, Mutex};
16use std::vec;
17
18use derive_setters::Setters;
19use rustc_data_structures::sync::IntoDynSyncSend;
20use rustc_error_messages::FluentArgs;
21use rustc_lint_defs::Applicability;
22use rustc_span::Span;
23use rustc_span::hygiene::ExpnData;
24use rustc_span::source_map::{FilePathMapping, SourceMap};
25use serde::Serialize;
26use termcolor::{ColorSpec, WriteColor};
27
28use crate::diagnostic::IsLint;
29use crate::emitter::{
30    ColorConfig, Destination, Emitter, HumanEmitter, HumanReadableErrorType, OutputTheme,
31    TimingEvent, should_show_source_code,
32};
33use crate::registry::Registry;
34use crate::timings::{TimingRecord, TimingSection};
35use crate::translation::{Translator, to_fluent_args};
36use crate::{CodeSuggestion, MultiSpan, SpanLabel, Subdiag, Suggestions, TerminalUrl};
37
38#[cfg(test)]
39mod tests;
40
41#[derive(Setters)]
42pub struct JsonEmitter {
43    #[setters(skip)]
44    dst: IntoDynSyncSend<Box<dyn Write + Send>>,
45    #[setters(skip)]
46    sm: Option<Arc<SourceMap>>,
47    #[setters(skip)]
48    translator: Translator,
49    #[setters(skip)]
50    pretty: bool,
51    ui_testing: bool,
52    ignored_directories_in_source_blocks: Vec<String>,
53    #[setters(skip)]
54    json_rendered: HumanReadableErrorType,
55    color_config: ColorConfig,
56    diagnostic_width: Option<usize>,
57    macro_backtrace: bool,
58    track_diagnostics: bool,
59    terminal_url: TerminalUrl,
60}
61
62impl JsonEmitter {
63    pub fn new(
64        dst: Box<dyn Write + Send>,
65        sm: Option<Arc<SourceMap>>,
66        translator: Translator,
67        pretty: bool,
68        json_rendered: HumanReadableErrorType,
69        color_config: ColorConfig,
70    ) -> JsonEmitter {
71        JsonEmitter {
72            dst: IntoDynSyncSend(dst),
73            sm,
74            translator,
75            pretty,
76            ui_testing: false,
77            ignored_directories_in_source_blocks: Vec::new(),
78            json_rendered,
79            color_config,
80            diagnostic_width: None,
81            macro_backtrace: false,
82            track_diagnostics: false,
83            terminal_url: TerminalUrl::No,
84        }
85    }
86
87    fn emit(&mut self, val: EmitTyped<'_>) -> io::Result<()> {
88        if self.pretty {
89            serde_json::to_writer_pretty(&mut *self.dst, &val)?
90        } else {
91            serde_json::to_writer(&mut *self.dst, &val)?
92        };
93        self.dst.write_all(b"\n")?;
94        self.dst.flush()
95    }
96}
97
98#[derive(Serialize)]
99#[serde(tag = "$message_type", rename_all = "snake_case")]
100enum EmitTyped<'a> {
101    Diagnostic(Diagnostic),
102    Artifact(ArtifactNotification<'a>),
103    SectionTiming(SectionTimestamp<'a>),
104    FutureIncompat(FutureIncompatReport<'a>),
105    UnusedExtern(UnusedExterns<'a>),
106}
107
108impl Emitter for JsonEmitter {
109    fn emit_diagnostic(&mut self, diag: crate::DiagInner, registry: &Registry) {
110        let data = Diagnostic::from_errors_diagnostic(diag, self, registry);
111        let result = self.emit(EmitTyped::Diagnostic(data));
112        if let Err(e) = result {
113            panic!("failed to print diagnostics: {e:?}");
114        }
115    }
116
117    fn emit_artifact_notification(&mut self, path: &Path, artifact_type: &str) {
118        let data = ArtifactNotification { artifact: path, emit: artifact_type };
119        let result = self.emit(EmitTyped::Artifact(data));
120        if let Err(e) = result {
121            panic!("failed to print notification: {e:?}");
122        }
123    }
124
125    fn emit_timing_section(&mut self, record: TimingRecord, event: TimingEvent) {
126        let event = match event {
127            TimingEvent::Start => "start",
128            TimingEvent::End => "end",
129        };
130        let name = match record.section {
131            TimingSection::Linking => "link",
132            TimingSection::Codegen => "codegen",
133        };
134        let data = SectionTimestamp { name, event, timestamp: record.timestamp };
135        let result = self.emit(EmitTyped::SectionTiming(data));
136        if let Err(e) = result {
137            panic!("failed to print timing section: {e:?}");
138        }
139    }
140
141    fn emit_future_breakage_report(&mut self, diags: Vec<crate::DiagInner>, registry: &Registry) {
142        let data: Vec<FutureBreakageItem<'_>> = diags
143            .into_iter()
144            .map(|mut diag| {
145                // Allowed or expected lints don't normally (by definition) emit a lint
146                // but future incompat lints are special and are emitted anyway.
147                //
148                // So to avoid ICEs and confused users we "upgrade" the lint level for
149                // those `FutureBreakageItem` to warn.
150                if matches!(diag.level, crate::Level::Allow | crate::Level::Expect) {
151                    diag.level = crate::Level::Warning;
152                }
153                FutureBreakageItem {
154                    diagnostic: EmitTyped::Diagnostic(Diagnostic::from_errors_diagnostic(
155                        diag, self, registry,
156                    )),
157                }
158            })
159            .collect();
160        let report = FutureIncompatReport { future_incompat_report: data };
161        let result = self.emit(EmitTyped::FutureIncompat(report));
162        if let Err(e) = result {
163            panic!("failed to print future breakage report: {e:?}");
164        }
165    }
166
167    fn emit_unused_externs(&mut self, lint_level: rustc_lint_defs::Level, unused_externs: &[&str]) {
168        let lint_level = lint_level.as_str();
169        let data = UnusedExterns { lint_level, unused_extern_names: unused_externs };
170        let result = self.emit(EmitTyped::UnusedExtern(data));
171        if let Err(e) = result {
172            panic!("failed to print unused externs: {e:?}");
173        }
174    }
175
176    fn source_map(&self) -> Option<&SourceMap> {
177        self.sm.as_deref()
178    }
179
180    fn should_show_explain(&self) -> bool {
181        !self.json_rendered.short()
182    }
183
184    fn translator(&self) -> &Translator {
185        &self.translator
186    }
187}
188
189// The following data types are provided just for serialisation.
190
191#[derive(Serialize)]
192struct Diagnostic {
193    /// The primary error message.
194    message: String,
195    code: Option<DiagnosticCode>,
196    /// "error: internal compiler error", "error", "warning", "note", "help".
197    level: &'static str,
198    spans: Vec<DiagnosticSpan>,
199    /// Associated diagnostic messages.
200    children: Vec<Diagnostic>,
201    /// The message as rustc would render it.
202    rendered: Option<String>,
203}
204
205#[derive(Serialize)]
206struct DiagnosticSpan {
207    file_name: String,
208    byte_start: u32,
209    byte_end: u32,
210    /// 1-based.
211    line_start: usize,
212    line_end: usize,
213    /// 1-based, character offset.
214    column_start: usize,
215    column_end: usize,
216    /// Is this a "primary" span -- meaning the point, or one of the points,
217    /// where the error occurred?
218    is_primary: bool,
219    /// Source text from the start of line_start to the end of line_end.
220    text: Vec<DiagnosticSpanLine>,
221    /// Label that should be placed at this location (if any)
222    label: Option<String>,
223    /// If we are suggesting a replacement, this will contain text
224    /// that should be sliced in atop this span.
225    suggested_replacement: Option<String>,
226    /// If the suggestion is approximate
227    suggestion_applicability: Option<Applicability>,
228    /// Macro invocations that created the code at this span, if any.
229    expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
230}
231
232#[derive(Serialize)]
233struct DiagnosticSpanLine {
234    text: String,
235
236    /// 1-based, character offset in self.text.
237    highlight_start: usize,
238
239    highlight_end: usize,
240}
241
242#[derive(Serialize)]
243struct DiagnosticSpanMacroExpansion {
244    /// span where macro was applied to generate this code; note that
245    /// this may itself derive from a macro (if
246    /// `span.expansion.is_some()`)
247    span: DiagnosticSpan,
248
249    /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
250    macro_decl_name: String,
251
252    /// span where macro was defined (if known)
253    def_site_span: DiagnosticSpan,
254}
255
256#[derive(Serialize)]
257struct DiagnosticCode {
258    /// The error code (e.g. "E1234"), if the diagnostic has one. Or the lint
259    /// name, if it's a lint without an error code.
260    code: String,
261    /// An explanation for the code.
262    explanation: Option<&'static str>,
263}
264
265#[derive(Serialize)]
266struct ArtifactNotification<'a> {
267    /// The path of the artifact.
268    artifact: &'a Path,
269    /// What kind of artifact we're emitting.
270    emit: &'a str,
271}
272
273#[derive(Serialize)]
274struct SectionTimestamp<'a> {
275    /// Name of the section
276    name: &'a str,
277    /// Start/end of the section
278    event: &'a str,
279    /// Opaque timestamp.
280    timestamp: u128,
281}
282
283#[derive(Serialize)]
284struct FutureBreakageItem<'a> {
285    // Always EmitTyped::Diagnostic, but we want to make sure it gets serialized
286    // with "$message_type".
287    diagnostic: EmitTyped<'a>,
288}
289
290#[derive(Serialize)]
291struct FutureIncompatReport<'a> {
292    future_incompat_report: Vec<FutureBreakageItem<'a>>,
293}
294
295// NOTE: Keep this in sync with the equivalent structs in rustdoc's
296// doctest component (as well as cargo).
297// We could unify this struct the one in rustdoc but they have different
298// ownership semantics, so doing so would create wasteful allocations.
299#[derive(Serialize)]
300struct UnusedExterns<'a> {
301    /// The severity level of the unused dependencies lint
302    lint_level: &'a str,
303    /// List of unused externs by their names.
304    unused_extern_names: &'a [&'a str],
305}
306
307impl Diagnostic {
308    /// Converts from `rustc_errors::DiagInner` to `Diagnostic`.
309    fn from_errors_diagnostic(
310        diag: crate::DiagInner,
311        je: &JsonEmitter,
312        registry: &Registry,
313    ) -> Diagnostic {
314        let args = to_fluent_args(diag.args.iter());
315        let sugg_to_diag = |sugg: &CodeSuggestion| {
316            let translated_message =
317                je.translator.translate_message(&sugg.msg, &args).map_err(Report::new).unwrap();
318            Diagnostic {
319                message: translated_message.to_string(),
320                code: None,
321                level: "help",
322                spans: DiagnosticSpan::from_suggestion(sugg, &args, je),
323                children: vec![],
324                rendered: None,
325            }
326        };
327        let sugg = match &diag.suggestions {
328            Suggestions::Enabled(suggestions) => suggestions.iter().map(sugg_to_diag),
329            Suggestions::Sealed(suggestions) => suggestions.iter().map(sugg_to_diag),
330            Suggestions::Disabled => [].iter().map(sugg_to_diag),
331        };
332
333        // generate regular command line output and store it in the json
334
335        // A threadsafe buffer for writing.
336        #[derive(Default, Clone)]
337        struct BufWriter(Arc<Mutex<Vec<u8>>>);
338
339        impl Write for BufWriter {
340            fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
341                self.0.lock().unwrap().write(buf)
342            }
343            fn flush(&mut self) -> io::Result<()> {
344                self.0.lock().unwrap().flush()
345            }
346        }
347        impl WriteColor for BufWriter {
348            fn supports_color(&self) -> bool {
349                false
350            }
351
352            fn set_color(&mut self, _spec: &ColorSpec) -> io::Result<()> {
353                Ok(())
354            }
355
356            fn reset(&mut self) -> io::Result<()> {
357                Ok(())
358            }
359        }
360
361        let translated_message = je.translator.translate_messages(&diag.messages, &args);
362
363        let code = if let Some(code) = diag.code {
364            Some(DiagnosticCode {
365                code: code.to_string(),
366                explanation: registry.try_find_description(code).ok(),
367            })
368        } else if let Some(IsLint { name, .. }) = &diag.is_lint {
369            Some(DiagnosticCode { code: name.to_string(), explanation: None })
370        } else {
371            None
372        };
373        let level = diag.level.to_str();
374        let spans = DiagnosticSpan::from_multispan(&diag.span, &args, je);
375        let mut children: Vec<Diagnostic> = diag
376            .children
377            .iter()
378            .map(|c| Diagnostic::from_sub_diagnostic(c, &args, je))
379            .chain(sugg)
380            .collect();
381        if je.track_diagnostics && diag.span.has_primary_spans() && !diag.span.is_dummy() {
382            children
383                .insert(0, Diagnostic::from_sub_diagnostic(&diag.emitted_at_sub_diag(), &args, je));
384        }
385        let buf = BufWriter::default();
386        let mut dst: Destination = Box::new(buf.clone());
387        let short = je.json_rendered.short();
388        match je.color_config {
389            ColorConfig::Always | ColorConfig::Auto => dst = Box::new(termcolor::Ansi::new(dst)),
390            ColorConfig::Never => {}
391        }
392        HumanEmitter::new(dst, je.translator.clone())
393            .short_message(short)
394            .sm(je.sm.clone())
395            .diagnostic_width(je.diagnostic_width)
396            .macro_backtrace(je.macro_backtrace)
397            .track_diagnostics(je.track_diagnostics)
398            .terminal_url(je.terminal_url)
399            .ui_testing(je.ui_testing)
400            .ignored_directories_in_source_blocks(je.ignored_directories_in_source_blocks.clone())
401            .theme(if let HumanReadableErrorType::Unicode = je.json_rendered {
402                OutputTheme::Unicode
403            } else {
404                OutputTheme::Ascii
405            })
406            .emit_diagnostic(diag, registry);
407        let buf = Arc::try_unwrap(buf.0).unwrap().into_inner().unwrap();
408        let buf = String::from_utf8(buf).unwrap();
409
410        Diagnostic {
411            message: translated_message.to_string(),
412            code,
413            level,
414            spans,
415            children,
416            rendered: Some(buf),
417        }
418    }
419
420    fn from_sub_diagnostic(
421        subdiag: &Subdiag,
422        args: &FluentArgs<'_>,
423        je: &JsonEmitter,
424    ) -> Diagnostic {
425        let translated_message = je.translator.translate_messages(&subdiag.messages, args);
426        Diagnostic {
427            message: translated_message.to_string(),
428            code: None,
429            level: subdiag.level.to_str(),
430            spans: DiagnosticSpan::from_multispan(&subdiag.span, args, je),
431            children: vec![],
432            rendered: None,
433        }
434    }
435}
436
437impl DiagnosticSpan {
438    fn from_span_label(
439        span: SpanLabel,
440        suggestion: Option<(&String, Applicability)>,
441        args: &FluentArgs<'_>,
442        je: &JsonEmitter,
443    ) -> DiagnosticSpan {
444        Self::from_span_etc(
445            span.span,
446            span.is_primary,
447            span.label
448                .as_ref()
449                .map(|m| je.translator.translate_message(m, args).unwrap())
450                .map(|m| m.to_string()),
451            suggestion,
452            je,
453        )
454    }
455
456    fn from_span_etc(
457        span: Span,
458        is_primary: bool,
459        label: Option<String>,
460        suggestion: Option<(&String, Applicability)>,
461        je: &JsonEmitter,
462    ) -> DiagnosticSpan {
463        // obtain the full backtrace from the `macro_backtrace`
464        // helper; in some ways, it'd be better to expand the
465        // backtrace ourselves, but the `macro_backtrace` helper makes
466        // some decision, such as dropping some frames, and I don't
467        // want to duplicate that logic here.
468        let backtrace = span.macro_backtrace();
469        DiagnosticSpan::from_span_full(span, is_primary, label, suggestion, backtrace, je)
470    }
471
472    fn from_span_full(
473        mut span: Span,
474        is_primary: bool,
475        label: Option<String>,
476        suggestion: Option<(&String, Applicability)>,
477        mut backtrace: impl Iterator<Item = ExpnData>,
478        je: &JsonEmitter,
479    ) -> DiagnosticSpan {
480        let empty_source_map;
481        let sm = match &je.sm {
482            Some(s) => s,
483            None => {
484                span = rustc_span::DUMMY_SP;
485                empty_source_map = Arc::new(SourceMap::new(FilePathMapping::empty()));
486                empty_source_map
487                    .new_source_file(std::path::PathBuf::from("empty.rs").into(), String::new());
488                &empty_source_map
489            }
490        };
491        let start = sm.lookup_char_pos(span.lo());
492        // If this goes from the start of a line to the end and the replacement
493        // is an empty string, increase the length to include the newline so we don't
494        // leave an empty line
495        if start.col.0 == 0
496            && let Some((suggestion, _)) = suggestion
497            && suggestion.is_empty()
498            && let Ok(after) = sm.span_to_next_source(span)
499            && after.starts_with('\n')
500        {
501            span = span.with_hi(span.hi() + rustc_span::BytePos(1));
502        }
503        let end = sm.lookup_char_pos(span.hi());
504        let backtrace_step = backtrace.next().map(|bt| {
505            let call_site = Self::from_span_full(bt.call_site, false, None, None, backtrace, je);
506            let def_site_span = Self::from_span_full(
507                sm.guess_head_span(bt.def_site),
508                false,
509                None,
510                None,
511                [].into_iter(),
512                je,
513            );
514            Box::new(DiagnosticSpanMacroExpansion {
515                span: call_site,
516                macro_decl_name: bt.kind.descr(),
517                def_site_span,
518            })
519        });
520
521        DiagnosticSpan {
522            file_name: sm.filename_for_diagnostics(&start.file.name).to_string(),
523            byte_start: start.file.original_relative_byte_pos(span.lo()).0,
524            byte_end: start.file.original_relative_byte_pos(span.hi()).0,
525            line_start: start.line,
526            line_end: end.line,
527            column_start: start.col.0 + 1,
528            column_end: end.col.0 + 1,
529            is_primary,
530            text: DiagnosticSpanLine::from_span(span, je),
531            suggested_replacement: suggestion.map(|x| x.0.clone()),
532            suggestion_applicability: suggestion.map(|x| x.1),
533            expansion: backtrace_step,
534            label,
535        }
536    }
537
538    fn from_multispan(
539        msp: &MultiSpan,
540        args: &FluentArgs<'_>,
541        je: &JsonEmitter,
542    ) -> Vec<DiagnosticSpan> {
543        msp.span_labels()
544            .into_iter()
545            .map(|span_str| Self::from_span_label(span_str, None, args, je))
546            .collect()
547    }
548
549    fn from_suggestion(
550        suggestion: &CodeSuggestion,
551        args: &FluentArgs<'_>,
552        je: &JsonEmitter,
553    ) -> Vec<DiagnosticSpan> {
554        suggestion
555            .substitutions
556            .iter()
557            .flat_map(|substitution| {
558                substitution.parts.iter().map(move |suggestion_inner| {
559                    let span_label =
560                        SpanLabel { span: suggestion_inner.span, is_primary: true, label: None };
561                    DiagnosticSpan::from_span_label(
562                        span_label,
563                        Some((&suggestion_inner.snippet, suggestion.applicability)),
564                        args,
565                        je,
566                    )
567                })
568            })
569            .collect()
570    }
571}
572
573impl DiagnosticSpanLine {
574    fn line_from_source_file(
575        sf: &rustc_span::SourceFile,
576        index: usize,
577        h_start: usize,
578        h_end: usize,
579    ) -> DiagnosticSpanLine {
580        DiagnosticSpanLine {
581            text: sf.get_line(index).map_or_else(String::new, |l| l.into_owned()),
582            highlight_start: h_start,
583            highlight_end: h_end,
584        }
585    }
586
587    /// Creates a list of DiagnosticSpanLines from span - each line with any part
588    /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
589    /// `span` within the line.
590    fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
591        je.sm
592            .as_ref()
593            .and_then(|sm| {
594                let lines = sm.span_to_lines(span).ok()?;
595                // We can't get any lines if the source is unavailable.
596                if !should_show_source_code(
597                    &je.ignored_directories_in_source_blocks,
598                    &sm,
599                    &lines.file,
600                ) {
601                    return None;
602                }
603
604                let sf = &*lines.file;
605                let span_lines = lines
606                    .lines
607                    .iter()
608                    .map(|line| {
609                        DiagnosticSpanLine::line_from_source_file(
610                            sf,
611                            line.line_index,
612                            line.start_col.0 + 1,
613                            line.end_col.0 + 1,
614                        )
615                    })
616                    .collect();
617                Some(span_lines)
618            })
619            .unwrap_or_default()
620    }
621}
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy