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