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