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