use std::io;
use std::path::Path;
use anyhow::Result;
use crate::LintMatch;
use crate::lines::Lines;
pub fn report_terminal(
r#match: &LintMatch,
code: &[u8],
path: &Path,
writer: &mut dyn io::Write,
) -> Result<()> {
let LintMatch {
lint_name,
message,
range,
} = r#match;
writeln!(writer, "warning: [{lint_name}] {message}")?;
let start_row = range.start_point.row;
let end_row = range.end_point.row;
let start_col = range.start_point.col;
let end_col = range.end_point.col;
writeln!(writer, " --> {}:{start_row}:{start_col}", path.display())?;
if range.bytes.is_empty() {
return Ok(())
}
let mut lines = Lines::new(code, range.bytes.start);
let prefix = format!("{:width$} | ", "", width = end_row.to_string().len());
writeln!(writer, "{prefix}")?;
if start_row == end_row {
let lprefix = format!("{start_row} | ");
let line = lines.next().unwrap();
writeln!(writer, "{lprefix}{}", String::from_utf8_lossy(line))?;
writeln!(
writer,
"{prefix}{:indent$}{:^<width$}",
"",
"",
indent = start_col,
width = end_col.saturating_sub(start_col)
)?;
} else {
for (idx, row) in (start_row..=end_row).enumerate() {
let lprefix = format!("{row} | ");
let c = if idx == 0 { "/" } else { "|" };
let line = lines.next().unwrap();
writeln!(writer, "{lprefix} {c} {}", String::from_utf8_lossy(line))?;
}
writeln!(writer, "{prefix} |{:_<width$}^", "", width = end_col)?;
}
writeln!(writer, "{prefix}")?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
use pretty_assertions::assert_eq;
use crate::Point;
use crate::Range;
#[test]
fn empty_range_reporting() {
let code = indoc! { r#"
int main() {}
"# };
let m = LintMatch {
lint_name: "bogus-file-extension".to_string(),
message: "by convention BPF C code should use the file extension '.bpf.c'".to_string(),
range: Range {
bytes: 0..0,
start_point: Point::default(),
end_point: Point::default(),
},
};
let mut report = Vec::new();
let () =
report_terminal(&m, code.as_bytes(), Path::new("./no_bytes.c"), &mut report).unwrap();
let report = String::from_utf8(report).unwrap();
let expected = indoc! { r#"
warning: [bogus-file-extension] by convention BPF C code should use the file extension '.bpf.c'
--> ./no_bytes.c:0:0
"# };
assert_eq!(report, expected);
}
#[test]
fn multi_line_report() {
let code = indoc! { r#"
SEC("tp_btf/sched_switch")
int handle__sched_switch(u64 *ctx) {
bpf_probe_read(
event.comm,
TASK_COMM_LEN,
prev->comm);
return 0;
}
"# };
let m = LintMatch {
lint_name: "probe-read".to_string(),
message: "bpf_probe_read() is deprecated".to_string(),
range: Range {
bytes: 68..140,
start_point: Point { row: 2, col: 4 },
end_point: Point { row: 5, col: 17 },
},
};
let mut report = Vec::new();
let () = report_terminal(&m, code.as_bytes(), Path::new("<stdin>"), &mut report).unwrap();
let report = String::from_utf8(report).unwrap();
let expected = indoc! { r#"
warning: [probe-read] bpf_probe_read() is deprecated
--> <stdin>:2:4
|
2 | / bpf_probe_read(
3 | | event.comm,
4 | | TASK_COMM_LEN,
5 | | prev->comm);
| |_________________^
|
"# };
assert_eq!(report, expected);
}
#[test]
fn terminal_reporting() {
let code = indoc! { r#"
SEC("tp_btf/sched_switch")
int handle__sched_switch(u64 *ctx)
{
struct task_struct *prev = (struct task_struct *)ctx[1];
struct event event = {0};
bpf_probe_read(event.comm, TASK_COMM_LEN, prev->comm);
return 0;
}
"# };
let m = LintMatch {
lint_name: "probe-read".to_string(),
message: "bpf_probe_read() is deprecated".to_string(),
range: Range {
bytes: 160..174,
start_point: Point { row: 6, col: 4 },
end_point: Point { row: 6, col: 18 },
},
};
let mut report = Vec::new();
let () = report_terminal(&m, code.as_bytes(), Path::new("<stdin>"), &mut report).unwrap();
let report = String::from_utf8(report).unwrap();
let expected = indoc! { r#"
warning: [probe-read] bpf_probe_read() is deprecated
--> <stdin>:6:4
|
6 | bpf_probe_read(event.comm, TASK_COMM_LEN, prev->comm);
| ^^^^^^^^^^^^^^
|
"# };
assert_eq!(report, expected);
}
#[test]
fn report_top_most_line() {
let code = indoc! { r#"
SEC("kprobe/test")
int handle__test(void)
{
}
"# };
let m = LintMatch {
lint_name: "unstable-attach-point".to_string(),
message: "kprobe/kretprobe/fentry/fexit are unstable".to_string(),
range: Range {
bytes: 4..17,
start_point: Point { row: 0, col: 4 },
end_point: Point { row: 0, col: 17 },
},
};
let mut report = Vec::new();
let () = report_terminal(&m, code.as_bytes(), Path::new("<stdin>"), &mut report).unwrap();
let report = String::from_utf8(report).unwrap();
let expected = indoc! { r#"
warning: [unstable-attach-point] kprobe/kretprobe/fentry/fexit are unstable
--> <stdin>:0:4
|
0 | SEC("kprobe/test")
| ^^^^^^^^^^^^^
|
"# };
assert_eq!(report, expected);
}
}