]> git.lizzy.rs Git - rust.git/blob - src/tools/compiletest/src/json.rs
Auto merge of #89165 - jkugelman:read-to-end-overallocation, r=joshtriplett
[rust.git] / src / tools / compiletest / src / json.rs
1 //! These structs are a subset of the ones found in `rustc_errors::json`.
2 //! They are only used for deserialization of JSON output provided by libtest.
3
4 use crate::errors::{Error, ErrorKind};
5 use crate::runtest::ProcRes;
6 use serde::Deserialize;
7 use std::path::{Path, PathBuf};
8 use std::str::FromStr;
9
10 #[derive(Deserialize)]
11 struct Diagnostic {
12     message: String,
13     code: Option<DiagnosticCode>,
14     level: String,
15     spans: Vec<DiagnosticSpan>,
16     children: Vec<Diagnostic>,
17     rendered: Option<String>,
18 }
19
20 #[derive(Deserialize)]
21 struct ArtifactNotification {
22     #[allow(dead_code)]
23     artifact: PathBuf,
24 }
25
26 #[derive(Deserialize, Clone)]
27 struct DiagnosticSpan {
28     file_name: String,
29     line_start: usize,
30     line_end: usize,
31     column_start: usize,
32     column_end: usize,
33     is_primary: bool,
34     label: Option<String>,
35     suggested_replacement: Option<String>,
36     expansion: Option<Box<DiagnosticSpanMacroExpansion>>,
37 }
38
39 #[derive(Deserialize)]
40 struct FutureIncompatReport {
41     future_incompat_report: Vec<FutureBreakageItem>,
42 }
43
44 #[derive(Deserialize)]
45 struct FutureBreakageItem {
46     diagnostic: Diagnostic,
47 }
48
49 impl DiagnosticSpan {
50     /// Returns the deepest source span in the macro call stack with a given file name.
51     /// This is either the supplied span, or the span for some macro callsite that expanded to it.
52     fn first_callsite_in_file(&self, file_name: &str) -> &DiagnosticSpan {
53         if self.file_name == file_name {
54             self
55         } else {
56             self.expansion
57                 .as_ref()
58                 .map(|origin| origin.span.first_callsite_in_file(file_name))
59                 .unwrap_or(self)
60         }
61     }
62 }
63
64 #[derive(Deserialize, Clone)]
65 struct DiagnosticSpanMacroExpansion {
66     /// span where macro was applied to generate this code
67     span: DiagnosticSpan,
68
69     /// name of macro that was applied (e.g., "foo!" or "#[derive(Eq)]")
70     macro_decl_name: String,
71 }
72
73 #[derive(Deserialize, Clone)]
74 struct DiagnosticCode {
75     /// The code itself.
76     code: String,
77     /// An explanation for the code.
78     explanation: Option<String>,
79 }
80
81 pub fn rustfix_diagnostics_only(output: &str) -> String {
82     output
83         .lines()
84         .filter(|line| line.starts_with('{') && serde_json::from_str::<Diagnostic>(line).is_ok())
85         .collect()
86 }
87
88 pub fn extract_rendered(output: &str) -> String {
89     output
90         .lines()
91         .filter_map(|line| {
92             if line.starts_with('{') {
93                 if let Ok(diagnostic) = serde_json::from_str::<Diagnostic>(line) {
94                     diagnostic.rendered
95                 } else if let Ok(report) = serde_json::from_str::<FutureIncompatReport>(line) {
96                     if report.future_incompat_report.is_empty() {
97                         None
98                     } else {
99                         Some(format!(
100                             "Future incompatibility report: {}",
101                             report
102                                 .future_incompat_report
103                                 .into_iter()
104                                 .map(|item| {
105                                     format!(
106                                         "Future breakage diagnostic:\n{}",
107                                         item.diagnostic
108                                             .rendered
109                                             .unwrap_or_else(|| "Not rendered".to_string())
110                                     )
111                                 })
112                                 .collect::<String>()
113                         ))
114                     }
115                 } else if serde_json::from_str::<ArtifactNotification>(line).is_ok() {
116                     // Ignore the notification.
117                     None
118                 } else {
119                     print!(
120                         "failed to decode compiler output as json: line: {}\noutput: {}",
121                         line, output
122                     );
123                     panic!()
124                 }
125             } else {
126                 // preserve non-JSON lines, such as ICEs
127                 Some(format!("{}\n", line))
128             }
129         })
130         .collect()
131 }
132
133 pub fn parse_output(file_name: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
134     output.lines().flat_map(|line| parse_line(file_name, line, output, proc_res)).collect()
135 }
136
137 fn parse_line(file_name: &str, line: &str, output: &str, proc_res: &ProcRes) -> Vec<Error> {
138     // The compiler sometimes intermingles non-JSON stuff into the
139     // output.  This hack just skips over such lines. Yuck.
140     if line.starts_with('{') {
141         match serde_json::from_str::<Diagnostic>(line) {
142             Ok(diagnostic) => {
143                 let mut expected_errors = vec![];
144                 push_expected_errors(&mut expected_errors, &diagnostic, &[], file_name);
145                 expected_errors
146             }
147             Err(error) => {
148                 // Ignore the future compat report message - this is handled
149                 // by `extract_rendered`
150                 if serde_json::from_str::<FutureIncompatReport>(line).is_ok() {
151                     vec![]
152                 } else {
153                     proc_res.fatal(
154                         Some(&format!(
155                             "failed to decode compiler output as json: \
156                          `{}`\nline: {}\noutput: {}",
157                             error, line, output
158                         )),
159                         || (),
160                     );
161                 }
162             }
163         }
164     } else {
165         vec![]
166     }
167 }
168
169 fn push_expected_errors(
170     expected_errors: &mut Vec<Error>,
171     diagnostic: &Diagnostic,
172     default_spans: &[&DiagnosticSpan],
173     file_name: &str,
174 ) {
175     // In case of macro expansions, we need to get the span of the callsite
176     let spans_info_in_this_file: Vec<_> = diagnostic
177         .spans
178         .iter()
179         .map(|span| (span.is_primary, span.first_callsite_in_file(file_name)))
180         .filter(|(_, span)| Path::new(&span.file_name) == Path::new(&file_name))
181         .collect();
182
183     let spans_in_this_file: Vec<_> = spans_info_in_this_file.iter().map(|(_, span)| span).collect();
184
185     let primary_spans: Vec<_> = spans_info_in_this_file
186         .iter()
187         .filter(|(is_primary, _)| *is_primary)
188         .map(|(_, span)| span)
189         .take(1) // sometimes we have more than one showing up in the json; pick first
190         .cloned()
191         .collect();
192     let primary_spans = if primary_spans.is_empty() {
193         // subdiagnostics often don't have a span of their own;
194         // inherit the span from the parent in that case
195         default_spans
196     } else {
197         &primary_spans
198     };
199
200     // We break the output into multiple lines, and then append the
201     // [E123] to every line in the output. This may be overkill.  The
202     // intention was to match existing tests that do things like "//|
203     // found `i32` [E123]" and expect to match that somewhere, and yet
204     // also ensure that `//~ ERROR E123` *always* works. The
205     // assumption is that these multi-line error messages are on their
206     // way out anyhow.
207     let with_code = |span: &DiagnosticSpan, text: &str| {
208         match diagnostic.code {
209             Some(ref code) =>
210             // FIXME(#33000) -- it'd be better to use a dedicated
211             // UI harness than to include the line/col number like
212             // this, but some current tests rely on it.
213             //
214             // Note: Do NOT include the filename. These can easily
215             // cause false matches where the expected message
216             // appears in the filename, and hence the message
217             // changes but the test still passes.
218             {
219                 format!(
220                     "{}:{}: {}:{}: {} [{}]",
221                     span.line_start,
222                     span.column_start,
223                     span.line_end,
224                     span.column_end,
225                     text,
226                     code.code.clone()
227                 )
228             }
229             None =>
230             // FIXME(#33000) -- it'd be better to use a dedicated UI harness
231             {
232                 format!(
233                     "{}:{}: {}:{}: {}",
234                     span.line_start, span.column_start, span.line_end, span.column_end, text
235                 )
236             }
237         }
238     };
239
240     // Convert multi-line messages into multiple expected
241     // errors. We expect to replace these with something
242     // more structured shortly anyhow.
243     let mut message_lines = diagnostic.message.lines();
244     if let Some(first_line) = message_lines.next() {
245         for span in primary_spans {
246             let msg = with_code(span, first_line);
247             let kind = ErrorKind::from_str(&diagnostic.level).ok();
248             expected_errors.push(Error { line_num: span.line_start, kind, msg });
249         }
250     }
251     for next_line in message_lines {
252         for span in primary_spans {
253             expected_errors.push(Error {
254                 line_num: span.line_start,
255                 kind: None,
256                 msg: with_code(span, next_line),
257             });
258         }
259     }
260
261     // If the message has a suggestion, register that.
262     for span in primary_spans {
263         if let Some(ref suggested_replacement) = span.suggested_replacement {
264             for (index, line) in suggested_replacement.lines().enumerate() {
265                 expected_errors.push(Error {
266                     line_num: span.line_start + index,
267                     kind: Some(ErrorKind::Suggestion),
268                     msg: line.to_string(),
269                 });
270             }
271         }
272     }
273
274     // Add notes for the backtrace
275     for span in primary_spans {
276         for frame in &span.expansion {
277             push_backtrace(expected_errors, frame, file_name);
278         }
279     }
280
281     // Add notes for any labels that appear in the message.
282     for span in spans_in_this_file.iter().filter(|span| span.label.is_some()) {
283         expected_errors.push(Error {
284             line_num: span.line_start,
285             kind: Some(ErrorKind::Note),
286             msg: span.label.clone().unwrap(),
287         });
288     }
289
290     // Flatten out the children.
291     for child in &diagnostic.children {
292         push_expected_errors(expected_errors, child, primary_spans, file_name);
293     }
294 }
295
296 fn push_backtrace(
297     expected_errors: &mut Vec<Error>,
298     expansion: &DiagnosticSpanMacroExpansion,
299     file_name: &str,
300 ) {
301     if Path::new(&expansion.span.file_name) == Path::new(&file_name) {
302         expected_errors.push(Error {
303             line_num: expansion.span.line_start,
304             kind: Some(ErrorKind::Note),
305             msg: format!("in this expansion of {}", expansion.macro_decl_name),
306         });
307     }
308
309     for previous_expansion in &expansion.span.expansion {
310         push_backtrace(expected_errors, previous_expansion, file_name);
311     }
312 }