Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions cli/src/commands/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use std::path::PathBuf;

use anyhow::Context;
use clap::{Arg, ArgAction, ArgMatches, Command, arg, value_parser};
use yansi::Color::Cyan;
use yansi::Paint;

use crate::commands::{
compilation_args, compile_rules, path_with_namespace_parser,
Expand Down Expand Up @@ -32,11 +34,27 @@ pub fn exec_compile(args: &ArgMatches, config: &Config) -> anyhow::Result<()> {
.unwrap();

let output_path = args.get_one::<PathBuf>("output").unwrap();
let rules = compile_rules(rules_path, args, config)?;
let (rules, ignored_rules) = compile_rules(rules_path, args, config)?;

let output_file = File::create(output_path).with_context(|| {
format!("can not write `{}`", output_path.display())
})?;

Ok(rules.serialize_into(&output_file)?)
rules.serialize_into(&output_file)?;

// With `--ignore-invalid-rules` the valid rules are still written, but exit
// with an error listing the ignored rules and reasons.
if !ignored_rules.is_empty() {
eprintln!(
"{} {}",
"note:".paint(Cyan).bold(),
Paint::bold(&"the following rules were ignored:")
);

for (rule_name, reason) in &ignored_rules {
eprintln!(" - {}: {}", rule_name.paint(Cyan).bold(), reason);
}
}

Ok(())
}
2 changes: 1 addition & 1 deletion cli/src/commands/fix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ pub fn exec_fix_warnings(
.get_many::<(Option<String>, PathBuf)>("[NAMESPACE:]RULES_PATH")
.unwrap();

let rules = compile_rules(rules_path, args, config)?;
let (rules, _) = compile_rules(rules_path, args, config)?;

let mut patches_per_origin = HashMap::new();
let mut num_warnings = 0;
Expand Down
37 changes: 32 additions & 5 deletions cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use crate::walk::Draw;
use crate::walk::Walker;
use crate::{APP_HELP_TEMPLATE, commands, help};

use yara_x::{Compiler, Rules, SourceCode};
use yara_x::{Compiler, IgnoredRuleReason, Rules, SourceCode};

pub fn command(name: &'static str) -> Command {
Command::new(name).help_template(
Expand Down Expand Up @@ -216,7 +216,7 @@ pub fn create_compiler<'a>(
Ok(compiler)
}

pub fn compilation_args() -> [Arg; 6] {
pub fn compilation_args() -> [Arg; 7] {
[
arg!(-d --"define")
.help("Define external variable")
Expand All @@ -232,6 +232,8 @@ pub fn compilation_args() -> [Arg; 6] {
.require_equals(true)
.value_delimiter(',')
.action(ArgAction::Append),
arg!(--"ignore-invalid-rules")
.help("Ignore rules that fail to compile and continue with the valid ones"),
arg!(-I --"ignore-module" <MODULE>)
.help("Ignore rules that use the specified module")
.long_help(help::IGNORE_MODULE_LONG_HELP)
Expand All @@ -252,11 +254,12 @@ pub fn compile_rules<'a, P>(
paths: P,
args: &ArgMatches,
config: &Config,
) -> Result<Rules, anyhow::Error>
) -> Result<(Rules, Vec<(String, String)>), anyhow::Error>
where
P: Iterator<Item = &'a (Option<String>, PathBuf)>,
{
let external_vars = get_external_vars(args);
let ignore_invalid_rules = args.get_flag("ignore-invalid-rules");
let mut compiler = create_compiler(external_vars, args, config)?;

let mut pb = if stdout().is_tty() {
Expand Down Expand Up @@ -336,13 +339,37 @@ where
eprintln!("{error}");
}

if !compiler.errors().is_empty() {
let errors_found = !compiler.errors().is_empty();

// Without `--ignore-invalid-rules` any compilation error is fatal. With the
// flag, the rules that compiled correctly are kept (the compiler already
// discards only the individual rules that failed) and compilation
// continues. The errors are still reported above.
if errors_found && !ignore_invalid_rules {
bail!("{} error(s) found", compiler.errors().len());
}

let ignored_rules: Vec<(String, String)> = compiler
.ignored_rules()
.map(|(rule_name, reason)| {
let reason_str = match reason {
IgnoredRuleReason::IgnoredModule(module) => {
format!("depends on ignored module `{module}`")
}
IgnoredRuleReason::IgnoredRule(parent_rule) => {
format!("depends on ignored rule `{parent_rule}`")
}
IgnoredRuleReason::CompileError(err) => {
format!("error: {}", err.title())
}
};
(rule_name.to_string(), reason_str)
})
.collect();

let rules = compiler.build();

Ok(rules)
Ok((rules, ignored_rules))
}

struct CompileState {
Expand Down
2 changes: 1 addition & 1 deletion cli/src/commands/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ pub fn exec_scan(args: &ArgMatches, config: &Config) -> anyhow::Result<()> {

rules
} else {
compile_rules(rules_path, args, config)?
compile_rules(rules_path, args, config)?.0
};

let rules_ref = &rules;
Expand Down
57 changes: 57 additions & 0 deletions cli/src/tests/compile.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use assert_cmd::{Command, cargo_bin};
use assert_fs::TempDir;
use assert_fs::prelude::*;
use predicates::prelude::*;

#[test]
fn ignore_invalid_rules() {
let temp_dir = TempDir::new().unwrap();
let yar_file = temp_dir.child("test.yar");
let yarc_file = temp_dir.child("test.yarc");

yar_file
.write_str(
r#"
rule valid_rule {
condition: true
}
rule invalid_rule {
condition: undefined_var == 1
}
"#,
)
.unwrap();

// Without --ignore-invalid-rules, compilation should fail early.
Command::new(cargo_bin!("yr"))
.arg("compile")
.arg("-o")
.arg(yarc_file.path())
.arg(yar_file.path())
.assert()
.failure()
.stderr(predicate::str::contains("1 error(s) found"));

// With --ignore-invalid-rules, valid rules are compiled to destination file
// and ignored rules are listed in the message, completing with success.
Command::new(cargo_bin!("yr"))
.arg("compile")
.arg("--ignore-invalid-rules")
.arg("-o")
.arg(yarc_file.path())
.arg(yar_file.path())
.assert()
.success()
.stderr(predicate::str::contains("the following rules were ignored:"))
.stderr(predicate::str::contains("invalid_rule"));

// Verify that the compiled rules file was created and contains the valid rule.
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--compiled-rules")
.arg(yarc_file.path())
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout(predicate::str::contains("valid_rule"));
}
1 change: 1 addition & 0 deletions cli/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod check;
mod compile;
#[cfg(feature = "debug-cmd")]
mod debug;
mod deps;
Expand Down
38 changes: 38 additions & 0 deletions cli/src/tests/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,3 +451,41 @@ fn cpu_limit() {
.assert()
.success();
}

#[test]
fn ignore_invalid_rules() {
let temp_dir = TempDir::new().unwrap();
let yar_file = temp_dir.child("test.yar");

yar_file
.write_str(
r#"
rule valid_rule {
condition: true
}
rule invalid_rule {
condition: undefined_var == 1
}
"#,
)
.unwrap();

// Without --ignore-invalid-rules, scan should fail on compile error.
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg(yar_file.path())
.arg("src/tests/testdata/dummy.file")
.assert()
.failure()
.stderr(predicate::str::contains("1 error(s) found"));

// With --ignore-invalid-rules, scan should succeed and match valid_rule.
Command::new(cargo_bin!("yr"))
.arg("scan")
.arg("--ignore-invalid-rules")
.arg(yar_file.path())
.arg("src/tests/testdata/dummy.file")
.assert()
.success()
.stdout(predicate::str::contains("valid_rule"));
}
Loading
Loading