]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_errors/src/json.rs
Rollup merge of #91457 - steffahn:additional_test_from_91068, r=jackh726
[rust.git] / compiler / rustc_errors / src / 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
12 use rustc_span::source_map::{FilePathMapping, SourceMap};
13
14 use crate::emitter::{Emitter, HumanReadableErrorType};
15 use crate::registry::Registry;
16 use crate::DiagnosticId;
17 use crate::ToolMetadata;
18 use crate::{CodeSuggestion, SubDiagnostic};
19 use rustc_lint_defs::Applicability;
20
21 use rustc_data_structures::sync::Lrc;
22 use rustc_span::hygiene::ExpnData;
23 use rustc_span::{MultiSpan, Span, SpanLabel};
24 use std::io::{self, Write};
25 use std::path::Path;
26 use std::sync::{Arc, Mutex};
27 use std::vec;
28
29 use rustc_serialize::json::{as_json, as_pretty_json};
30 use rustc_serialize::{Encodable, Encoder};
31
32 #[cfg(test)]
33 mod tests;
34
35 pub struct JsonEmitter {
36     dst: Box<dyn Write + Send>,
37     registry: Option<Registry>,
38     sm: Lrc<SourceMap>,
39     pretty: bool,
40     ui_testing: bool,
41     json_rendered: HumanReadableErrorType,
42     terminal_width: Option<usize>,
43     macro_backtrace: bool,
44 }
45
46 impl JsonEmitter {
47     pub fn stderr(
48         registry: Option<Registry>,
49         source_map: Lrc<SourceMap>,
50         pretty: bool,
51         json_rendered: HumanReadableErrorType,
52         terminal_width: Option<usize>,
53         macro_backtrace: bool,
54     ) -> JsonEmitter {
55         JsonEmitter {
56             dst: Box::new(io::BufWriter::new(io::stderr())),
57             registry,
58             sm: source_map,
59             pretty,
60             ui_testing: false,
61             json_rendered,
62             terminal_width,
63             macro_backtrace,
64         }
65     }
66
67     pub fn basic(
68         pretty: bool,
69         json_rendered: HumanReadableErrorType,
70         terminal_width: Option<usize>,
71         macro_backtrace: bool,
72     ) -> JsonEmitter {
73         let file_path_mapping = FilePathMapping::empty();
74         JsonEmitter::stderr(
75             None,
76             Lrc::new(SourceMap::new(file_path_mapping)),
77             pretty,
78             json_rendered,
79             terminal_width,
80             macro_backtrace,
81         )
82     }
83
84     pub fn new(
85         dst: Box<dyn Write + Send>,
86         registry: Option<Registry>,
87         source_map: Lrc<SourceMap>,
88         pretty: bool,
89         json_rendered: HumanReadableErrorType,
90         terminal_width: Option<usize>,
91         macro_backtrace: bool,
92     ) -> JsonEmitter {
93         JsonEmitter {
94             dst,
95             registry,
96             sm: source_map,
97             pretty,
98             ui_testing: false,
99             json_rendered,
100             terminal_width,
101             macro_backtrace,
102         }
103     }
104
105     pub fn ui_testing(self, ui_testing: bool) -> Self {
106         Self { ui_testing, ..self }
107     }
108 }
109
110 impl Emitter for JsonEmitter {
111     fn emit_diagnostic(&mut self, diag: &crate::Diagnostic) {
112         let data = Diagnostic::from_errors_diagnostic(diag, self);
113         let result = if self.pretty {
114             writeln!(&mut self.dst, "{}", as_pretty_json(&data))
115         } else {
116             writeln!(&mut self.dst, "{}", as_json(&data))
117         }
118         .and_then(|_| self.dst.flush());
119         if let Err(e) = result {
120             panic!("failed to print diagnostics: {:?}", e);
121         }
122     }
123
124     fn emit_artifact_notification(&mut self, path: &Path, artifact_type: &str) {
125         let data = ArtifactNotification { artifact: path, emit: artifact_type };
126         let result = if self.pretty {
127             writeln!(&mut self.dst, "{}", as_pretty_json(&data))
128         } else {
129             writeln!(&mut self.dst, "{}", as_json(&data))
130         }
131         .and_then(|_| self.dst.flush());
132         if let Err(e) = result {
133             panic!("failed to print notification: {:?}", e);
134         }
135     }
136
137     fn emit_future_breakage_report(&mut self, diags: Vec<crate::Diagnostic>) {
138         let data: Vec<FutureBreakageItem> = diags
139             .into_iter()
140             .map(|mut diag| {
141                 if diag.level == crate::Level::Allow {
142                     diag.level = crate::Level::Warning;
143                 }
144                 FutureBreakageItem { diagnostic: Diagnostic::from_errors_diagnostic(&diag, self) }
145             })
146             .collect();
147         let report = FutureIncompatReport { future_incompat_report: data };
148         let result = if self.pretty {
149             writeln!(&mut self.dst, "{}", as_pretty_json(&report))
150         } else {
151             writeln!(&mut self.dst, "{}", as_json(&report))
152         }
153         .and_then(|_| self.dst.flush());
154         if let Err(e) = result {
155             panic!("failed to print future breakage report: {:?}", e);
156         }
157     }
158
159     fn emit_unused_externs(&mut self, lint_level: &str, unused_externs: &[&str]) {
160         let data = UnusedExterns { lint_level, unused_extern_names: unused_externs };
161         let result = if self.pretty {
162             writeln!(&mut self.dst, "{}", as_pretty_json(&data))
163         } else {
164             writeln!(&mut self.dst, "{}", as_json(&data))
165         }
166         .and_then(|_| self.dst.flush());
167         if let Err(e) = result {
168             panic!("failed to print unused externs: {:?}", e);
169         }
170     }
171
172     fn source_map(&self) -> Option<&Lrc<SourceMap>> {
173         Some(&self.sm)
174     }
175
176     fn should_show_explain(&self) -> bool {
177         !matches!(self.json_rendered, HumanReadableErrorType::Short(_))
178     }
179 }
180
181 // The following data types are provided just for serialisation.
182
183 // NOTE: this has a manual implementation of Encodable which needs to be updated in
184 // parallel.
185 struct Diagnostic {
186     /// The primary error message.
187     message: String,
188     code: Option<DiagnosticCode>,
189     /// "error: internal compiler error", "error", "warning", "note", "help".
190     level: &'static str,
191     spans: Vec<DiagnosticSpan>,
192     /// Associated diagnostic messages.
193     children: Vec<Diagnostic>,
194     /// The message as rustc would render it.
195     rendered: Option<String>,
196     /// Extra tool metadata
197     tool_metadata: ToolMetadata,
198 }
199
200 macro_rules! encode_fields {
201     (
202         $enc:expr,                  // encoder
203         $idx:expr,                  // starting field index
204         $struct:expr,               // struct we're serializing
205         $struct_name:ident,         // struct name
206         [ $($name:ident),+$(,)? ],  // fields to encode
207         [ $($ignore:ident),+$(,)? ] // fields we're skipping
208     ) => {
209         {
210             // Pattern match to make sure all fields are accounted for
211             let $struct_name { $($name,)+ $($ignore: _,)+ } = $struct;
212             let mut idx = $idx;
213             $(
214                 $enc.emit_struct_field(
215                     stringify!($name),
216                     idx == 0,
217                     |enc| $name.encode(enc),
218                 )?;
219                 idx += 1;
220             )+
221             idx
222         }
223     };
224 }
225
226 // Special-case encoder to skip tool_metadata if not set
227 impl<E: Encoder> Encodable<E> for Diagnostic {
228     fn encode(&self, s: &mut E) -> Result<(), E::Error> {
229         s.emit_struct(false, |s| {
230             let mut idx = 0;
231
232             idx = encode_fields!(
233                 s,
234                 idx,
235                 self,
236                 Self,
237                 [message, code, level, spans, children, rendered],
238                 [tool_metadata]
239             );
240             if self.tool_metadata.is_set() {
241                 idx = encode_fields!(
242                     s,
243                     idx,
244                     self,
245                     Self,
246                     [tool_metadata],
247                     [message, code, level, spans, children, rendered]
248                 );
249             }
250
251             let _ = idx;
252             Ok(())
253         })
254     }
255 }
256
257 #[derive(Encodable)]
258 struct DiagnosticSpan {
259     file_name: String,
260     byte_start: u32,
261     byte_end: u32,
262     /// 1-based.
263     line_start: usize,
264     line_end: usize,
265     /// 1-based, character offset.
266     column_start: usize,
267     column_end: usize,
268     /// Is this a "primary" span -- meaning the point, or one of the points,
269     /// where the error occurred?
270     is_primary: bool,
271     /// Source text from the start of line_start to the end of line_end.
272     text: Vec<DiagnosticSpanLine>,
273     /// Label that should be placed at this location (if any)
274     label: Option<String>,
275     /// If we are suggesting a replacement, this will contain text
276     /// that should be sliced in atop this span.
277     suggested_replacement: Option<String>,
278     /// If the suggestion is approximate
279     suggestion_applicability: Option<Applicability>,
280     /// Macro invocations that created the code at this span, if any.
281     expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
282 }
283
284 #[derive(Encodable)]
285 struct DiagnosticSpanLine {
286     text: String,
287
288     /// 1-based, character offset in self.text.
289     highlight_start: usize,
290
291     highlight_end: usize,
292 }
293
294 #[derive(Encodable)]
295 struct DiagnosticSpanMacroExpansion {
296     /// span where macro was applied to generate this code; note that
297     /// this may itself derive from a macro (if
298     /// `span.expansion.is_some()`)
299     span: DiagnosticSpan,
300
301     /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
302     macro_decl_name: String,
303
304     /// span where macro was defined (if known)
305     def_site_span: DiagnosticSpan,
306 }
307
308 #[derive(Encodable)]
309 struct DiagnosticCode {
310     /// The code itself.
311     code: String,
312     /// An explanation for the code.
313     explanation: Option<&'static str>,
314 }
315
316 #[derive(Encodable)]
317 struct ArtifactNotification<'a> {
318     /// The path of the artifact.
319     artifact: &'a Path,
320     /// What kind of artifact we're emitting.
321     emit: &'a str,
322 }
323
324 #[derive(Encodable)]
325 struct FutureBreakageItem {
326     diagnostic: Diagnostic,
327 }
328
329 #[derive(Encodable)]
330 struct FutureIncompatReport {
331     future_incompat_report: Vec<FutureBreakageItem>,
332 }
333
334 // NOTE: Keep this in sync with the equivalent structs in rustdoc's
335 // doctest component (as well as cargo).
336 // We could unify this struct the one in rustdoc but they have different
337 // ownership semantics, so doing so would create wasteful allocations.
338 #[derive(Encodable)]
339 struct UnusedExterns<'a, 'b, 'c> {
340     /// The severity level of the unused dependencies lint
341     lint_level: &'a str,
342     /// List of unused externs by their names.
343     unused_extern_names: &'b [&'c str],
344 }
345
346 impl Diagnostic {
347     fn from_errors_diagnostic(diag: &crate::Diagnostic, je: &JsonEmitter) -> Diagnostic {
348         let sugg = diag.suggestions.iter().map(|sugg| Diagnostic {
349             message: sugg.msg.clone(),
350             code: None,
351             level: "help",
352             spans: DiagnosticSpan::from_suggestion(sugg, je),
353             children: vec![],
354             rendered: None,
355             tool_metadata: sugg.tool_metadata.clone(),
356         });
357
358         // generate regular command line output and store it in the json
359
360         // A threadsafe buffer for writing.
361         #[derive(Default, Clone)]
362         struct BufWriter(Arc<Mutex<Vec<u8>>>);
363
364         impl Write for BufWriter {
365             fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
366                 self.0.lock().unwrap().write(buf)
367             }
368             fn flush(&mut self) -> io::Result<()> {
369                 self.0.lock().unwrap().flush()
370             }
371         }
372         let buf = BufWriter::default();
373         let output = buf.clone();
374         je.json_rendered
375             .new_emitter(
376                 Box::new(buf),
377                 Some(je.sm.clone()),
378                 false,
379                 je.terminal_width,
380                 je.macro_backtrace,
381             )
382             .ui_testing(je.ui_testing)
383             .emit_diagnostic(diag);
384         let output = Arc::try_unwrap(output.0).unwrap().into_inner().unwrap();
385         let output = String::from_utf8(output).unwrap();
386
387         Diagnostic {
388             message: diag.message(),
389             code: DiagnosticCode::map_opt_string(diag.code.clone(), je),
390             level: diag.level.to_str(),
391             spans: DiagnosticSpan::from_multispan(&diag.span, je),
392             children: diag
393                 .children
394                 .iter()
395                 .map(|c| Diagnostic::from_sub_diagnostic(c, je))
396                 .chain(sugg)
397                 .collect(),
398             rendered: Some(output),
399             tool_metadata: ToolMetadata::default(),
400         }
401     }
402
403     fn from_sub_diagnostic(diag: &SubDiagnostic, je: &JsonEmitter) -> Diagnostic {
404         Diagnostic {
405             message: diag.message(),
406             code: None,
407             level: diag.level.to_str(),
408             spans: diag
409                 .render_span
410                 .as_ref()
411                 .map(|sp| DiagnosticSpan::from_multispan(sp, je))
412                 .unwrap_or_else(|| DiagnosticSpan::from_multispan(&diag.span, je)),
413             children: vec![],
414             rendered: None,
415             tool_metadata: ToolMetadata::default(),
416         }
417     }
418 }
419
420 impl DiagnosticSpan {
421     fn from_span_label(
422         span: SpanLabel,
423         suggestion: Option<(&String, Applicability)>,
424         je: &JsonEmitter,
425     ) -> DiagnosticSpan {
426         Self::from_span_etc(span.span, span.is_primary, span.label, suggestion, je)
427     }
428
429     fn from_span_etc(
430         span: Span,
431         is_primary: bool,
432         label: Option<String>,
433         suggestion: Option<(&String, Applicability)>,
434         je: &JsonEmitter,
435     ) -> DiagnosticSpan {
436         // obtain the full backtrace from the `macro_backtrace`
437         // helper; in some ways, it'd be better to expand the
438         // backtrace ourselves, but the `macro_backtrace` helper makes
439         // some decision, such as dropping some frames, and I don't
440         // want to duplicate that logic here.
441         let backtrace = span.macro_backtrace();
442         DiagnosticSpan::from_span_full(span, is_primary, label, suggestion, backtrace, je)
443     }
444
445     fn from_span_full(
446         span: Span,
447         is_primary: bool,
448         label: Option<String>,
449         suggestion: Option<(&String, Applicability)>,
450         mut backtrace: impl Iterator<Item = ExpnData>,
451         je: &JsonEmitter,
452     ) -> DiagnosticSpan {
453         let start = je.sm.lookup_char_pos(span.lo());
454         let end = je.sm.lookup_char_pos(span.hi());
455         let backtrace_step = backtrace.next().map(|bt| {
456             let call_site = Self::from_span_full(bt.call_site, false, None, None, backtrace, je);
457             let def_site_span =
458                 Self::from_span_full(bt.def_site, false, None, None, vec![].into_iter(), je);
459             Box::new(DiagnosticSpanMacroExpansion {
460                 span: call_site,
461                 macro_decl_name: bt.kind.descr(),
462                 def_site_span,
463             })
464         });
465
466         DiagnosticSpan {
467             file_name: je.sm.filename_for_diagnostics(&start.file.name).to_string(),
468             byte_start: start.file.original_relative_byte_pos(span.lo()).0,
469             byte_end: start.file.original_relative_byte_pos(span.hi()).0,
470             line_start: start.line,
471             line_end: end.line,
472             column_start: start.col.0 + 1,
473             column_end: end.col.0 + 1,
474             is_primary,
475             text: DiagnosticSpanLine::from_span(span, je),
476             suggested_replacement: suggestion.map(|x| x.0.clone()),
477             suggestion_applicability: suggestion.map(|x| x.1),
478             expansion: backtrace_step,
479             label,
480         }
481     }
482
483     fn from_multispan(msp: &MultiSpan, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
484         msp.span_labels()
485             .into_iter()
486             .map(|span_str| Self::from_span_label(span_str, None, je))
487             .collect()
488     }
489
490     fn from_suggestion(suggestion: &CodeSuggestion, je: &JsonEmitter) -> Vec<DiagnosticSpan> {
491         suggestion
492             .substitutions
493             .iter()
494             .flat_map(|substitution| {
495                 substitution.parts.iter().map(move |suggestion_inner| {
496                     let span_label =
497                         SpanLabel { span: suggestion_inner.span, is_primary: true, label: None };
498                     DiagnosticSpan::from_span_label(
499                         span_label,
500                         Some((&suggestion_inner.snippet, suggestion.applicability)),
501                         je,
502                     )
503                 })
504             })
505             .collect()
506     }
507 }
508
509 impl DiagnosticSpanLine {
510     fn line_from_source_file(
511         sf: &rustc_span::SourceFile,
512         index: usize,
513         h_start: usize,
514         h_end: usize,
515     ) -> DiagnosticSpanLine {
516         DiagnosticSpanLine {
517             text: sf.get_line(index).map_or_else(String::new, |l| l.into_owned()),
518             highlight_start: h_start,
519             highlight_end: h_end,
520         }
521     }
522
523     /// Creates a list of DiagnosticSpanLines from span - each line with any part
524     /// of `span` gets a DiagnosticSpanLine, with the highlight indicating the
525     /// `span` within the line.
526     fn from_span(span: Span, je: &JsonEmitter) -> Vec<DiagnosticSpanLine> {
527         je.sm
528             .span_to_lines(span)
529             .map(|lines| {
530                 // We can't get any lines if the source is unavailable.
531                 if !je.sm.ensure_source_file_source_present(lines.file.clone()) {
532                     return vec![];
533                 }
534
535                 let sf = &*lines.file;
536                 lines
537                     .lines
538                     .iter()
539                     .map(|line| {
540                         DiagnosticSpanLine::line_from_source_file(
541                             sf,
542                             line.line_index,
543                             line.start_col.0 + 1,
544                             line.end_col.0 + 1,
545                         )
546                     })
547                     .collect()
548             })
549             .unwrap_or_else(|_| vec![])
550     }
551 }
552
553 impl DiagnosticCode {
554     fn map_opt_string(s: Option<DiagnosticId>, je: &JsonEmitter) -> Option<DiagnosticCode> {
555         s.map(|s| {
556             let s = match s {
557                 DiagnosticId::Error(s) => s,
558                 DiagnosticId::Lint { name, .. } => name,
559             };
560             let je_result =
561                 je.registry.as_ref().map(|registry| registry.try_find_description(&s)).unwrap();
562
563             DiagnosticCode { code: s, explanation: je_result.unwrap_or(None) }
564         })
565     }
566 }