Skip to content

Commit

Permalink
feat: expose compiler warnings in Golang and Python APIs.
Browse files Browse the repository at this point in the history
  • Loading branch information
plusvic committed Aug 28, 2024
1 parent a4b1ff5 commit 2a4188d
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 8 deletions.
42 changes: 41 additions & 1 deletion capi/include/yara_x.h
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ enum YRX_RESULT yrx_compiler_define_global_float(struct YRX_COMPILER *compiler,
//
// * type: A string that describes the type of error.
// * code: Error code (e.g: "E009").
// * title: Error title (e.g: ""unknown identifier `foo`").
// * title: Error title (e.g: "unknown identifier `foo`").
// * labels: Array of labels.
// * text: The full text of the error report, as shown by the command-line tool.
//
Expand Down Expand Up @@ -367,6 +367,46 @@ enum YRX_RESULT yrx_compiler_define_global_float(struct YRX_COMPILER *compiler,
enum YRX_RESULT yrx_compiler_errors_json(struct YRX_COMPILER *compiler,
struct YRX_BUFFER **buf);

// Returns the warnings encountered during the compilation in JSON format.
//
// In the address indicated by the `buf` pointer, the function will copy a
// `YRX_BUFFER*` pointer. The `YRX_BUFFER` structure represents a buffer
// that contains the JSON representation of the warnings.
//
// The JSON consists on an array of objects, each object representing a
// warning. The object has the following fields:
//
// * type: A string that describes the type of warning.
// * code: Warning code (e.g: "slow_pattern").
// * title: Error title (e.g: "slow pattern").
// * labels: Array of labels.
// * text: The full text of the warning report, as shown by the command-line tool.
//
// Here is an example:
//
// ```json
// [
// {
// "type": "SlowPattern",
// "code": "slow_pattern",
// "title": "slow pattern",
// "labels": [
// {
// "level": "warning",
// "code_origin": null,
// "span": {"start":25,"end":28},
// "text": "this pattern may slow down the scan"
// }
// ],
// "text": "... <full report here> ..."
// }
// ]
// ```
//
// The [`YRX_BUFFER`] must be destroyed with [`yrx_buffer_destroy`].
enum YRX_RESULT yrx_compiler_warnings_json(struct YRX_COMPILER *compiler,
struct YRX_BUFFER **buf);

// Builds the source code previously added to the compiler.
//
// After calling this function the compiler is reset to its initial state,
Expand Down
68 changes: 67 additions & 1 deletion capi/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ pub unsafe extern "C" fn yrx_compiler_define_global_float(
///
/// * type: A string that describes the type of error.
/// * code: Error code (e.g: "E009").
/// * title: Error title (e.g: ""unknown identifier `foo`").
/// * title: Error title (e.g: "unknown identifier `foo`").
/// * labels: Array of labels.
/// * text: The full text of the error report, as shown by the command-line tool.
///
Expand Down Expand Up @@ -331,6 +331,72 @@ pub unsafe extern "C" fn yrx_compiler_errors_json(
}
}

/// Returns the warnings encountered during the compilation in JSON format.
///
/// In the address indicated by the `buf` pointer, the function will copy a
/// `YRX_BUFFER*` pointer. The `YRX_BUFFER` structure represents a buffer
/// that contains the JSON representation of the warnings.
///
/// The JSON consists on an array of objects, each object representing a
/// warning. The object has the following fields:
///
/// * type: A string that describes the type of warning.
/// * code: Warning code (e.g: "slow_pattern").
/// * title: Error title (e.g: "slow pattern").
/// * labels: Array of labels.
/// * text: The full text of the warning report, as shown by the command-line tool.
///
/// Here is an example:
///
/// ```json
/// [
/// {
/// "type": "SlowPattern",
/// "code": "slow_pattern",
/// "title": "slow pattern",
/// "labels": [
/// {
/// "level": "warning",
/// "code_origin": null,
/// "span": {"start":25,"end":28},
/// "text": "this pattern may slow down the scan"
/// }
/// ],
/// "text": "... <full report here> ..."
/// }
/// ]
/// ```
///
/// The [`YRX_BUFFER`] must be destroyed with [`yrx_buffer_destroy`].
#[no_mangle]
pub unsafe extern "C" fn yrx_compiler_warnings_json(
compiler: *mut YRX_COMPILER,
buf: &mut *mut YRX_BUFFER,
) -> YRX_RESULT {
let compiler = if let Some(compiler) = compiler.as_mut() {
compiler
} else {
return YRX_RESULT::INVALID_ARGUMENT;
};

match serde_json::to_vec(compiler.inner.warnings()) {
Ok(json) => {
let json = json.into_boxed_slice();
let mut json = ManuallyDrop::new(json);
*buf = Box::into_raw(Box::new(YRX_BUFFER {
data: json.as_mut_ptr(),
length: json.len(),
}));
_yrx_set_last_error::<SerializationError>(None);
YRX_RESULT::SUCCESS
}
Err(err) => {
_yrx_set_last_error(Some(err));
YRX_RESULT::SERIALIZATION_ERROR
}
}
}

/// Builds the source code previously added to the compiler.
///
/// After calling this function the compiler is reset to its initial state,
Expand Down
35 changes: 34 additions & 1 deletion go/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,18 @@ type CompileError struct {
Text string
}

// Warning represents each of the warnings returned by [Compiler.Warnings].
type Warning struct {
// Error code (e.g: "slow_pattern").
Code string
// Error title (e.g: "slow pattern").
Title string
// Each of the labels in the error report.
Labels []Label
// The error's full report, as shown by the command-line tool.
Text string
}

// Label represents a label in a [CompileError].
type Label struct {
// Label's level (e.g: "error", "warning", "info", "note", "help").
Expand Down Expand Up @@ -350,7 +362,6 @@ func (c *Compiler) DefineGlobal(ident string, value interface{}) error {
return nil
}


// Errors that occurred during the compilation, across multiple calls to
// [Compiler.AddSource].
func (c *Compiler) Errors() []CompileError {
Expand All @@ -373,6 +384,28 @@ func (c *Compiler) Errors() []CompileError {
return result
}

// Warnings that occurred during the compilation, across multiple calls to
// [Compiler.AddSource].
func (c *Compiler) Warnings() []Warning {
var buf *C.YRX_BUFFER
if C.yrx_compiler_warnings_json(c.cCompiler, &buf) != C.SUCCESS {
panic("yrx_compiler_warnings_json failed")
}

defer C.yrx_buffer_destroy(buf)
runtime.KeepAlive(c)

jsonWarnings := C.GoBytes(unsafe.Pointer(buf.data), C.int(buf.length))

var result []Warning

if err := json.Unmarshal(jsonWarnings, &result); err != nil {
panic(err)
}

return result
}

// Build creates a [Rules] object containing a compiled version of all the
// YARA rules previously added to the compiler.
//
Expand Down
47 changes: 47 additions & 0 deletions go/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,50 @@ func TestErrors(t *testing.T) {
},
}, c.Errors())
}


func TestWarnings(t *testing.T) {
c, err := NewCompiler()
assert.NoError(t, err)

c.AddSource("rule test { strings: $a = {01 [0-1][0-1] 02 } condition: $a }")

assert.Equal(t, []Warning{
{
Code: "consecutive_jumps",
Title: "consecutive jumps in hex pattern `$a`",
Labels: []Label{
{
Level: "warning",
CodeOrigin: "",
Span: Span { Start: 30, End: 40 },
Text: "these consecutive jumps will be treated as [0-2]",
},
},
Text: `warning[consecutive_jumps]: consecutive jumps in hex pattern `+"`$a`"+`
--> line:1:31
|
1 | rule test { strings: $a = {01 [0-1][0-1] 02 } condition: $a }
| ---------- these consecutive jumps will be treated as [0-2]
|`,
},
{
Code: "slow_pattern",
Title: "slow pattern",
Labels: []Label{
{
Level: "warning",
CodeOrigin: "",
Span: Span { Start: 21, End: 43 },
Text: "this pattern may slow down the scan",
},
},
Text: `warning[slow_pattern]: slow pattern
--> line:1:22
|
1 | rule test { strings: $a = {01 [0-1][0-1] 02 } condition: $a }
| ---------------------- this pattern may slow down the scan
|`,
},
}, c.Warnings())
}
22 changes: 17 additions & 5 deletions py/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,25 @@ impl Compiler {
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)
}

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

/// Scans data with already compiled YARA rules.
Expand Down Expand Up @@ -523,10 +539,6 @@ impl Rules {

Python::with_gil(|py| Py::new(py, Rules::new(rules)))
}

fn warnings(&self) -> Vec<String> {
self.inner.rules.warnings().iter().map(|w| w.to_string()).collect()
}
}

fn scan_results_to_py(
Expand Down
19 changes: 19 additions & 0 deletions py/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,25 @@ def tests_compiler_errors():
assert errors[0]['title'] == "unknown identifier `bar`"


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

compiler.add_source(
'rule test { strings: $a = {01 [0-1][0-1] 02 } condition: $a }')

warnings = compiler.warnings()

assert len(warnings) == 2

assert warnings[0]['type'] == "ConsecutiveJumps"
assert warnings[0]['code'] == "consecutive_jumps"
assert warnings[0]['title'] == "consecutive jumps in hex pattern `$a`"

assert warnings[1]['type'] == "SlowPattern"
assert warnings[1]['code'] == "slow_pattern"
assert warnings[1]['title'] == "slow pattern"


def test_console_log():
ok = False

Expand Down

0 comments on commit 2a4188d

Please sign in to comment.