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