Skip to content

Commit e8868eb

Browse files
author
Tom Lancaster
committed
Fix #596: yr check should notify the users of all errors, not just the first one
1 parent 4b705db commit e8868eb

File tree

4 files changed

+107
-33
lines changed

4 files changed

+107
-33
lines changed

cli/src/commands/check.rs

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -212,40 +212,42 @@ pub fn exec_check(args: &ArgMatches, config: &Config) -> anyhow::Result<()> {
212212

213213
compiler.colorize_errors(io::stdout().is_tty());
214214

215-
match compiler.add_source(src) {
216-
Ok(compiler) => {
217-
if compiler.warnings().is_empty() {
218-
state.files_passed.fetch_add(1, Ordering::Relaxed);
219-
lines.push(format!(
220-
"[ {} ] {}",
221-
"PASS".paint(Green).bold(),
222-
file_path.display()
223-
));
224-
} else {
225-
state.warnings.fetch_add(
226-
compiler.warnings().len(),
227-
Ordering::Relaxed,
228-
);
229-
lines.push(format!(
230-
"[ {} ] {}",
231-
"WARN".paint(Yellow).bold(),
232-
file_path.display()
233-
));
234-
for warning in compiler.warnings() {
235-
eprintln!("{warning}");
236-
}
237-
}
215+
let _ = compiler.add_source(src);
216+
217+
if !compiler.errors().is_empty() {
218+
state.errors.fetch_add(
219+
compiler.errors().len(),
220+
Ordering::Relaxed,
221+
);
222+
lines.push(format!(
223+
"[ {} ] {}",
224+
"FAIL".paint(Red).bold(),
225+
file_path.display()
226+
));
227+
for error in compiler.errors() {
228+
eprintln!("{error}");
238229
}
239-
Err(err) => {
240-
state.errors.fetch_add(1, Ordering::Relaxed);
241-
lines.push(format!(
242-
"[ {} ] {}\n{}",
243-
"FAIL".paint(Red).bold(),
244-
file_path.display(),
245-
err,
246-
));
230+
} else if !compiler.warnings().is_empty() {
231+
state.warnings.fetch_add(
232+
compiler.warnings().len(),
233+
Ordering::Relaxed,
234+
);
235+
lines.push(format!(
236+
"[ {} ] {}",
237+
"WARN".paint(Yellow).bold(),
238+
file_path.display()
239+
));
240+
for warning in compiler.warnings() {
241+
eprintln!("{warning}");
247242
}
248-
};
243+
} else {
244+
state.files_passed.fetch_add(1, Ordering::Relaxed);
245+
lines.push(format!(
246+
"[ {} ] {}",
247+
"PASS".paint(Green).bold(),
248+
file_path.display()
249+
));
250+
}
249251

250252
output.send(Message::Info(lines.join("\n")))?;
251253

cli/src/tests/check.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,64 @@ fn config_error() {
203203
)
204204
);
205205
}
206+
207+
#[test]
208+
fn check_reports_all_errors() {
209+
let temp_dir = TempDir::new().unwrap();
210+
let config_file = temp_dir.child("config.toml");
211+
212+
config_file
213+
.write_str(
214+
r#"
215+
[check.rule_name]
216+
regexp = "^[A-Z][a-zA-Z0-9]+_[A-Z][a-zA-Z0-9_]+$"
217+
error = true
218+
219+
[check.metadata.author]
220+
type = "string"
221+
required = true
222+
error = true
223+
224+
[check.metadata.severity]
225+
type = "string"
226+
regexp = "^(low|medium|high|critical)$"
227+
required = true
228+
error = true
229+
"#,
230+
)
231+
.unwrap();
232+
233+
let yar_file = temp_dir.child("test.yar");
234+
235+
yar_file
236+
.write_str(
237+
r#"rule bad_rule {
238+
meta:
239+
description = "test"
240+
strings:
241+
$a = "test"
242+
condition:
243+
$a
244+
}"#,
245+
)
246+
.unwrap();
247+
248+
// All three errors should be reported, not just the first one.
249+
Command::new(cargo_bin!("yr"))
250+
.arg("--config")
251+
.arg(config_file.path())
252+
.arg("check")
253+
.arg(yar_file.path())
254+
.assert()
255+
.failure()
256+
.code(1)
257+
.stderr(predicate::str::contains(
258+
"required metadata `author` not found",
259+
))
260+
.stderr(predicate::str::contains(
261+
"required metadata `severity` not found",
262+
))
263+
.stderr(predicate::str::contains(
264+
"rule name does not match regex",
265+
));
266+
}

lib/src/compiler/mod.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1436,6 +1436,7 @@ impl Compiler<'_> {
14361436
}
14371437

14381438
// Check the rule with all the linters.
1439+
let mut first_linter_err: Option<CompileError> = None;
14391440
for linter in self.linters.iter() {
14401441
match linter.check(&self.report_builder, rule) {
14411442
LinterResult::Ok => {}
@@ -1447,9 +1448,18 @@ impl Compiler<'_> {
14471448
self.warnings.add(|| warning);
14481449
}
14491450
}
1450-
LinterResult::Err(err) => return Err(err),
1451+
LinterResult::Err(err) => {
1452+
if first_linter_err.is_none() {
1453+
first_linter_err = Some(err);
1454+
} else {
1455+
self.errors.push(err);
1456+
}
1457+
}
14511458
}
14521459
}
1460+
if let Some(err) = first_linter_err {
1461+
return Err(err);
1462+
}
14531463

14541464
// Take snapshot of the current compiler state. In case of error
14551465
// compiling the current rule this snapshot allows restoring the

site/content/docs/cli/commands.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
---
23
title: "Commands"
34
description: "The commands supported by the YARA-X CLI"

0 commit comments

Comments
 (0)