Skip to content

Commit

Permalink
feat(py): add origin argument to Compiler.add_source method and i…
Browse files Browse the repository at this point in the history
…mplement `Compiler.errors` method
  • Loading branch information
plusvic committed Aug 27, 2024
1 parent 65dc2d9 commit 0f7a232
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 2 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions py/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ crate-type = ["cdylib"]
[dependencies]
pyo3 = { version = "0.21.2", features = ["abi3", "abi3-py38", "extension-module"] }
pyo3-file = "0.8.0"
serde_json = { workspace = true }

protobuf-json-mapping = { workspace = true }
yara-x = { workspace = true }
Expand Down
43 changes: 41 additions & 2 deletions py/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,36 @@ impl Compiler {

/// Adds a YARA source code to be compiled.
///
/// This function can be used multiple times before calling [`Compiler::build`].
fn add_source(&mut self, src: &str) -> PyResult<()> {
/// This function may be invoked multiple times to add several sets of YARA
/// rules before calling [`Compiler::build`]. If the rules provided in
/// `src` contain errors that prevent compilation, the function will raise
/// an exception with the first error encountered. Additionally, the
/// compiler will store this error, along with any others discovered during
/// compilation, which can be accessed using [`Compiler::errors`].
///
/// Even if a previous invocation resulted in a compilation error, you can
/// continue calling this function. In such cases, any rules that failed to
/// compile will not be included in the final compiled set.
///
/// The optional parameter `origin` allows to specify the origin of the
/// source code. This usually receives the path of the file from where the
/// code was read, but it can be any arbitrary string that conveys information
/// about the source code's origin.
fn add_source(
&mut self,
src: &str,
origin: Option<String>,
) -> PyResult<()> {
let mut src = yrx::SourceCode::from(src);

if let Some(origin) = origin.as_ref() {
src = src.with_origin(origin)
}

self.inner
.add_source(src)
.map_err(|err| CompileError::new_err(err.to_string()))?;

Ok(())
}

Expand Down Expand Up @@ -181,6 +206,20 @@ impl Compiler {
);
Rules::new(compiler.build())
}

/// Retrieves all errors generated by the compiler.
///
/// This method returns every error encountered during the compilation,
/// across all invocations of [`Compiler::add_source`].
fn errors<'py>(&'py self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let json = PyModule::import_bound(py, "json")?;
let json_loads = json.getattr("loads")?;
let errors_json = serde_json::to_string_pretty(&self.inner.errors());
let errors_json = errors_json
.map_err(|err| PyValueError::new_err(err.to_string()))?;

json_loads.call((errors_json,), None)
}
}

/// Scans data with already compiled YARA rules.
Expand Down
14 changes: 14 additions & 0 deletions py/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,20 @@ def test_serialization():
assert len(rules.scan(b'').matching_rules) == 1


def tests_compiler_errors():
compiler = yara_x.Compiler()

with pytest.raises(yara_x.CompileError):
compiler.add_source('rule foo { condition: bar }')

errors = compiler.errors()

assert len(errors) == 1
assert errors[0]['type'] == "UnknownIdentifier"
assert errors[0]['code'] == "E009"
assert errors[0]['title'] == "unknown identifier `bar`"


def test_console_log():
ok = False

Expand Down

0 comments on commit 0f7a232

Please sign in to comment.