Skip to content

Commit 13c596e

Browse files
authored
process_wrapper: Convert non-JSON diagnostics from LLVM to JSON. (#3309)
Even when rustc is run with `--error-format=json`, LLVM may still [output human-readable diagnostics](https://github.com/llvm/llvm-project/blob/680391f07a45272bb9bfd385cf4c6846b8be32dd/llvm/lib/MC/MCSubtargetInfo.cpp#L82) that are not formatted as JSON. So far, process_wrapper has choked on these. This patch detects these lines and converts them to JSON so that they can be processed like JSON diagnostics from rustc itself. The correct fundamental fix would likely be to teach rustc itself to perform this conversion, but this is a change that would require some time to implement, so we're putting this fix in place instead.
1 parent 2b98caf commit 13c596e

File tree

1 file changed

+185
-7
lines changed

1 file changed

+185
-7
lines changed

util/process_wrapper/main.rs

Lines changed: 185 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@ mod output;
1818
mod rustc;
1919
mod util;
2020

21+
use std::collections::HashMap;
2122
use std::fmt;
2223
use std::fs::{copy, OpenOptions};
2324
use std::io;
2425
use std::process::{exit, Command, ExitStatus, Stdio};
2526

27+
use tinyjson::JsonValue;
28+
2629
use crate::options::options;
2730
use crate::output::{process_output, LineOutput};
31+
use crate::rustc::ErrorFormat;
2832

2933
#[cfg(windows)]
3034
fn status_code(status: ExitStatus, was_killed: bool) -> i32 {
@@ -69,6 +73,48 @@ macro_rules! log {
6973
};
7074
}
7175

76+
fn json_warning(line: &str) -> JsonValue {
77+
JsonValue::Object(HashMap::from([
78+
(
79+
"$message_type".to_string(),
80+
JsonValue::String("diagnostic".to_string()),
81+
),
82+
("message".to_string(), JsonValue::String(line.to_string())),
83+
("code".to_string(), JsonValue::Null),
84+
(
85+
"level".to_string(),
86+
JsonValue::String("warning".to_string()),
87+
),
88+
("spans".to_string(), JsonValue::Array(Vec::new())),
89+
("children".to_string(), JsonValue::Array(Vec::new())),
90+
("rendered".to_string(), JsonValue::String(line.to_string())),
91+
]))
92+
}
93+
94+
fn process_line(
95+
mut line: String,
96+
quit_on_rmeta: bool,
97+
format: ErrorFormat,
98+
metadata_emitted: &mut bool,
99+
) -> Result<LineOutput, String> {
100+
// LLVM can emit lines that look like the following, and these will be interspersed
101+
// with the regular JSON output. Arguably, rustc should be fixed not to emit lines
102+
// like these (or to convert them to JSON), but for now we convert them to JSON
103+
// ourselves.
104+
if line.contains("is not a recognized feature for this target (ignoring feature)") {
105+
if let Ok(json_str) = json_warning(&line).stringify() {
106+
line = json_str;
107+
} else {
108+
return Ok(LineOutput::Skip);
109+
}
110+
}
111+
if quit_on_rmeta {
112+
rustc::stop_on_rmeta_completion(line, format, metadata_emitted)
113+
} else {
114+
rustc::process_json(line, format)
115+
}
116+
}
117+
72118
fn main() -> Result<(), ProcessWrapperError> {
73119
let opts = options().map_err(|e| ProcessWrapperError(e.to_string()))?;
74120

@@ -135,13 +181,7 @@ fn main() -> Result<(), ProcessWrapperError> {
135181
&mut child_stderr,
136182
stderr.as_mut(),
137183
output_file.as_mut(),
138-
move |line| {
139-
if quit_on_rmeta {
140-
rustc::stop_on_rmeta_completion(line, format, metadata_emitted)
141-
} else {
142-
rustc::process_json(line, format)
143-
}
144-
},
184+
move |line| process_line(line, quit_on_rmeta, format, metadata_emitted),
145185
);
146186
if me {
147187
// If recv returns Ok(), a signal was sent in this channel so we should terminate the child process.
@@ -188,3 +228,141 @@ fn main() -> Result<(), ProcessWrapperError> {
188228

189229
exit(code)
190230
}
231+
232+
#[cfg(test)]
233+
mod test {
234+
use super::*;
235+
236+
fn parse_json(json_str: &str) -> Result<JsonValue, String> {
237+
json_str.parse::<JsonValue>().map_err(|e| e.to_string())
238+
}
239+
240+
#[test]
241+
fn test_process_line_diagnostic_json() -> Result<(), String> {
242+
let mut metadata_emitted = false;
243+
let LineOutput::Message(msg) = process_line(
244+
r#"
245+
{
246+
"$message_type": "diagnostic",
247+
"rendered": "Diagnostic message"
248+
}
249+
"#
250+
.to_string(),
251+
false,
252+
ErrorFormat::Json,
253+
&mut metadata_emitted,
254+
)?
255+
else {
256+
return Err("Expected a LineOutput::Message".to_string());
257+
};
258+
assert_eq!(
259+
parse_json(&msg)?,
260+
parse_json(
261+
r#"
262+
{
263+
"$message_type": "diagnostic",
264+
"rendered": "Diagnostic message"
265+
}
266+
"#
267+
)?
268+
);
269+
Ok(())
270+
}
271+
272+
#[test]
273+
fn test_process_line_diagnostic_rendered() -> Result<(), String> {
274+
let mut metadata_emitted = false;
275+
let LineOutput::Message(msg) = process_line(
276+
r#"
277+
{
278+
"$message_type": "diagnostic",
279+
"rendered": "Diagnostic message"
280+
}
281+
"#
282+
.to_string(),
283+
/*quit_on_rmeta=*/ false,
284+
ErrorFormat::Rendered,
285+
&mut metadata_emitted,
286+
)?
287+
else {
288+
return Err("Expected a LineOutput::Message".to_string());
289+
};
290+
assert_eq!(msg, "Diagnostic message");
291+
Ok(())
292+
}
293+
294+
#[test]
295+
fn test_process_line_llvm_feature_warning() -> Result<(), String> {
296+
let mut metadata_emitted = false;
297+
let LineOutput::Message(msg) = process_line(
298+
"'+zaamo' is not a recognized feature for this target (ignoring feature)".to_string(),
299+
/*quit_on_rmeta=*/ false,
300+
ErrorFormat::Json,
301+
&mut metadata_emitted,
302+
)?
303+
else {
304+
return Err("Expected a LineOutput::Message".to_string());
305+
};
306+
assert_eq!(
307+
parse_json(&msg)?,
308+
parse_json(
309+
r#"
310+
{
311+
"$message_type": "diagnostic",
312+
"message": "'+zaamo' is not a recognized feature for this target (ignoring feature)",
313+
"code": null,
314+
"level": "warning",
315+
"spans": [],
316+
"children": [],
317+
"rendered": "'+zaamo' is not a recognized feature for this target (ignoring feature)"
318+
}
319+
"#
320+
)?
321+
);
322+
Ok(())
323+
}
324+
325+
#[test]
326+
fn test_process_line_emit_link() -> Result<(), String> {
327+
let mut metadata_emitted = false;
328+
assert!(matches!(
329+
process_line(
330+
r#"
331+
{
332+
"$message_type": "artifact",
333+
"emit": "link"
334+
}
335+
"#
336+
.to_string(),
337+
/*quit_on_rmeta=*/ true,
338+
ErrorFormat::Rendered,
339+
&mut metadata_emitted,
340+
)?,
341+
LineOutput::Skip
342+
));
343+
assert!(!metadata_emitted);
344+
Ok(())
345+
}
346+
347+
#[test]
348+
fn test_process_line_emit_metadata() -> Result<(), String> {
349+
let mut metadata_emitted = false;
350+
assert!(matches!(
351+
process_line(
352+
r#"
353+
{
354+
"$message_type": "artifact",
355+
"emit": "metadata"
356+
}
357+
"#
358+
.to_string(),
359+
/*quit_on_rmeta=*/ true,
360+
ErrorFormat::Rendered,
361+
&mut metadata_emitted,
362+
)?,
363+
LineOutput::Terminate
364+
));
365+
assert!(metadata_emitted);
366+
Ok(())
367+
}
368+
}

0 commit comments

Comments
 (0)