librz/util: implement a bounded interval. (#4977)

This commit is contained in:
Rot127 2025-03-09 07:24:07 +00:00 committed by GitHub
parent 815ec50b17
commit 7b06dbc6a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 246 additions and 0 deletions

View file

@ -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
View 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;
}

View file

@ -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',

View file

@ -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;
}