librz/util: implement a bounded interval. (#4977)
This commit is contained in:
parent
815ec50b17
commit
7b06dbc6a2
4 changed files with 246 additions and 0 deletions
|
|
@ -13,14 +13,54 @@ extern "C" {
|
|||
* Precondition: 0 <= size < 2**64 and addr + size <= 2**64
|
||||
* Interval range is [addr, addr + size)
|
||||
* e.g. with addr = 10 and size = 5, interval range is [10, 15) where 10 <= x < (10 + 5).
|
||||
*
|
||||
* This interval is usually (though not always!) treated as right open interval.
|
||||
*/
|
||||
typedef struct rz_interval_t {
|
||||
ut64 addr; ///< Start address of the interval.
|
||||
ut64 size; ///< Size of the interval in bytes.
|
||||
} RzInterval;
|
||||
|
||||
typedef enum {
|
||||
RZ_INTERVAL_IN = 0, ///< A value is in the interval.
|
||||
RZ_INTERVAL_OUT, ///< A value is outside of the interval.
|
||||
RZ_INTERVAL_UNDEF, ///< A value affiliation is not defined for the interval.
|
||||
} RzIntervalAffiliation;
|
||||
|
||||
typedef enum rz_interval_bound_t {
|
||||
RZ_INTERVAL_BOUND_CLOSED = 0, ///< A closed interval: `[a, b]`. True if right and left open are NOT set.
|
||||
RZ_INTERVAL_BOUND_RIGHT_OPEN = 1, ///< A right-open interval: `[a, b)`. This is the assumed default interpretation in Rizin.
|
||||
RZ_INTERVAL_BOUND_LEFT_OPEN = 2, ///< A left-open interval: `(a, b]`.
|
||||
RZ_INTERVAL_BOUND_OPEN = 3, ///< An open interval: `(a, b)`. True if right and left open are set.
|
||||
RZ_INTERVAL_BOUND_UNDEF = 4, ///< Undefined.
|
||||
} RzIntervalBound;
|
||||
|
||||
/**
|
||||
* \brief An interval with explicitly defined bounds.
|
||||
*/
|
||||
typedef struct {
|
||||
ut64 a; ///< The left-hand value.
|
||||
ut64 b; ///< The right-hand value.
|
||||
RzIntervalBound bound; ///< Interval bound attribute.
|
||||
} RzIntervalBoundedUt64;
|
||||
|
||||
typedef RzInterval rz_itv_t;
|
||||
|
||||
static inline RzIntervalAffiliation rz_itv_bound_contains_ut64(RzIntervalBoundedUt64 *itv, ut64 value) {
|
||||
switch (itv->bound) {
|
||||
default:
|
||||
return RZ_INTERVAL_UNDEF;
|
||||
case RZ_INTERVAL_BOUND_RIGHT_OPEN:
|
||||
return (itv->a <= value && value < itv->b) ? RZ_INTERVAL_IN : RZ_INTERVAL_OUT;
|
||||
case RZ_INTERVAL_BOUND_LEFT_OPEN:
|
||||
return (itv->a < value && value <= itv->b) ? RZ_INTERVAL_IN : RZ_INTERVAL_OUT;
|
||||
case RZ_INTERVAL_BOUND_OPEN:
|
||||
return (itv->a < value && value < itv->b) ? RZ_INTERVAL_IN : RZ_INTERVAL_OUT;
|
||||
case RZ_INTERVAL_BOUND_CLOSED:
|
||||
return (itv->a <= value && value <= itv->b) ? RZ_INTERVAL_IN : RZ_INTERVAL_OUT;
|
||||
}
|
||||
}
|
||||
|
||||
static inline RzInterval *rz_itv_new(ut64 addr, ut64 size) {
|
||||
RzInterval *itv = RZ_NEW(RzInterval);
|
||||
if (itv) {
|
||||
|
|
@ -83,6 +123,8 @@ static inline RzInterval rz_itv_intersect(RzInterval itv, RzInterval x) {
|
|||
return rai;
|
||||
}
|
||||
|
||||
RZ_API bool rz_itv_str_to_bounded_itv_ut64(RZ_NONNULL const char *itv_str, RZ_OUT RzIntervalBoundedUt64 *out_itv);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
103
librz/util/itv.c
Normal file
103
librz/util/itv.c
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// SPDX-FileCopyrightText: 2025 RizinOrg <info@rizin.re>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#include <rz_vector.h>
|
||||
#include <rz_util/rz_assert.h>
|
||||
#include <rz_util/rz_itv.h>
|
||||
#include <rz_util/rz_num.h>
|
||||
#include <rz_util/rz_regex.h>
|
||||
|
||||
/**
|
||||
* \brief Parses a string to a bounded interval.
|
||||
*
|
||||
* \param itv_str The string describing the interval.
|
||||
* \param out_itv The output interval to write the result into. It is not written in case of error.
|
||||
*
|
||||
* \return True if parsing was a success, false otherwise.
|
||||
*
|
||||
* Example
|
||||
*
|
||||
* ```c
|
||||
* rz_itv_str_to_bounded_itv_ut64("[0,1)", &itv);
|
||||
* assert(itv.a == 0);
|
||||
* assert(itv.b == 1);
|
||||
* assert(itv.bound == RZ_INTERVAL_BOUND_RIGHT_OPEN);
|
||||
*
|
||||
* rz_itv_str_to_bounded_itv_ut64("0x8", &itv);
|
||||
* assert(itv.a == 8);
|
||||
* assert(itv.b == 8);
|
||||
* assert(itv.bound == RZ_INTERVAL_BOUND_CLOSED);
|
||||
* ```
|
||||
*/
|
||||
RZ_API bool rz_itv_str_to_bounded_itv_ut64(RZ_NONNULL const char *itv_str, RZ_OUT RzIntervalBoundedUt64 *out_itv) {
|
||||
rz_return_val_if_fail(itv_str && out_itv, false);
|
||||
if (!itv_str[0]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RzRegex *re_interval = rz_regex_new("(?<left_bound>[([])\\s*(?<a>(0x[a-fA-F0-9]+|[0-9]+))\\s*,\\s*(?<b>(0x[a-fA-F0-9]+|[0-9]+))\\s*(?<right_bound>[])])", RZ_REGEX_EXTENDED, 0, NULL);
|
||||
if (!re_interval) {
|
||||
RZ_LOG_ERROR("Could not build interval regex pattern.\n");
|
||||
return false;
|
||||
}
|
||||
RzPVector *matches = rz_regex_match_first(re_interval, itv_str, RZ_REGEX_ZERO_TERMINATED, 0, RZ_REGEX_DEFAULT);
|
||||
if (!matches || rz_pvector_empty(matches)) {
|
||||
ut64 num = rz_num_get(NULL, itv_str);
|
||||
if (num == 0 && itv_str[0] != '0') {
|
||||
RZ_LOG_ERROR("Failed to parse: '%s'.\n", itv_str);
|
||||
rz_pvector_free(matches);
|
||||
return false;
|
||||
}
|
||||
out_itv->a = num;
|
||||
out_itv->b = num;
|
||||
out_itv->bound = RZ_INTERVAL_BOUND_CLOSED;
|
||||
return true;
|
||||
}
|
||||
int lb_group = rz_regex_get_group_idx_by_name(re_interval, "left_bound");
|
||||
int rb_group = rz_regex_get_group_idx_by_name(re_interval, "right_bound");
|
||||
int a_group = rz_regex_get_group_idx_by_name(re_interval, "a");
|
||||
int b_group = rz_regex_get_group_idx_by_name(re_interval, "b");
|
||||
RzRegexMatch *match;
|
||||
if (!(match = rz_pvector_at(matches, lb_group))) {
|
||||
rz_warn_if_reached();
|
||||
goto error;
|
||||
}
|
||||
bool left_open = itv_str[match->start] == '(';
|
||||
|
||||
if (!(match = rz_pvector_at(matches, rb_group))) {
|
||||
rz_warn_if_reached();
|
||||
goto error;
|
||||
}
|
||||
bool right_open = itv_str[match->start] == ')';
|
||||
|
||||
if (!(match = rz_pvector_at(matches, a_group))) {
|
||||
rz_warn_if_reached();
|
||||
goto error;
|
||||
}
|
||||
ut64 a = rz_num_math(NULL, itv_str + match->start);
|
||||
|
||||
if (!(match = rz_pvector_at(matches, b_group))) {
|
||||
rz_warn_if_reached();
|
||||
goto error;
|
||||
}
|
||||
ut64 b = rz_num_math(NULL, itv_str + match->start);
|
||||
|
||||
if (a > b) {
|
||||
RZ_LOG_ERROR("a > b is not defined.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
out_itv->a = a;
|
||||
out_itv->b = b;
|
||||
out_itv->bound = RZ_INTERVAL_BOUND_CLOSED;
|
||||
out_itv->bound |= left_open ? RZ_INTERVAL_BOUND_LEFT_OPEN : 0;
|
||||
out_itv->bound |= right_open ? RZ_INTERVAL_BOUND_RIGHT_OPEN : 0;
|
||||
rz_pvector_free(matches);
|
||||
rz_regex_free(re_interval);
|
||||
return true;
|
||||
|
||||
error:
|
||||
rz_pvector_free(matches);
|
||||
rz_regex_free(re_interval);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ rz_util_common_sources = [
|
|||
'hex.c',
|
||||
'idpool.c',
|
||||
'intervaltree.c',
|
||||
'itv.c',
|
||||
'json_indent.c',
|
||||
'json_parser.c',
|
||||
'lang_byte_array.c',
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <rz_util.h>
|
||||
#include "minunit.h"
|
||||
#include "rz_util/rz_itv.h"
|
||||
|
||||
bool test_rz_itv_overlap(void) {
|
||||
RzInterval a = { 0 }, b = { 0 };
|
||||
|
|
@ -34,8 +35,107 @@ bool test_rz_itv_overlap(void) {
|
|||
mu_end;
|
||||
}
|
||||
|
||||
bool test_rz_itv_overlap_bounded(void) {
|
||||
RzIntervalBoundedUt64 right_open = { .a = 1, .b = 4, .bound = RZ_INTERVAL_BOUND_RIGHT_OPEN };
|
||||
RzIntervalBoundedUt64 left_open = { .a = 1, .b = 4, .bound = RZ_INTERVAL_BOUND_LEFT_OPEN };
|
||||
RzIntervalBoundedUt64 open = { .a = 1, .b = 4, .bound = RZ_INTERVAL_BOUND_OPEN };
|
||||
RzIntervalBoundedUt64 closed = { .a = 1, .b = 4, .bound = RZ_INTERVAL_BOUND_CLOSED };
|
||||
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&right_open, 0), RZ_INTERVAL_OUT, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&right_open, 1), RZ_INTERVAL_IN, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&right_open, 2), RZ_INTERVAL_IN, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&right_open, 3), RZ_INTERVAL_IN, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&right_open, 4), RZ_INTERVAL_OUT, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&right_open, 5), RZ_INTERVAL_OUT, "Affiliation mismatch");
|
||||
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&left_open, 0), RZ_INTERVAL_OUT, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&left_open, 1), RZ_INTERVAL_OUT, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&left_open, 2), RZ_INTERVAL_IN, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&left_open, 3), RZ_INTERVAL_IN, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&left_open, 4), RZ_INTERVAL_IN, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&left_open, 5), RZ_INTERVAL_OUT, "Affiliation mismatch");
|
||||
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&open, 0), RZ_INTERVAL_OUT, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&open, 1), RZ_INTERVAL_OUT, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&open, 2), RZ_INTERVAL_IN, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&open, 3), RZ_INTERVAL_IN, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&open, 4), RZ_INTERVAL_OUT, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&open, 5), RZ_INTERVAL_OUT, "Affiliation mismatch");
|
||||
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&closed, 0), RZ_INTERVAL_OUT, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&closed, 1), RZ_INTERVAL_IN, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&closed, 2), RZ_INTERVAL_IN, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&closed, 3), RZ_INTERVAL_IN, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&closed, 4), RZ_INTERVAL_IN, "Affiliation mismatch");
|
||||
mu_assert_eq(rz_itv_bound_contains_ut64(&closed, 5), RZ_INTERVAL_OUT, "Affiliation mismatch");
|
||||
mu_end;
|
||||
}
|
||||
|
||||
bool test_rz_itv_str_to_bounded(void) {
|
||||
RzIntervalBoundedUt64 itv = { 0 };
|
||||
itv.a = UT64_MAX;
|
||||
itv.b = UT64_MAX;
|
||||
itv.bound = RZ_INTERVAL_BOUND_UNDEF;
|
||||
|
||||
mu_assert_false(rz_itv_str_to_bounded_itv_ut64("", &itv), "Parsing should have failed");
|
||||
mu_assert_eq(itv.a, UT64_MAX, "Invalid value was not set.");
|
||||
mu_assert_eq(itv.b, UT64_MAX, "Invalid value was not set.");
|
||||
mu_assert_eq(itv.bound, RZ_INTERVAL_BOUND_UNDEF, "Invalid value was not set.");
|
||||
|
||||
mu_assert_false(rz_itv_str_to_bounded_itv_ut64("{0x111,1]", &itv), "Parsing should have failed");
|
||||
mu_assert_eq(itv.a, UT64_MAX, "Invalid value was not set.");
|
||||
mu_assert_eq(itv.b, UT64_MAX, "Invalid value was not set.");
|
||||
mu_assert_eq(itv.bound, RZ_INTERVAL_BOUND_UNDEF, "Invalid value was not set.");
|
||||
|
||||
// a must be smaller than b.
|
||||
mu_assert_false(rz_itv_str_to_bounded_itv_ut64("[0x111,1]", &itv), "Parsing should have failed");
|
||||
mu_assert_eq(itv.a, UT64_MAX, "Invalid value was not set.");
|
||||
mu_assert_eq(itv.b, UT64_MAX, "Invalid value was not set.");
|
||||
mu_assert_eq(itv.bound, RZ_INTERVAL_BOUND_UNDEF, "Invalid value was not set.");
|
||||
|
||||
mu_assert_true(rz_itv_str_to_bounded_itv_ut64("1", &itv), "parsing failed");
|
||||
mu_assert_eq(itv.a, 1, "Left limit was not set");
|
||||
mu_assert_eq(itv.b, 1, "Right limit was not set");
|
||||
mu_assert_eq(itv.bound, RZ_INTERVAL_BOUND_CLOSED, "Bound was not set.");
|
||||
|
||||
mu_assert_true(rz_itv_str_to_bounded_itv_ut64("0", &itv), "parsing failed");
|
||||
mu_assert_eq(itv.a, 0, "Left limit was not set");
|
||||
mu_assert_eq(itv.b, 0, "Right limit was not set");
|
||||
mu_assert_eq(itv.bound, RZ_INTERVAL_BOUND_CLOSED, "Bound was not set.");
|
||||
|
||||
// The empty interval is valid.
|
||||
mu_assert_true(rz_itv_str_to_bounded_itv_ut64("(1,1)", &itv), "parsing failed");
|
||||
mu_assert_eq(itv.a, 1, "Left limit was not set");
|
||||
mu_assert_eq(itv.b, 1, "Right limit was not set");
|
||||
mu_assert_eq(itv.bound, RZ_INTERVAL_BOUND_OPEN, "Bound was not set.");
|
||||
|
||||
mu_assert_true(rz_itv_str_to_bounded_itv_ut64("[0,1]", &itv), "Parsing failed");
|
||||
mu_assert_eq(itv.a, 0, "Left limit was not set");
|
||||
mu_assert_eq(itv.b, 1, "Right limit was not set.");
|
||||
mu_assert_eq(itv.bound, RZ_INTERVAL_BOUND_CLOSED, "Bound was not set.");
|
||||
|
||||
mu_assert_true(rz_itv_str_to_bounded_itv_ut64("[0,0xffFfffFfff)", &itv), "Parsing failed");
|
||||
mu_assert_eq(itv.a, 0, "Left limit was not set");
|
||||
mu_assert_eq(itv.b, 0xffffffffff, "Right limit was not set.");
|
||||
mu_assert_eq(itv.bound, RZ_INTERVAL_BOUND_RIGHT_OPEN, "Bound was not set.");
|
||||
|
||||
mu_assert_true(rz_itv_str_to_bounded_itv_ut64("[0,0xA)", &itv), "Parsing failed");
|
||||
mu_assert_eq(itv.a, 0, "Left limit was not set");
|
||||
mu_assert_eq(itv.b, 10, "Right limit was not set.");
|
||||
mu_assert_eq(itv.bound, RZ_INTERVAL_BOUND_RIGHT_OPEN, "Bound was not set.");
|
||||
|
||||
mu_assert_true(rz_itv_str_to_bounded_itv_ut64("(2,10]", &itv), "Parsing failed");
|
||||
mu_assert_eq(itv.a, 2, "Left limit was not set");
|
||||
mu_assert_eq(itv.b, 10, "Right limit was not set.");
|
||||
mu_assert_eq(itv.bound, RZ_INTERVAL_BOUND_LEFT_OPEN, "Bound was not set.");
|
||||
|
||||
mu_end;
|
||||
}
|
||||
|
||||
bool all_tests(void) {
|
||||
mu_run_test(test_rz_itv_overlap);
|
||||
mu_run_test(test_rz_itv_overlap_bounded);
|
||||
mu_run_test(test_rz_itv_str_to_bounded);
|
||||
return tests_passed != tests_run;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue