rizin/test/unit/test_unum.c
Anton Kochkov 689bafb7a4
Rewrite the RzNum parser and calculator on tree-sitter (#4326)
Replace the hand-written parser in calc.c with a tree-sitter grammar
(subprojects/rizin-math-parser) and a typed evaluator. The old parser
could only ever produce a ut64 and folded anything it failed to read to
0, which left callers unable to tell a failed expression from one that
evaluated to zero.

Expressions now evaluate to an RzNumValue, a tagged union over ut64,
double, RzBitVector, arbitrary-precision integer and arbitrary-precision
decimal, carrying an RzNumError rather than signalling failure as 0.
Literals keep the width they were written with (5u8, 0xffu128, any width
from 1 to 65536), results that outgrow 64 bits promote to a big number on
their own, and a parse error, division by zero or unresolved identifier
reaches the caller.

rz_num_math() is deprecated. rz_num_math_ut64() keeps its exact behaviour
for callers that want a ut64, and rz_num_math_value() exposes the typed
result. rz_core_math() adds the RzCore-backed form used by the % command,
with rz_core_math_ut64() deprecated alongside it. rz-ax routes through the
typed API, so it prints values at full precision, reports errors on stderr
and exits non-zero. rz_il_lift_num() converts an expression to an
RzILOpPure, so a numeric argument can be lifted instead of pre-evaluated.

Legacy input still works: trailing base suffixes (101b, 35o, 212t), the
trailing-'h' hex form and the k/m/g scale suffixes are all accepted and
warn once, pointing at the 0b/0o/0t prefixes. doc/math.md documents the
language and doc/math-il-lift.md the lift; the grammar, the evaluator,
rz-ax and the % command are covered by unit and db tests.
2026-07-24 02:57:27 +08:00

2675 lines
94 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-FileCopyrightText: 2017 kriw <kotarou777775@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_util.h>
#include <rz_il/rz_il_opcodes.h>
#include <math.h>
#include "minunit.h"
static RzNum *num;
bool test_rz_num_units() {
char humansz[8];
const struct {
const char *expected_res;
const char *message;
ut64 num;
} test_cases[] = {
{ "0", "B", 0ULL },
{ "512", "B", 512ULL },
{ "1K", "K", 1ULL << 10 },
{ "1M", "M", 1ULL << 20 },
{ "1G", "G", 1ULL << 30 },
{ "1T", "T", 1ULL << 40 },
{ "1P", "P", 1ULL << 50 },
{ "1E", "E", 1ULL << 60 },
/* Decimal test */
{ "1.0K", "K", 1025 },
{ "994K", "K", 994 * (1ULL << 10) },
{ "999K", "K", 999 * (1ULL << 10) },
{ "1.0M", "M", 1025 * (1ULL << 10) },
{ "1.5M", "M", 1536 * (1ULL << 10) },
{ "1.9M", "M", 1996 * (1ULL << 10) },
{ "2.0M", "M", 1997 * (1ULL << 10) },
{ "2.0M", "M", 2047 * (1ULL << 10) },
{ "2M", "M", 2048 * (1ULL << 10) },
{ "2.0M", "M", 2099 * (1ULL << 10) },
{ "2.1M", "M", 2100 * (1ULL << 10) },
{ "9.9G", "G", 10188 * (1ULL << 20) },
/* Biggest units */
{ "82P", "P", 82 * (1ULL << 50) },
{ "16E", "E", UT64_MAX }
};
size_t nitems = sizeof(test_cases) / sizeof(test_cases[0]);
size_t i;
for (i = 0; i < nitems; i++) {
rz_num_units(humansz, sizeof(humansz), test_cases[i].num);
mu_assert_streq(humansz, test_cases[i].expected_res, test_cases[i].message);
}
mu_end;
}
bool test_rz_num_minmax_swap_i() {
int a = -1, b = 2;
rz_num_minmax_swap_i(&a, &b);
mu_assert_eq(a == -1 && b == 2, 1, "a < b -> a < b");
a = 2, b = -1;
rz_num_minmax_swap_i(&a, &b);
mu_assert_eq(a == -1 && b == 2, 1, "b < a -> a < b");
mu_end;
}
bool test_rz_num_minmax_swap() {
ut64 a = 1, b = 2;
rz_num_minmax_swap(&a, &b);
mu_assert_eq(a == 1 && b == 2, 1, "a < b -> a < b");
a = 2, b = 1;
rz_num_minmax_swap(&a, &b);
mu_assert_eq(a == 1 && b == 2, 1, "b < a -> a < b");
mu_end;
}
bool test_rz_num_between() {
mu_assert_eq(rz_num_between(num, "1 2 3"), 1, "1 <= 2 <= 3");
mu_assert_eq(rz_num_between(num, "3 2 1"), 0, "3 <= 2 <= 1");
mu_assert_eq(rz_num_between(num, "1 1 1"), 1, "1 <= 1 <= 1");
mu_assert_eq(rz_num_between(num, "2 1 3"), 0, "2 <= 1 <= 3");
mu_assert_eq(rz_num_between(num, "1 2 1+2"), 1, "1 <= 2 <= 1+2");
mu_assert_eq(rz_num_between(num, "2 3 1+2+3"), 1, "2 <= 3 <= 1+2+3");
mu_assert_eq(rz_num_between(num, "1+2 2 1+1"), 0, "1+2 <= 2 <= 1+1");
mu_assert_eq(rz_num_between(num, "1 + 2 2 1 + 1"), 0, "1 + 2 <= 2 <= 1 + 1");
mu_end;
}
bool test_rz_num_str_len() {
mu_assert_eq(rz_num_str_len("1"), 1, "\"1\"");
mu_assert_eq(rz_num_str_len("1+1"), 3, "\"1+1\"");
mu_assert_eq(rz_num_str_len("1 + 1"), 5, "\"1 + 1\"");
mu_assert_eq(rz_num_str_len("1 + 1 "), 5, "\"1 + 1 \"");
mu_assert_eq(rz_num_str_len("1 + 1 "), 5, "\"1 + 1 \"");
mu_assert_eq(rz_num_str_len("1 + 1 1"), 5, "\"1 + 1 1\"");
mu_assert_eq(rz_num_str_len("1 + 1 1 + 1"), 5, "\"1 + 1 1 + 1\"");
mu_assert_eq(rz_num_str_len("1 + (1 + 1) 1"), 11, "\"1 + (1 + 1) 1\"");
mu_assert_eq(rz_num_str_len("1 + (1 + (1 + 1)) 1"), 17, "\"1 + (1 + (1 + 1)) 1\"");
mu_assert_eq(rz_num_str_len("1+(1+(1+1)) 1"), 11, "\"1+(1+(1+1)) 1\"");
mu_assert_eq(rz_num_str_len("(1 + 1) + (1 + 1) 1"), 17, "\"(1 + 1) + (1 + 1) 1\"");
mu_assert_eq(rz_num_str_len("(1+1)+(1+1) 1"), 11, "\"(1+1)+(1+1) 1\"");
mu_end;
}
bool test_rz_num_str_split() {
char *str = malloc(0x20);
strcpy(str, "1 1 + 2 1 + (2 + 3) 4 ");
// expected "1\01 + 2\01 + (2 + 3)\04\0"
int count = rz_num_str_split(str);
mu_assert_eq(count, 4, "rz_num_str_split (str) == 4");
mu_assert_streq(str + 0, "1", "1");
mu_assert_streq(str + 2, "1 + 2", "1 + 2");
mu_assert_streq(str + 8, "1 + (2 + 3)", "1 + (2 + 3)");
mu_assert_streq(str + 20, "4", "4");
free(str);
mu_end;
}
bool test_rz_num_str_split_list() {
char *s;
char *str = malloc(0x20);
strcpy(str, "1 1 + 2 1 + (2 + 3) 4 ");
// expected {"1", "1 + 2", "1 + (2 + 3)", "4"} as list
RzList *list = rz_num_str_split_list(str);
mu_assert_eq(rz_list_length(list), 4, "rz_list_length (list) == 4");
s = (char *)rz_list_pop_head(list);
mu_assert_streq(s, "1", "1");
s = (char *)rz_list_pop_head(list);
mu_assert_streq(s, "1 + 2", "1 + 2");
s = (char *)rz_list_pop_head(list);
mu_assert_streq(s, "1 + (2 + 3)", "1 + (2 + 3)");
s = (char *)rz_list_pop_head(list);
mu_assert_streq(s, "4", "4");
free(str);
rz_list_free(list);
mu_end;
}
bool test_rz_num_align_delta() {
ut64 d = rz_num_align_delta(0, 8);
mu_assert_eq(d, 0, "align delta");
d = rz_num_align_delta(3, 8);
mu_assert_eq(d, 5, "align delta");
d = rz_num_align_delta(0x10, 8);
mu_assert_eq(d, 0, "align delta");
d = rz_num_align_delta(0x11, 8);
mu_assert_eq(d, 7, "align delta");
d = rz_num_align_delta(0x42, 0);
mu_assert_eq(d, 0, "align delta");
mu_end;
}
bool test_rz_num_bitmask() {
static const ut64 expect_masks[] = {
0x0, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f, 0xff, 0x1ff, 0x3ff, 0x7ff,
0xfff, 0x1fff, 0x3fff, 0x7fff, 0xffff, 0x1ffff, 0x3ffff, 0x7ffff,
0xfffff, 0x1fffff, 0x3fffff, 0x7fffff, 0xffffff, 0x1ffffffLL, 0x3ffffffLL,
0x7ffffffLL, 0xfffffffLL, 0x1fffffffLL, 0x3fffffffLL, 0x7fffffffLL, 0xffffffffLL,
0x1ffffffffLL, 0x3ffffffffLL, 0x7ffffffffLL, 0xfffffffffLL, 0x1fffffffffLL,
0x3fffffffffLL, 0x7fffffffffLL, 0xffffffffffLL, 0x1ffffffffffLL, 0x3ffffffffffLL,
0x7ffffffffffLL, 0xfffffffffffLL, 0x1fffffffffffLL, 0x3fffffffffffLL, 0x7fffffffffffLL,
0xffffffffffffLL, 0x1ffffffffffffLL, 0x3ffffffffffffLL, 0x7ffffffffffffLL,
0xfffffffffffffLL, 0x1fffffffffffffLL, 0x3fffffffffffffLL, 0x7fffffffffffffLL,
0xffffffffffffffLL, 0x1ffffffffffffffLL, 0x3ffffffffffffffLL, 0x7ffffffffffffffLL,
0xfffffffffffffffLL, 0x1fffffffffffffffLL, 0x3fffffffffffffffLL, 0x7fffffffffffffffLL, 0xffffffffffffffffLL
};
for (ut16 width = 0; width < 256; width++) {
ut64 actual = rz_num_bitmask((ut8)width);
ut64 expect = expect_masks[RZ_MIN(width, 64)];
char msg[0x100];
snprintf(msg, sizeof(msg), "bitmask of %u bits\n", (unsigned int)width);
mu_assert_eq(actual, expect, msg);
}
mu_end;
}
bool test_rz_num_abs() {
mu_assert_eq(rz_num_abs(ST64_MAX), ST64_MAX, "rz_num_abs(2^63 - 1) = 2^63 - 1");
mu_assert_eq(rz_num_abs(0), 0, "rz_num_abs(0) = 0");
mu_assert_eq(rz_num_abs(ST64_MIN), UT64_GT0, "rz_num_abs(-2^63) = 2^63");
mu_end;
}
// rz_num_math_value() tests for the tree-sitter-based evaluator.
static ut64 test_var_cb(RzNum *self, const char *name, int *ok) {
(void)self;
if (!strcmp(name, "x")) {
*ok = 1;
return 10;
}
if (!strcmp(name, "y")) {
*ok = 1;
return 20;
}
if (!strcmp(name, "$$")) {
*ok = 1;
return 0x1000;
}
if (!strcmp(name, "$F")) {
*ok = 1;
return 0x2000;
}
if (!strcmp(name, "$S")) {
*ok = 1;
return 0x40000;
}
*ok = 0;
return 0;
}
// Callback for the local-label test. It resolves a function-local
// ".label" relative to the value that precedes the '+', which the
// evaluator must place in nc.number_value.n (mirroring how the rizin
// core callback resolves `.foo` against the function at that address).
// It reads nc through `self`, so the test wires the RzNum's userptr to
// the RzNum itself, exactly as a host that needs nc would.
static ut64 label_cb(RzNum *self, const char *name, int *ok) {
*ok = 0;
if (!strcmp(name, "base")) {
*ok = 1;
return 0x1000;
}
// foo's absolute address is 0x1004; return it as a delta from the
// base the evaluator handed us through nc.
if (!strcmp(name, ".foo") && self && self->nc.curr_tok == '+') {
*ok = 1;
return 0x1004 - self->nc.number_value.n;
}
return 0;
}
#define ASSERT_U64(expr, want) \
do { \
RzNumValue _v; \
rz_num_value_init(&_v); \
char *_err = NULL; \
bool _ok = rz_num_math_value(num_with_cb, (expr), &_v, &_err); \
mu_assert_true(_ok, "evaluation failed: " expr); \
mu_assert_eq(_v.kind, RZ_NUM_KIND_UT64, "wrong kind for: " expr); \
mu_assert_eq(_v.val.n, (ut64)(want), "wrong ut64 result for: " expr); \
rz_num_value_fini(&_v); \
free(_err); \
} while (0)
#define ASSERT_F64(expr, want) \
do { \
RzNumValue _v; \
rz_num_value_init(&_v); \
char *_err = NULL; \
bool _ok = rz_num_math_value(num_with_cb, (expr), &_v, &_err); \
mu_assert_true(_ok, "evaluation failed: " expr); \
mu_assert_true(_v.kind == RZ_NUM_KIND_FLOAT || _v.kind == RZ_NUM_KIND_BIGDECIMAL, \
"wrong kind for: " expr); \
mu_assert_true(rz_num_value_to_double(&_v) == (double)(want), "wrong float result for: " expr); \
rz_num_value_fini(&_v); \
free(_err); \
} while (0)
#define ASSERT_FAIL(expr) \
do { \
RzNumValue _v; \
rz_num_value_init(&_v); \
char *_err = NULL; \
bool _ok = rz_num_math_value(num_with_cb, (expr), &_v, &_err); \
mu_assert_false(_ok, "should have failed: " expr); \
rz_num_value_fini(&_v); \
free(_err); \
} while (0)
static RzNum *num_with_cb = NULL;
bool test_rz_num_math_value_integer_arith() {
ASSERT_U64("1 + 2", 3);
ASSERT_U64("1 + 2 * 3", 7);
ASSERT_U64("(1 + 2) * 3", 9);
ASSERT_U64("10 - 4", 6);
ASSERT_U64("100 / 10", 10);
ASSERT_U64("10 mod 3", 1);
ASSERT_U64("10 % 3", 1);
ASSERT_U64("0x10 + 0b1010", 0x1a);
ASSERT_U64("0x10 + 0o10", 0x18);
ASSERT_U64("0x10 + 0t10", 0x13);
// C-style octal: a leading 0 followed by more digits is octal,
// matching the legacy parser (used by e.g. `s 034`).
ASSERT_U64("034", 28);
ASSERT_U64("010", 8);
ASSERT_U64("00120000", 0xa000); // 0o120000 == 40960
ASSERT_U64("0", 0);
ASSERT_U64("00", 0);
ASSERT_U64("07 + 1", 8); // 0o7 + 1
mu_end;
}
bool test_rz_num_math_value_bitwise() {
ASSERT_U64("0xff & 0x0f", 0x0f);
ASSERT_U64("0xf0 | 0x0f", 0xff);
ASSERT_U64("0xff ^ 0xaa", 0x55);
ASSERT_U64("~0", UT64_MAX);
ASSERT_U64("~0xff", UT64_MAX & ~(ut64)0xff);
ASSERT_U64("1 << 4", 16);
ASSERT_U64("0x100 >> 4", 0x10);
// Bitwise precedence: AND > XOR > OR
ASSERT_U64("0x10 | 0x20 ^ 0x30 & 0xf0", 0x10 | (0x20 ^ (0x30 & 0xf0)));
// Shift binds tighter than OR
ASSERT_U64("1 | 2 << 4", 1 | (2 << 4));
mu_end;
}
bool test_rz_num_math_value_rotate() {
ASSERT_U64("1 <<< 60", 1ULL << 60);
// Rotate by 64 == identity (the shift amount is masked to 6 bits)
ASSERT_U64("0xdeadbeef <<< 64", 0xdeadbeef);
ASSERT_U64("1 >>> 4", (1ULL >> 4) | (1ULL << (64 - 4)));
mu_end;
}
bool test_rz_num_math_value_logical() {
ASSERT_U64("!0", 1);
ASSERT_U64("!1", 0);
ASSERT_U64("!42", 0);
ASSERT_U64("!(1 - 1)", 1);
mu_end;
}
bool test_rz_num_math_value_unary_signs() {
// Unary minus and plus are operators, not part of the literal.
ASSERT_U64("-5", (ut64)(-5LL));
ASSERT_U64("+5", 5);
ASSERT_U64("-x", (ut64)(-10LL));
ASSERT_U64("-(2 + 3)", (ut64)(-5LL));
// Binary subtraction still binds correctly.
ASSERT_U64("10 - 3", 7);
ASSERT_U64("10 + -3", 7);
ASSERT_U64("10 - +3", 7);
mu_end;
}
bool test_rz_num_math_value_increment() {
ASSERT_U64("++5", 6);
ASSERT_U64("--5", 4);
mu_end;
}
bool test_rz_num_math_value_floats() {
// Float literals and mixed-kind arithmetic.
ASSERT_F64("1.5 + 1.5", 3.0);
ASSERT_F64("1 + 1.5", 2.5);
ASSERT_F64("1.5 + 1", 2.5);
ASSERT_F64("3.0 * 2.0", 6.0);
ASSERT_F64("3.0 / 2.0", 1.5);
ASSERT_F64("3.0 % 2.0", 1.0);
ASSERT_F64("-1.5", -1.5);
// Comparisons return ut64, even on float operands.
ASSERT_U64("1.5 < 2.0", 1);
ASSERT_U64("1.5 == 1.5", 1);
ASSERT_U64("1.5 != 1.5", 0);
mu_end;
}
bool test_rz_num_math_value_bigdecimal() {
// Pure decimal arithmetic keeps full precision (this is the point):
// the result is exact, not the nearest double.
#define ASSERT_DEC(expr, want_str) \
do { \
RzNumValue _v; \
rz_num_value_init(&_v); \
char *_err = NULL; \
bool _ok = rz_num_math_value(num_with_cb, (expr), &_v, &_err); \
mu_assert_true(_ok, "eval failed: " expr); \
mu_assert_eq(_v.kind, RZ_NUM_KIND_BIGDECIMAL, "expected decimal kind: " expr); \
char *_s = rz_num_value_tostring(&_v); \
mu_assert_streq_free(_s, want_str, expr); \
rz_num_value_fini(&_v); \
free(_err); \
} while (0)
ASSERT_DEC("0.1 + 0.2", "0.3"); // not 0.30000000000000004
ASSERT_DEC("0.1 * 0.1", "0.01");
ASSERT_DEC("1.5 + 2.5", "4");
ASSERT_DEC("10.0 - 0.0001", "9.9999");
ASSERT_DEC("-2.5", "-2.5");
ASSERT_DEC("1 + 0.5", "1.5"); // an integer operand promotes to decimal
ASSERT_DEC("0.3 / 0.1", "3"); // bounded division, exact here
#undef ASSERT_DEC
// Modulo and transcendental functions have no exact decimal form, so
// they fall back to double (FLOAT); mixing a double back in stays
// double.
#define ASSERT_KIND(expr, k) \
do { \
RzNumValue _v; \
rz_num_value_init(&_v); \
char *_err = NULL; \
bool _ok = rz_num_math_value(num_with_cb, (expr), &_v, &_err); \
mu_assert_true(_ok, "eval failed: " expr); \
mu_assert_eq(_v.kind, (k), "wrong kind: " expr); \
rz_num_value_fini(&_v); \
free(_err); \
} while (0)
ASSERT_KIND("3.0 % 2.0", RZ_NUM_KIND_FLOAT);
ASSERT_KIND("sqrt(2.0)", RZ_NUM_KIND_FLOAT);
ASSERT_KIND("sqrt(4.0) + 0.5", RZ_NUM_KIND_FLOAT);
#undef ASSERT_KIND
mu_end;
}
bool test_rz_num_math_value_units() {
ASSERT_U64("1KiB", 1024);
ASSERT_U64("2MiB", 2 * 1024 * 1024);
ASSERT_U64("3PiB", 3ULL << 50);
ASSERT_U64("4EiB", 4ULL << 60);
ASSERT_U64("1KB", 1000);
ASSERT_U64("1MB", 1000000);
ASSERT_U64("1GB", 1000000000ULL);
mu_end;
}
bool test_rz_num_math_value_comparisons() {
ASSERT_U64("1 < 2", 1);
ASSERT_U64("2 < 1", 0);
ASSERT_U64("1 <= 1", 1);
ASSERT_U64("1 > 2", 0);
ASSERT_U64("2 >= 2", 1);
ASSERT_U64("3 == 3", 1);
ASSERT_U64("3 != 4", 1);
// Comparison composes with arithmetic.
ASSERT_U64("(1 < 2) + (3 < 4)", 2);
ASSERT_U64("(1 + 1) == 2", 1);
mu_end;
}
bool test_rz_num_math_value_special_variables() {
ASSERT_U64("$$", 0x1000);
ASSERT_U64("$F", 0x2000);
ASSERT_U64("$S", 0x40000);
ASSERT_U64("$F + 0x10", 0x2010);
ASSERT_U64("$$ * 2", 0x2000);
mu_end;
}
// Function-local flags: `base + .foo` resolves .foo relative to the
// value before the '+'. The evaluator must publish that value through
// nc.number_value.n / nc.curr_tok so the host callback (here label_cb)
// can turn the absolute label address into a delta. (Regression: the
// new parser left nc untouched, so `s main+.foo` came out as `main`.)
bool test_rz_num_math_value_local_label() {
RzNum *ln = rz_num_new(label_cb, NULL, NULL);
mu_assert_notnull(ln, "rz_num_new");
ln->userptr = ln; // a host that needs nc passes a ptr it can reach nc through
RzNumValue v;
char *err = NULL;
rz_num_value_init(&v);
bool ok = rz_num_math_value(ln, "base + .foo", &v, &err);
mu_assert_true(ok, "base + .foo evaluates");
mu_assert_eq(v.kind, RZ_NUM_KIND_UT64, "ut64 result");
mu_assert_eq(v.val.n, 0x1004, "base + .foo == foo's absolute address");
rz_num_value_fini(&v);
free(err);
// And through the legacy entry point, since that is what `s` uses.
mu_assert_eq(rz_num_math(ln, "base + .foo"), 0x1004, "rz_num_math base + .foo");
rz_num_free(ln);
mu_end;
}
// Regression: the host callback must receive the opaque userptr that
// was passed to rz_num_new() as its first argument - NOT the RzNum
// itself. RzCore's num_callback casts that first argument straight to
// RzCore*, so passing the RzNum would make it dereference a wrong
// pointer and crash. This callback dereferences its userptr to prove
// the right value is threaded through for plain and special
// variables alike.
typedef struct {
ut64 magic;
ut64 base;
} cb_host_ctx;
static ut64 userptr_var_cb(RzNum *self, const char *name, int *ok) {
// 'self' is really the userptr (a cb_host_ctx*), exactly as
// RzCore's callback treats it. Dereferencing it must be safe.
cb_host_ctx *ctx = (cb_host_ctx *)self;
if (!ctx || ctx->magic != 0xC0FFEE) {
*ok = 0;
return 0;
}
if (!strcmp(name, "base")) {
*ok = 1;
return ctx->base;
}
if (!strcmp(name, "$$")) {
*ok = 1;
return ctx->base + 0x10;
}
*ok = 0;
return 0;
}
bool test_rz_num_math_value_callback_userptr() {
cb_host_ctx ctx = { .magic = 0xC0FFEE, .base = 0x4000 };
RzNum *n = rz_num_new(userptr_var_cb, NULL, &ctx);
mu_assert_notnull(n, "rz_num_new");
RzNumValue v;
char *err = NULL;
// Plain variable: the callback reads ctx->base via the userptr.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(n, "base + 1", &v, &err),
"base + 1 resolves via userptr");
mu_assert_eq(rz_num_value_to_ut64(&v), 0x4001, "base + 1 == 0x4001");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Special variable: same userptr path.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(n, "$$ + 4", &v, &err),
"$$ + 4 resolves via userptr");
mu_assert_eq(rz_num_value_to_ut64(&v), 0x4014, "$$ + 4 == 0x4014");
rz_num_value_fini(&v);
free(err);
err = NULL;
// An unknown name still resolves to 0 without touching anything
// unsafe (the callback sets ok=0).
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(n, "unknown_name + 7", &v, &err),
"unknown var resolves to 0");
mu_assert_eq(rz_num_value_to_ut64(&v), 7, "unknown + 7 == 7");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_free(n);
mu_end;
}
bool test_rz_num_math_value_string_bytes() {
// A string-bytes literal now evaluates to a bit-vector whose
// width is 8 * byte-count, packed little-endian (first source
// byte is the LSB). This makes len() recover the byte count and
// lets the value take part in width-aware bit-vector arithmetic.
RzNumValue v;
char *err = NULL;
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(num, "\"AB\"", &v, &err), "\"AB\"");
mu_assert_eq(v.kind, RZ_NUM_KIND_BITVECTOR, "\"AB\" is bitvector");
mu_assert_eq(rz_bv_len(v.val.bv), 16, "\"AB\" width 16");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0x4241, "\"AB\" value");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(num, "\"A\"", &v, &err), "\"A\"");
mu_assert_eq(rz_bv_len(v.val.bv), 8, "\"A\" width 8");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0x41, "\"A\" value");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(num, "\"\\x41\\x42\"", &v, &err), "hex escapes");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0x4241, "hex-escape value");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Arithmetic keeps the bit-vector width: "AB" is 16-bit, so + 1
// wraps within 16 bits.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(num, "\"AB\" + 1", &v, &err), "\"AB\" + 1");
mu_assert_eq(v.kind, RZ_NUM_KIND_BITVECTOR, "\"AB\" + 1 is bitvector");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0x4242, "\"AB\" + 1 value");
rz_num_value_fini(&v);
free(err);
err = NULL;
// A longer string than would fit a ut64 is now representable.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(num, "\"ABCDEFGHIJ\"", &v, &err), "10-byte string");
mu_assert_eq(rz_bv_len(v.val.bv), 80, "10-byte string width 80");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_math_value_reserved_words() {
// Bare reserved words must error, not silently resolve to 0.
ASSERT_FAIL("mod");
ASSERT_FAIL("log");
ASSERT_FAIL("le");
ASSERT_FAIL("be");
// Identifiers with reserved-word prefix are valid (longest match).
// `modulus` resolves to 0 (no callback hit) but does not error.
ASSERT_U64("modulus", 0);
ASSERT_U64("logger", 0);
ASSERT_U64("letter", 0);
// `let` is a true parser keyword: bare use is a parse error.
ASSERT_FAIL("let");
mu_end;
}
bool test_rz_num_math_value_errors() {
ASSERT_FAIL("");
ASSERT_FAIL("1 +");
ASSERT_FAIL("(1 +");
ASSERT_FAIL("1 / 0");
ASSERT_FAIL("$nonexistent_special_var");
mu_end;
}
bool test_rz_num_math_value_assignment() {
// Assignment evaluates to the RHS value.
ASSERT_U64("x = 5", 5);
ASSERT_U64("let y = 7", 7);
ASSERT_U64("x = 1 + 2 * 3", 7);
// The binding is readable later in the same expression.
ASSERT_U64("(x = 7) + x", 14);
ASSERT_U64("(a = 2) * (a + 1)", 6);
ASSERT_U64("let n = 0x10 + n", 0x10); // n unknown on RHS -> 0
// Reassignment updates the binding.
ASSERT_U64("(c = 1) + (c = 9) + c", 19); // 1 + 9 + 9
// A bignum can be bound and read back at full precision.
RzNumValue v;
char *err = NULL;
bool ok = rz_num_math_value(NULL,
"(big = 0x10000000000000000) + (big - big)", &v, &err);
mu_assert_true(ok, "bignum binding");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIG, "bound bignum stays BIG");
rz_num_value_fini(&v);
free(err);
err = NULL;
// A reserved word cannot be assigned.
mu_assert_false(rz_num_math_value(NULL, "mod = 5", &v, &err),
"reserved word assignment");
mu_assert_eq(v.err, RZ_NUM_ERR_RESERVED_WORD, "mod= -> RESERVED_WORD");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_math_value_sequence() {
// ';' separates statements; the value of the whole is the value
// of the last. Earlier statements run for their side effects
// (bindings).
ASSERT_U64("1 + 2; 3 + 4", 7);
ASSERT_U64("let x = 5; x * 2", 10);
ASSERT_U64("x = 10; y = 20; x + y", 30);
// Bindings made earlier in the sequence are visible later.
ASSERT_U64("a = 3; b = a * a; a + b", 12);
// A trailing ';' is allowed.
ASSERT_U64("1 + 1;", 2);
// A single statement (no ';') is unchanged.
ASSERT_U64("6 * 7", 42);
mu_end;
}
bool test_rz_num_math_value_conditional() {
// Basic selection on a truthy / falsy integer condition.
ASSERT_U64("1 ? 10 : 20", 10);
ASSERT_U64("0 ? 10 : 20", 20);
ASSERT_U64("5 ? 0xaa : 0xbb", 0xaa); // any non-zero is true
// The condition is usually a comparison.
ASSERT_U64("5 > 3 ? 100 : 200", 100);
ASSERT_U64("5 < 3 ? 100 : 200", 200);
ASSERT_U64("(7 & 1) ? 1 : 2", 1);
// Right-associative: a ? b : c ? d : e == a ? b : (c ? d : e).
ASSERT_U64("0 ? 1 : 0 ? 2 : 3", 3);
ASSERT_U64("0 ? 1 : 1 ? 2 : 3", 2);
ASSERT_U64("1 ? 2 : 1 ? 3 : 4", 2);
// Branches can be arbitrary sub-expressions and nest.
ASSERT_U64("1 ? 2 + 3 : 4 * 5", 5);
ASSERT_U64("0 ? 2 + 3 : 4 * 5", 20);
ASSERT_U64("(2 > 1 ? 3 : 4) + 10", 13);
// Short-circuit: the untaken branch is NOT evaluated, so a
// division by zero there must not trigger an error.
ASSERT_U64("1 ? 42 : 1 / 0", 42);
ASSERT_U64("0 ? 1 / 0 : 42", 42);
// Float condition is tested as a float: 0.0 is false, any other
// value (even one whose integer truncation is 0) is true.
ASSERT_U64("0.5 ? 7 : 8", 7);
ASSERT_U64("0.0 ? 7 : 8", 8);
// The selected branch keeps its own kind.
RzNumValue v;
char *err = NULL;
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "1 ? 5u8 : 9u8", &v, &err),
"bitvector branch");
mu_assert_eq(v.kind, RZ_NUM_KIND_BITVECTOR, "ternary yields bitvector");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 5, "1 ? 5u8 : 9u8 == 5u8");
rz_num_value_fini(&v);
free(err);
err = NULL;
// A big-number branch is returned exact.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "0 ? 1 : 2 ** 100", &v, &err),
"bignum branch");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIG, "ternary yields big");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_math_value_signed_arith() {
// Signed division / remainder over the ut64 domain interpreted
// as two's-complement st64. 0xffff...fb is -5.
ASSERT_U64("0xfffffffffffffffb sdiv 2", (ut64)(-2)); // -5 / 2 = -2
ASSERT_U64("10 sdiv 3", 3);
ASSERT_U64("0xfffffffffffffffb smod 3", (ut64)(-2)); // -5 % 3 = -2
ASSERT_U64("10 smod 3", 1);
// Unsigned division differs from signed for "negative" operands.
ASSERT_U64("0xfffffffffffffffb / 2", 0xfffffffffffffffb / 2);
// Arithmetic (sign-propagating) shift right.
ASSERT_U64("256 sar 2", 64); // positive: same as logical
ASSERT_U64("65280 sar 4", 4080);
ASSERT_U64("0xffffffffffffff00 sar 4", 0xfffffffffffffff0); // -256 >> 4 = -16
ASSERT_U64("0xffffffffffffffff sar 4", 0xffffffffffffffff); // -1 stays -1
// Logical >> on the same value does NOT sign-extend.
ASSERT_U64("0xffffffffffffff00 >> 4", 0x0ffffffffffffff0);
// Division / modulo by zero is trapped for the signed ops too.
RzNumValue v;
char *err = NULL;
rz_num_value_init(&v);
mu_assert_false(rz_num_math_value(NULL, "5 sdiv 0", &v, &err), "sdiv by zero");
mu_assert_eq(v.err, RZ_NUM_ERR_DIV_ZERO, "sdiv 0 -> DIV_ZERO");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_assert_false(rz_num_math_value(NULL, "5 smod 0", &v, &err), "smod by zero");
mu_assert_eq(v.err, RZ_NUM_ERR_DIV_ZERO, "smod 0 -> DIV_ZERO");
rz_num_value_fini(&v);
free(err);
err = NULL;
// sdiv / smod / sar are reserved and cannot be variable names.
mu_assert_false(rz_num_math_value(NULL, "sdiv = 1", &v, &err), "sdiv reserved");
mu_assert_eq(v.err, RZ_NUM_ERR_RESERVED_WORD, "sdiv -> RESERVED_WORD");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_math_value_len() {
// len() of a bit-vector is its width in BITS.
ASSERT_U64("len(5u8)", 8);
ASSERT_U64("len(0xffu16)", 16);
ASSERT_U64("len(1u32)", 32);
// A string literal is a bit-vector of 8 * byte-count; len gives
// the bit count and len()/8 the byte count.
ASSERT_U64("len(\"ABCD\")", 32);
ASSERT_U64("len(\"ABCD\") / 8", 4);
ASSERT_U64("len(\"ABCDEFGHIJ\") / 8", 10); // longer than a ut64
// len of a big number is its significant bit width.
ASSERT_U64("len(2 ** 100)", 101);
// len of a plain ut64 is its significant bit width.
ASSERT_U64("len(0xff)", 8);
ASSERT_U64("len(0)", 0);
// len on a float is a type error.
RzNumValue v;
char *err = NULL;
rz_num_value_init(&v);
mu_assert_false(rz_num_math_value(NULL, "len(1.5)", &v, &err), "len on float");
mu_assert_eq(v.err, RZ_NUM_ERR_TYPE_MISMATCH, "len(float) -> TYPE_MISMATCH");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_math_value_persistent_vars() {
// A caller-supplied store makes bindings survive across separate
// rz_num_math_value_ex() calls.
HtSP *store = rz_num_value_store_new();
mu_assert_notnull(store, "store created");
RzNumValue v;
char *err = NULL;
RzNumMathOptions opt = { 0 };
opt.vars = store;
// Call 1: bind.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value_ex(NULL, "base = 0x1000", &opt, &v, &err), "bind base");
mu_assert_eq(rz_num_value_to_ut64(&v), 0x1000, "base value");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Call 2: read the persisted binding.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value_ex(NULL, "base + 0x20", &opt, &v, &err), "read base");
mu_assert_eq(rz_num_value_to_ut64(&v), 0x1020, "persisted base + 0x20");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Call 3: bind another using the persisted one, then read it.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value_ex(NULL, "top = base + 0x100", &opt, &v, &err), "bind top");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value_ex(NULL, "top - base", &opt, &v, &err), "read top");
mu_assert_eq(rz_num_value_to_ut64(&v), 0x100, "top - base");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_store_free(store);
// Without a store, a binding does NOT persist to the next call.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "ephem = 42", &v, &err), "ephemeral bind");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "ephem + 1", &v, &err), "ephem unknown");
mu_assert_eq(rz_num_value_to_ut64(&v), 1, "ephem resolves to 0 -> 1"); // unknown var -> 0
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_math_value_address_typed() {
// V1 limitation: dereference is not implemented, so the
// literal address (the part before the colon) is returned.
ASSERT_U64("0x1234:le32", 0x1234);
ASSERT_U64("0x1000:8", 0x1000);
ASSERT_U64("0x10:le32 + 4", 0x14);
mu_end;
}
bool test_rz_num_math_value_bignum_arith() {
RzNumValue v;
char *err = NULL;
// Bignum * bignum, checked via exact decimal.
bool ok = rz_num_math_value(NULL,
"0x10000000000000000 * 0x10000000000000000", &v, &err);
mu_assert_true(ok, "big * big");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIG, "big*big is BIG");
char *dec = rz_big_to_decstr(v.val.big);
// (2^64)^2 == 2^128
mu_assert_streq_free(dec,
"340282366920938463463374607431768211456", "2^128 exact");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Bignum + ut64 stays exact.
ok = rz_num_math_value(NULL, "0x10000000000000000 + 5", &v, &err);
mu_assert_true(ok, "big + ut64");
dec = rz_big_to_decstr(v.val.big);
mu_assert_streq_free(dec, "18446744073709551621", "2^64 + 5");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Bignum subtraction that demotes back to ut64 when it fits.
ok = rz_num_math_value(NULL,
"0x10000000000000000 - 0x10000000000000000", &v, &err);
mu_assert_true(ok, "big - big");
mu_assert_eq(v.kind, RZ_NUM_KIND_UT64, "demotes to ut64");
mu_assert_eq(v.val.n, 0, "2^64 - 2^64 == 0");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Decimal bignum literal round-trips through the decstr.
ok = rz_num_math_value(NULL,
"123456789012345678901234567890", &v, &err);
mu_assert_true(ok, "decimal bignum literal");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIG, "decimal overflow -> BIG");
dec = rz_big_to_decstr(v.val.big);
mu_assert_streq_free(dec, "123456789012345678901234567890",
"decimal literal round-trip");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_math_value_complex_exprs() {
// Precedence and nesting sanity, all exact ut64.
ASSERT_U64("1 + 2 * 3 - 4 / 2", 5);
ASSERT_U64("((1 + 2) * (3 + 4))", 21);
ASSERT_U64("0xff & 0x0f | 0x30", 0x3f);
ASSERT_U64("(1 << 8) + (1 << 4) + 1", 0x111);
ASSERT_U64("2 ** 3 ** 2", 512); // right-assoc: 2 ** (3 ** 2) == 2**9
ASSERT_U64("min(max(1, 9), max(2, 3))", 3);
ASSERT_U64("popcount(0xffff) + popcount(0xff)", 24);
ASSERT_U64("(0x10 > 0x8) + (0x8 > 0x10)", 1); // true + false
mu_end;
}
bool test_rz_num_math_value_bitvector() {
RzNumValue v;
char *err = NULL;
// A width-suffixed literal produces a bit-vector of that width.
bool ok = rz_num_math_value(NULL, "5u8", &v, &err);
mu_assert_true(ok, "5u8");
mu_assert_eq(v.kind, RZ_NUM_KIND_BITVECTOR, "5u8 is bitvector");
mu_assert_eq(rz_bv_len(v.val.bv), 8, "width 8");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 5, "value 5");
rz_num_value_fini(&v);
free(err);
err = NULL;
#define ASSERT_BV(expr, want_width, want_val) \
do { \
RzNumValue _v; \
char *_e = NULL; \
bool _ok = rz_num_math_value(NULL, (expr), &_v, &_e); \
mu_assert_true(_ok, expr); \
mu_assert_eq(_v.kind, RZ_NUM_KIND_BITVECTOR, expr " kind"); \
mu_assert_eq(rz_bv_len(_v.val.bv), (want_width), expr " width"); \
mu_assert_eq(rz_bv_to_ut64(_v.val.bv), (want_val), expr " value"); \
rz_num_value_fini(&_v); \
free(_e); \
} while (0)
// Arithmetic wraps modulo the operand width.
ASSERT_BV("200u8 + 100u8", 8, 44); // 300 mod 256
ASSERT_BV("0xffu8 + 1u8", 8, 0); // overflow wraps to 0
ASSERT_BV("0xffffu16 + 1u16", 16, 0);
ASSERT_BV("100u8 - 50u8", 8, 50);
ASSERT_BV("10u16 * 10u16", 16, 100);
ASSERT_BV("10u8 / 3u8", 8, 3);
ASSERT_BV("10u8 % 3u8", 8, 1);
// Bitwise and shifts.
ASSERT_BV("5u8 & 3u8", 8, 1);
ASSERT_BV("5u8 | 2u8", 8, 7);
ASSERT_BV("5u8 ^ 1u8", 8, 4);
ASSERT_BV("1u8 << 4u8", 8, 16);
ASSERT_BV("0x80u8 >> 3u8", 8, 16);
// Mixing a bit-vector with a plain ut64 keeps the bit-vector
// width.
ASSERT_BV("5u8 + 3", 8, 8);
ASSERT_BV("3 + 5u8", 8, 8);
// A wide bit-vector survives at full width.
ASSERT_BV("0x123456789abcu64", 64, 0x123456789abcULL);
#undef ASSERT_BV
// Comparisons on bit-vectors return a ut64 boolean.
ASSERT_U64("5u8 == 5u8", 1);
ASSERT_U64("5u8 != 6u8", 1);
ASSERT_U64("5u8 < 10u8", 1);
ASSERT_U64("10u8 > 5u8", 1);
ASSERT_U64("5u8 <= 5u8", 1);
ASSERT_U64("5u8 >= 6u8", 0);
// Division by zero on a bit-vector is reported.
ok = rz_num_math_value(NULL, "1u8 / 0u8", &v, &err);
mu_assert_false(ok, "bv div by zero fails");
mu_assert_eq(v.err, RZ_NUM_ERR_DIV_ZERO, "bv div zero -> DIV_ZERO");
rz_num_value_fini(&v);
free(err);
err = NULL;
// ** on a bit-vector is modular at the operand width: it is
// computed by iterative squaring entirely within W bits, so a
// power that overflows wraps rather than failing.
ok = rz_num_math_value(NULL, "2u8 ** 3u8", &v, &err);
mu_assert_true(ok, "bv ** succeeds");
mu_assert_eq(v.kind, RZ_NUM_KIND_BITVECTOR, "bv ** -> bitvector");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 8, "2u8 ** 3u8 == 8u8");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Overflow wraps modulo 2^W.
ok = rz_num_math_value(NULL, "2u8 ** 8u8", &v, &err);
mu_assert_true(ok, "bv ** overflow ok");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0, "2u8 ** 8u8 wraps to 0");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_math_value_bignum() {
// Hex literal that overflows ut64 is promoted to BIG.
RzNumValue v;
rz_num_value_init(&v);
char *err = NULL;
bool ok = rz_num_math_value(NULL, "0x10000000000000000", &v, &err);
mu_assert_true(ok, "parse 65-bit hex");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIG, "65-bit hex is BIG kind");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Arithmetic on a BIG operand stays in BIG when the result
// also exceeds ut64.
ok = rz_num_math_value(NULL, "0x10000000000000000 * 2", &v, &err);
mu_assert_true(ok, "BIG * 2");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIG, "BIG * 2 stays BIG");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Arithmetic that brings a BIG operand back inside ut64 must
// demote to UT64. 0x10000000000000001 - 2 == 0xffffffffffffffff.
ok = rz_num_math_value(NULL, "0x10000000000000001 - 2", &v, &err);
mu_assert_true(ok, "BIG - small demotes");
mu_assert_eq(v.kind, RZ_NUM_KIND_UT64, "result fits in ut64");
mu_assert_eq(v.val.n, 0xffffffffffffffffULL, "result value");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Comparisons between BIG and UT64 work without losing precision.
ok = rz_num_math_value(NULL,
"0x10000000000000000 > 0xffffffffffffffff", &v, &err);
mu_assert_true(ok, "BIG > UT64");
mu_assert_eq(v.kind, RZ_NUM_KIND_UT64, "compare result is ut64");
mu_assert_eq(v.val.n, 1, "2^64 > 2^64-1");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Large decimal literal that overflows ut64 is also promoted to
// BIG (was an error in earlier versions; the evaluator now
// shift-and-adds through rz_big_mul / rz_big_add).
ok = rz_num_math_value(NULL,
"99999999999999999999999999999999", &v, &err);
mu_assert_true(ok, "large decimal parses");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIG, "decimal overflow -> BIG");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Round-trip: a decimal bignum can be used in arithmetic. 10^40
// divided by 10 is still 10^39, which exceeds ut64 and stays
// BIG.
ok = rz_num_math_value(NULL,
"10000000000000000000000000000000000000000 / 10", &v, &err);
mu_assert_true(ok, "decimal bignum / 10");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIG, "still BIG after /10");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Binary literal that overflows ut64 also promotes:
// 2^65 in binary = '1' followed by 65 zeros.
ok = rz_num_math_value(NULL,
"0b100000000000000000000000000000000000000000000000000000000000000000",
&v, &err);
mu_assert_true(ok, "binary overflow promotes");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIG, "binary overflow -> BIG");
rz_num_value_fini(&v);
free(err);
err = NULL;
// BIG + decimal now keeps full precision as an exact decimal rather
// than collapsing to a lossy double - the decimal path wins.
ok = rz_num_math_value(NULL, "0x10000000000000000 + 0.5", &v, &err);
mu_assert_true(ok, "BIG + decimal");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIGDECIMAL, "big + decimal -> decimal");
char *bigdec_str = rz_num_value_tostring(&v);
mu_assert_streq_free(bigdec_str, "18446744073709551616.5", "exact big + decimal");
rz_num_value_fini(&v);
free(err);
err = NULL;
// rz_num_math_ut64 projects BIG back to ut64 by truncation.
ut64 truncated = rz_num_math(num, "0x10000000000000001");
mu_assert_eq(truncated, 1, "BIG truncated to low 64 bits");
// Exact integer exponentiation: 2 ** 64 must be the exact
// bignum, not the double approximation. Verify via the decimal
// stringifier.
ok = rz_num_math_value(NULL, "2 ** 64", &v, &err);
mu_assert_true(ok, "2 ** 64");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIG, "2^64 is BIG");
char *dec = rz_big_to_decstr(v.val.big);
mu_assert_streq_free(dec, "18446744073709551616", "exact 2^64");
rz_num_value_fini(&v);
free(err);
err = NULL;
// 10 ** 40 is exact.
ok = rz_num_math_value(NULL, "10 ** 40", &v, &err);
mu_assert_true(ok, "10 ** 40");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIG, "10^40 is BIG");
dec = rz_big_to_decstr(v.val.big);
mu_assert_streq_free(dec,
"10000000000000000000000000000000000000000", "exact 10^40");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Small integer powers stay ut64.
ASSERT_U64("2 ** 10", 1024);
ASSERT_U64("3 ** 5", 243);
ASSERT_U64("2 ** 0", 1);
ASSERT_U64("0 ** 0", 1);
ASSERT_U64("5 ** 1", 5);
// Boundary of the fixed 4096-bit bignum. 2 ** 4095 is the widest
// power of two that still fits exactly (1233 decimal digits); it
// must be an exact BIG, not a truncated value and not a float.
ok = rz_num_math_value(NULL, "2 ** 4095", &v, &err);
mu_assert_true(ok, "2 ** 4095");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIG, "2^4095 is BIG");
dec = rz_big_to_decstr(v.val.big);
mu_assert_notnull(dec, "2^4095 decstr");
mu_assert_eq((ut64)strlen(dec), (ut64)1233, "2^4095 digit count");
free(dec);
rz_num_value_fini(&v);
free(err);
err = NULL;
// One bit past the ceiling: 2 ** 4096 is 4097 bits and cannot be
// represented exactly, so the evaluator deliberately falls back
// to the bounded double path (yielding +inf) rather than silently
// truncating to a wrong integer. This guards the overflow check.
ok = rz_num_math_value(NULL, "2 ** 4096", &v, &err);
mu_assert_true(ok, "2 ** 4096 evaluates");
mu_assert_eq(v.kind, RZ_NUM_KIND_FLOAT, "2^4096 overflows to float");
mu_assert_true(isinf(v.val.d), "2^4096 is +inf");
rz_num_value_fini(&v);
free(err);
err = NULL;
// A float operand forces the double path; the result is float.
ok = rz_num_math_value(NULL, "2 ** 0.5", &v, &err);
mu_assert_true(ok, "2 ** 0.5");
mu_assert_eq(v.kind, RZ_NUM_KIND_FLOAT, "fractional exponent -> float");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_math_value_timeout() {
// A long flat sequence ("0+1;0+1;...") that visits many nodes and so
// should take measurably more than 1 ms in a debug build. We assert
// the timeout fires; on hosts so fast that even this completes within
// 1 ms (an optimised release build on a recent CPU) the assertion
// would flake, so we skip in that case.
//
// A sequence rather than a single "0+1+1+..." chain on purpose: the
// chain nests one binary_expression per term and eval_node() recurses
// once per level, so thousands of terms would exceed the evaluator's
// recursion-depth guard (and, before it existed, overflow a 1 MB
// stack). The ';' sequence is evaluated iteratively, so it exercises
// the deadline cadence with the same node volume but constant depth.
//
// The intent here is documentary: keep the test in the suite so that
// anyone tweaking the deadline-check cadence in eval_node can verify
// the path still fires, while staying robust on future hardware.
const int N = 3000;
char *buf = malloc(64 * N);
mu_assert_notnull(buf, "allocation");
int p = 0;
buf[p++] = '0';
buf[p++] = '+';
buf[p++] = '1';
for (int i = 1; i < N; i++) {
buf[p++] = ';';
buf[p++] = '0';
buf[p++] = '+';
buf[p++] = '1';
}
buf[p] = 0;
// Baseline: no timeout -> the expression completes and we time
// how long it took, so we can decide whether a 1 ms budget is
// expected to trip.
RzNumValue v;
char *err = NULL;
rz_num_value_init(&v);
RzNumMathOptions opt = { .timeout_ms = 0 };
ut64 t0 = rz_time_now_mono();
bool ok = rz_num_math_value_ex(NULL, buf, &opt, &v, &err);
ut64 elapsed_us = rz_time_now_mono() - t0;
mu_assert_true(ok, "no timeout completes");
mu_assert_eq(v.kind, RZ_NUM_KIND_UT64, "result is ut64");
// Value of a ';' sequence is its last statement: 0 + 1 == 1.
mu_assert_eq(v.val.n, (ut64)1, "result value");
rz_num_value_fini(&v);
free(err);
err = NULL;
if (elapsed_us > 5000) {
// The host is slow enough that a 1 ms budget will reliably
// trip. Assert that it does.
opt.timeout_ms = 1;
rz_num_value_init(&v);
ok = rz_num_math_value_ex(NULL, buf, &opt, &v, &err);
mu_assert_false(ok, "1ms timeout aborts");
mu_assert_eq(v.err, RZ_NUM_ERR_TIMEOUT, "1ms -> TIMEOUT");
rz_num_value_fini(&v);
free(err);
}
free(buf);
mu_end;
}
bool test_rz_num_math_value_error_codes() {
// Each category of failure should populate out_value.err with a
// matching RzNumError, so that callers can dispatch on the
// category without sniffing the error string.
RzNumValue v;
char *err = NULL;
rz_num_value_init(&v);
mu_assert_false(rz_num_math_value(NULL, "", &v, &err), "empty");
mu_assert_eq(v.err, RZ_NUM_ERR_EMPTY, "empty -> RZ_NUM_ERR_EMPTY");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_assert_false(rz_num_math_value(NULL, "1 +", &v, &err), "truncated");
mu_assert_eq(v.err, RZ_NUM_ERR_PARSE, "truncated -> RZ_NUM_ERR_PARSE");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_assert_false(rz_num_math_value(NULL, "1 / 0", &v, &err), "div by zero");
mu_assert_eq(v.err, RZ_NUM_ERR_DIV_ZERO, "div0 -> RZ_NUM_ERR_DIV_ZERO");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_assert_false(rz_num_math_value(NULL, "5 % 0", &v, &err), "mod by zero");
mu_assert_eq(v.err, RZ_NUM_ERR_DIV_ZERO, "mod0 -> RZ_NUM_ERR_DIV_ZERO");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_assert_false(rz_num_math_value(NULL, "mod", &v, &err), "reserved word");
mu_assert_eq(v.err, RZ_NUM_ERR_RESERVED_WORD, "mod -> RZ_NUM_ERR_RESERVED_WORD");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_assert_false(rz_num_math_value(NULL, "$nonexistent_var", &v, &err),
"unknown special variable");
// The grammar's special_variable rule is a closed alternation
// (see grammar.js), so an unknown $-name is a SYNTAX error rather
// than a runtime evaluator error - the parser cannot match it
// against any alternative. The RZ_NUM_ERR_UNDEFINED_VAR category
// is reserved for the case where the parser DID match a known
// special variable but the host's callback does not provide a
// value for it (NULL host or callback returns ok=false).
mu_assert_eq(v.err, RZ_NUM_ERR_PARSE,
"$nonexistent_var -> RZ_NUM_ERR_PARSE (closed-list grammar)");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_assert_false(rz_num_math_value(NULL, "foo(1, 2)", &v, &err),
"unknown function");
mu_assert_eq(v.err, RZ_NUM_ERR_NOT_IMPLEMENTED,
"foo() -> RZ_NUM_ERR_NOT_IMPLEMENTED");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Decimal literal overflow is now supported via shift-and-add
// bignum promotion. RZ_NUM_ERR_OVERFLOW is reserved for cases
// where even the bignum path could not handle the literal -
// today none of the supported bases can hit that, but the
// category remains for future fixed-precision literal kinds
// (e.g. user-specified bit-vector widths).
mu_assert_true(rz_num_math_value(NULL,
"99999999999999999999999999999999", &v, &err),
"decimal overflow no longer errors");
mu_assert_eq(v.err, RZ_NUM_ERR_OK,
"decimal overflow -> OK (promoted to BIG)");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_assert_false(rz_num_math_value(NULL, "0 log 5", &v, &err),
"log with bad base");
mu_assert_eq(v.err, RZ_NUM_ERR_UNCOMPUTABLE,
"log(0) -> RZ_NUM_ERR_UNCOMPUTABLE");
rz_num_value_fini(&v);
free(err);
err = NULL;
// On success, err must be RZ_NUM_ERR_OK.
mu_assert_true(rz_num_math_value(NULL, "1 + 2", &v, &err), "valid");
mu_assert_eq(v.err, RZ_NUM_ERR_OK, "valid -> RZ_NUM_ERR_OK");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
// --- helpers for the custom-function / IO-callback tests ---
static void tfn_triple(void *user, const RzNumValue *args, int argc,
RzNumCallbackResult *out) {
(void)user;
if (argc != 1) {
out->ok = false;
return;
}
out->ok = true;
out->kind = RZ_NUM_KIND_UT64;
out->val.n = args[0].val.n * 3;
}
static void tfn_pow2(void *user, const RzNumValue *args, int argc,
RzNumCallbackResult *out) {
(void)user;
if (argc != 1) {
out->ok = false;
return;
}
RzNumBig *base = rz_big_new();
RzNumBig *e = rz_big_new();
RzNumBig *r = rz_big_new();
rz_big_from_int(base, 2);
rz_big_from_int(e, (st64)args[0].val.n);
rz_big_pow(r, base, e);
rz_big_free(base);
rz_big_free(e);
out->ok = true;
out->kind = RZ_NUM_KIND_BIG;
out->val.big = r;
}
static void tfn_fail(void *user, const RzNumValue *args, int argc,
RzNumCallbackResult *out) {
(void)user;
(void)args;
(void)argc;
out->ok = false; // always signals an error
}
// Variadic: sum of all ut64 arguments. Used to prove the registry
// accepts arity -1 and that a call may pass an arbitrary number of
// arguments (beyond the small inline buffer the evaluator uses).
static void tfn_sum(void *user, const RzNumValue *args, int argc,
RzNumCallbackResult *out) {
(void)user;
ut64 s = 0;
for (int i = 0; i < argc; i++) {
s += args[i].val.n;
}
out->ok = true;
out->kind = RZ_NUM_KIND_UT64;
out->val.n = s;
}
// Fake IO reader: synthesise raw bytes for the requested address.
// The conceptual value at `addr` is `addr ^ 0xcafe`, written in
// little-endian byte order into `buf` (so a `:le32` decode yields
// `(addr ^ 0xcafe) & 0xffffffff` and a `:beN` decode of the same
// bytes returns its byte-reversed form). The callback returns the
// number of bytes actually written, matching RzPfReadAtCb.
static ut64 tio_read(void *user, ut64 addr, ut8 *buf, size_t len) {
(void)user;
ut64 v = addr ^ 0xcafe;
for (size_t i = 0; i < len; i++) {
buf[i] = (i < 8) ? (ut8)(v >> (8 * i)) : 0;
}
return len;
}
bool test_rz_num_math_value_custom_funcs() {
RzNumFuncRegistry *reg = rz_num_func_registry_new();
mu_assert_notnull(reg, "registry alloc");
mu_assert_true(rz_num_func_registry_add(reg, "triple", 1, tfn_triple, NULL),
"register triple");
mu_assert_true(rz_num_func_registry_add(reg, "pow2", 1, tfn_pow2, NULL),
"register pow2");
mu_assert_true(rz_num_func_registry_add(reg, "boom", 0, tfn_fail, NULL),
"register boom");
RzNumMathOptions opt = { .funcs = reg };
RzNumValue v;
char *err = NULL;
// Simple custom ut64 function.
mu_assert_true(rz_num_math_value_ex(NULL, "triple(7)", &opt, &v, &err),
"triple(7)");
mu_assert_eq(v.kind, RZ_NUM_KIND_UT64, "triple kind");
mu_assert_eq(v.val.n, 21, "triple(7) == 21");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Custom function used as a sub-expression.
mu_assert_true(rz_num_math_value_ex(NULL, "triple(7) + 1", &opt, &v, &err),
"triple in expr");
mu_assert_eq(v.val.n, 22, "triple(7)+1 == 22");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Custom function returning a bignum.
mu_assert_true(rz_num_math_value_ex(NULL, "pow2(100)", &opt, &v, &err),
"pow2(100)");
mu_assert_eq(v.kind, RZ_NUM_KIND_BIG, "pow2(100) is BIG");
char *dec = rz_big_to_decstr(v.val.big);
mu_assert_streq_free(dec,
"1267650600228229401496703205376", "pow2(100) exact");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Built-ins still resolve alongside the registry.
mu_assert_true(rz_num_math_value_ex(NULL, "min(3, 7)", &opt, &v, &err),
"built-in min still works");
mu_assert_eq(v.val.n, 3, "min(3,7) == 3");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Nesting a built-in inside a custom function.
mu_assert_true(rz_num_math_value_ex(NULL, "triple(min(2, 5))", &opt, &v, &err),
"nest builtin in custom");
mu_assert_eq(v.val.n, 6, "triple(min(2,5)) == 6");
rz_num_value_fini(&v);
free(err);
err = NULL;
// A callback that signals failure surfaces RZ_NUM_ERR_UNCOMPUTABLE.
mu_assert_false(rz_num_math_value_ex(NULL, "boom()", &opt, &v, &err),
"failing callback");
mu_assert_eq(v.err, RZ_NUM_ERR_UNCOMPUTABLE, "boom -> UNCOMPUTABLE");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Arity mismatch on a custom function.
mu_assert_false(rz_num_math_value_ex(NULL, "triple(1, 2)", &opt, &v, &err),
"wrong arity");
mu_assert_eq(v.err, RZ_NUM_ERR_NOT_IMPLEMENTED, "arity -> NOT_IMPLEMENTED");
rz_num_value_fini(&v);
free(err);
err = NULL;
// A function may be registered under a non-ASCII (Unicode) name
// and is then callable by that name. The name is copied as UTF-8,
// so any identifier the grammar accepts works.
// \u0441\u0443\u043c\u043c\u0430 = Cyrillic "summa" (sum)
// \u5408\u8ba1 = CJK "total"
mu_assert_true(rz_num_func_registry_add(reg,
"\u0441\u0443\u043c\u043c\u0430", -1, tfn_sum, NULL),
"register Cyrillic-named function");
mu_assert_true(rz_num_func_registry_add(reg,
"\u5408\u8ba1", -1, tfn_sum, NULL),
"register CJK-named function");
mu_assert_true(rz_num_math_value_ex(NULL,
"\u0441\u0443\u043c\u043c\u0430(1, 2, 3)", &opt, &v, &err),
"call Cyrillic-named function");
mu_assert_eq(v.val.n, 6, "summa(1,2,3) == 6");
rz_num_value_fini(&v);
free(err);
err = NULL;
// A Unicode-named function nests with the rest of the language.
mu_assert_true(rz_num_math_value_ex(NULL,
"\u5408\u8ba1(10, 20) * 2", &opt, &v, &err),
"CJK-named function in expr");
mu_assert_eq(v.val.n, 60, "total(10,20)*2 == 60");
rz_num_value_fini(&v);
free(err);
err = NULL;
// A variadic function may take many arguments - well beyond the
// small inline buffer the evaluator keeps for the common case -
// so registration is not limited to a fixed maximum arity.
mu_assert_true(rz_num_math_value_ex(NULL,
"\u0441\u0443\u043c\u043c\u0430(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)",
&opt, &v, &err),
"10-argument call");
mu_assert_eq(v.val.n, 55, "sum of 1..10 == 55");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_assert_true(rz_num_math_value_ex(NULL,
"\u5408\u8ba1(100, 200, 300, 400, 500, 600, 700, 800, "
"900, 1000, 1100, 1200)",
&opt, &v, &err),
"12-argument call");
mu_assert_eq(v.val.n, 7800, "sum of the 12 args == 7800");
rz_num_value_fini(&v);
free(err);
err = NULL;
// A registered function may also shadow a built-in by name; here
// a variadic "min" replaces the 2-arg built-in and now accepts
// any number of arguments.
mu_assert_true(rz_num_func_registry_add(reg, "min", -1, tfn_sum, NULL),
"register variadic shadow of min");
mu_assert_true(rz_num_math_value_ex(NULL, "min(1, 2, 3, 4)", &opt, &v, &err),
"shadowed min with 4 args");
mu_assert_eq(v.val.n, 10, "shadow min(1,2,3,4) sums to 10");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_func_registry_free(reg);
mu_end;
}
bool test_rz_num_math_value_io_read() {
RzNumMathOptions opt = { .io_read = tio_read, .io_read_user = NULL };
RzNumValue v;
char *err = NULL;
// 0x1000 ^ 0xcafe == 0xdafe; masked to 32 bits is unchanged.
mu_assert_true(rz_num_math_value_ex(NULL, "0x1000:le32", &opt, &v, &err),
"typed read le32");
mu_assert_eq(v.kind, RZ_NUM_KIND_UT64, "read kind");
mu_assert_eq(v.val.n, 0xdafe, "0x1000:le32 dereferenced");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Width-2 read with the OPPOSITE endianness of how the mock
// wrote the bytes: the conceptual value 0xdafe was written
// little-endian as {0xfe, 0xda}, so a be16 decode swaps it to
// 0xfeda. The previous test mock pretended endianness did not
// matter (it returned the same scalar for either flag), which
// hid this byte-swap; the raw-bytes callback signature makes
// the actual semantics visible.
mu_assert_true(rz_num_math_value_ex(NULL, "0x1000:be16", &opt, &v, &err),
"typed read be16");
mu_assert_eq(v.val.n, 0xfeda, "0x1000:be16 byte-swaps the LE bytes");
rz_num_value_fini(&v);
free(err);
err = NULL;
// The dereferenced value is usable in further arithmetic.
mu_assert_true(rz_num_math_value_ex(NULL, "0x1000:le32 + 1", &opt, &v, &err),
"typed read in expr");
mu_assert_eq(v.val.n, 0xdaff, "0x1000:le32 + 1");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Without a callback, the literal address is returned.
mu_assert_true(rz_num_math_value(NULL, "0x1000:le32", &v, &err),
"no callback -> literal");
mu_assert_eq(v.val.n, 0x1000, "literal address when no io_read");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
// Fake IO reader that writes `addr` itself, in little-endian byte
// order, as the raw bytes of the read. This lets a test express the
// exact bit pattern it wants via the address literal: an address of
// 0xffffffff with a `:s32` read produces the bytes {0xff, 0xff, 0xff,
// 0xff}, which sign-extends to -1; 0x3fc00000 with `:f32` produces
// the IEEE-754 bytes for 1.5; and so on. The semantic preserves what
// the previous typed-callback mock produced through the evaluator's
// new raw-bytes path.
static ut64 tio_read_pattern(void *user, ut64 addr, ut8 *buf, size_t len) {
(void)user;
for (size_t i = 0; i < len; i++) {
buf[i] = (i < 8) ? (ut8)(addr >> (8 * i)) : 0;
}
return len;
}
bool test_rz_num_math_value_typed_reads() {
RzNumMathOptions opt = { .io_read = tio_read_pattern, .io_read_user = NULL };
RzNumValue v;
char *err = NULL;
// Signed read: 0xffffffff as a signed 32-bit is -1.
mu_assert_true(rz_num_math_value_ex(NULL, "0xffffffff:s32", &opt, &v, &err),
"signed s32 read");
mu_assert_eq(v.kind, RZ_NUM_KIND_UT64, "s32 kind");
mu_assert_eq(v.val.n, (ut64)(-1), "0xffffffff:s32 == -1");
rz_num_value_fini(&v);
free(err);
err = NULL;
// The same bytes read unsigned are 0xffffffff.
mu_assert_true(rz_num_math_value_ex(NULL, "0xffffffff:le32", &opt, &v, &err),
"unsigned le32 read");
mu_assert_eq(v.val.n, 0xffffffff, "0xffffffff:le32 == 0xffffffff");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Signed 16-bit: 0xfffe is -2.
mu_assert_true(rz_num_math_value_ex(NULL, "0xfffe:s16", &opt, &v, &err),
"signed s16 read");
mu_assert_eq(v.val.n, (ut64)(-2), "0xfffe:s16 == -2");
rz_num_value_fini(&v);
free(err);
err = NULL;
// A positive signed value is unchanged.
mu_assert_true(rz_num_math_value_ex(NULL, "0x7f:s8", &opt, &v, &err),
"signed s8 positive");
mu_assert_eq(v.val.n, 0x7f, "0x7f:s8 == 127");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Float read (single precision): the raw bits of 1.5f are
// 0x3fc00000.
mu_assert_true(rz_num_math_value_ex(NULL, "0x3fc00000:f32", &opt, &v, &err),
"float f32 read");
mu_assert_eq(v.kind, RZ_NUM_KIND_FLOAT, "f32 kind is float");
mu_assert_true(v.val.d == 1.5, "0x3fc00000:f32 == 1.5");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Float read (double precision): the raw bits of 2.0 are
// 0x4000000000000000.
mu_assert_true(rz_num_math_value_ex(NULL, "0x4000000000000000:f64", &opt, &v, &err),
"float f64 read");
mu_assert_true(v.val.d == 2.0, "...:f64 == 2.0");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Half precision (f16): the raw bits of 1.5 are 0x3e00.
mu_assert_true(rz_num_math_value_ex(NULL, "0x3e00:f16", &opt, &v, &err),
"float f16 read");
mu_assert_eq(v.kind, RZ_NUM_KIND_FLOAT, "f16 kind is float");
mu_assert_true(v.val.d == 1.5, "0x3e00:f16 == 1.5");
rz_num_value_fini(&v);
free(err);
err = NULL;
// f16 of 0xc000 is -2.0.
mu_assert_true(rz_num_math_value_ex(NULL, "0xc000:f16", &opt, &v, &err),
"float f16 negative");
mu_assert_true(v.val.d == -2.0, "0xc000:f16 == -2.0");
rz_num_value_fini(&v);
free(err);
err = NULL;
// f16 Inf/NaN (exp all ones). These decode through a bit-pattern
// path (no 0.0/0.0), so check the special values come out right.
mu_assert_true(rz_num_math_value_ex(NULL, "0x7c00:f16", &opt, &v, &err),
"float f16 +inf read");
mu_assert_true(isinf(v.val.d) && v.val.d > 0, "0x7c00:f16 == +inf");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_assert_true(rz_num_math_value_ex(NULL, "0xfc00:f16", &opt, &v, &err),
"float f16 -inf read");
mu_assert_true(isinf(v.val.d) && v.val.d < 0, "0xfc00:f16 == -inf");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_assert_true(rz_num_math_value_ex(NULL, "0x7e00:f16", &opt, &v, &err),
"float f16 nan read");
mu_assert_true(isnan(v.val.d), "0x7e00:f16 == nan");
rz_num_value_fini(&v);
free(err);
err = NULL;
// 128-bit reads need two underlying IO reads (the callback
// returns ut64); the result is a width-128 bit-vector. The mock
// returns `addr` for each read, so at address X we see lo=X and
// hi=X+8 in little-endian and the swap in big-endian.
mu_assert_true(rz_num_math_value_ex(NULL, "0x100:128", &opt, &v, &err),
":128 integer read");
mu_assert_eq(v.kind, RZ_NUM_KIND_BITVECTOR, ":128 yields bitvector");
mu_assert_eq(rz_bv_len(v.val.bv), 128, ":128 is width 128");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0x100, ":128 low qword");
rz_num_value_fini(&v);
free(err);
err = NULL;
// :f128 likewise yields a width-128 bit-vector carrying the raw
// IEEE-754 quad-precision bit pattern. RzNum's float kind is
// double, so the quad value is not decoded to a native double;
// the bit-vector preserves every bit for inspection and
// bit-vector arithmetic.
mu_assert_true(rz_num_math_value_ex(NULL, "0x100:f128", &opt, &v, &err),
":f128 quad-precision read");
mu_assert_eq(v.kind, RZ_NUM_KIND_BITVECTOR, ":f128 yields bitvector");
mu_assert_eq(rz_bv_len(v.val.bv), 128, ":f128 is width 128");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Two reads of the same 128-bit value are equal.
mu_assert_true(rz_num_math_value_ex(NULL, "0x200:128 == 0x200:128",
&opt, &v, &err),
":128 equality");
mu_assert_eq(v.val.n, 1, ":128 equals itself");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Without an IO callback the literal address comes back (parity
// with the other typed-read widths).
mu_assert_true(rz_num_math_value(NULL, "0x1234:128", &v, &err),
":128 literal fallback");
mu_assert_eq(v.val.n, 0x1234, ":128 == address with no io_read");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_assert_true(rz_num_math_value(NULL, "0x1234:f128", &v, &err),
":f128 literal fallback");
mu_assert_eq(v.val.n, 0x1234, ":f128 == address with no io_read");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_math_value_bitvector_extended_ops() {
// Bit-vector rotates, **, log and the signed family. All
// arithmetic is modular at the combined width; rotates cycle
// bits within the width and a rotation by W is the identity.
RzNumValue v;
char *err = NULL;
// Rotates.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "0x12u8 <<< 4", &v, &err),
"u8 rol 4");
mu_assert_eq(v.kind, RZ_NUM_KIND_BITVECTOR, "rol yields bv");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0x21, "0x12u8 rol 4 == 0x21");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "0x12u8 >>> 4", &v, &err),
"u8 ror 4");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0x21, "0x12u8 ror 4 == 0x21");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "0x12345678u32 <<< 8", &v, &err),
"u32 rol 8");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0x34567812, "u32 rol 8 wraps top byte");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Rotation by a full width is the identity.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "0xabu8 <<< 8", &v, &err),
"u8 rol 8 == identity");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0xab, "rol full-width is identity");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Power: modular at W bits, exponentiation by squaring.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "3u32 ** 10u32", &v, &err),
"u32 power");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 59049, "3^10 == 59049");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "2u8 ** 8u8", &v, &err),
"u8 power overflow wraps");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0, "2u8 ** 8 wraps to 0");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "2u32 ** 32u32", &v, &err),
"u32 power overflow wraps");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0, "2u32 ** 32 wraps to 0");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Logarithm: floor(log_base(value)) at the combined width.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "2u32 log 16u32", &v, &err),
"u32 log");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 4, "log2(16) == 4");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "2u8 log 4u8", &v, &err),
"u8 log");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 2, "log2(4) == 2");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Signed family on bit-vectors. 0xff as u8-signed is -1, 0xfe is
// -2; sdiv truncates toward zero, sar sign-extends.
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "0xffu8 sdiv 2u8", &v, &err),
"u8 sdiv");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0, "-1 sdiv 2 == 0 (trunc)");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "0xffu8 smod 3u8", &v, &err),
"u8 smod");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0xff, "-1 smod 3 == -1");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_true(rz_num_math_value(NULL, "0xfeu8 sar 1u8", &v, &err),
"u8 sar");
mu_assert_eq(rz_bv_to_ut64(v.val.bv), 0xff, "-2 sar 1 == -1 (sign-fill)");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Division by zero on the signed bit-vector ops is reported.
mu_assert_false(rz_num_math_value(NULL, "5u8 sdiv 0u8", &v, &err),
"bv sdiv by zero fails");
mu_assert_eq(v.err, RZ_NUM_ERR_DIV_ZERO, "bv sdiv 0 -> DIV_ZERO");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_math_value_builtins() {
// Built-in math functions: min, max, abs, popcount, plus the
// unicode-named aliases that demonstrate the parser's
// transparent UTF-8 identifier handling.
ASSERT_U64("min(3, 7)", 3);
ASSERT_U64("max(3, 7)", 7);
ASSERT_U64("min(100, 50)", 50);
ASSERT_U64("abs(0xfffffffffffffffb)", 5); // -5 in ut64
ASSERT_U64("abs(5)", 5);
ASSERT_U64("popcount(0xff)", 8);
ASSERT_U64("popcount(0xffff0000)", 16);
ASSERT_U64("popcount(0)", 0);
// Nested calls and use as a sub-expression.
ASSERT_U64("min(1, max(2, 3))", 1);
ASSERT_U64("max(min(10, 20), min(30, 40))", 30);
// Unicode-named aliases. The byte strings below are the UTF-8
// encodings of "минимум" and "максимум" (Russian for min/max).
// The grammar's identifier rule (XID_Start / XID_Continue per
// tree-sitter's word handling) admits these without any
// special-casing.
ASSERT_U64("\xd0\xbc\xd0\xb8\xd0\xbd\xd0\xb8\xd0\xbc\xd1\x83\xd0\xbc(5, 10)", 5);
ASSERT_U64("\xd0\xbc\xd0\xb0\xd0\xba\xd1\x81\xd0\xb8\xd0\xbc\xd1\x83\xd0\xbc(5, 10)", 10);
// Arity errors surface as RZ_NUM_ERR_NOT_IMPLEMENTED with a
// descriptive message; we only check the error category, not
// the diagnostic string.
RzNumValue v;
char *err = NULL;
rz_num_value_init(&v);
mu_assert_false(rz_num_math_value(num, "min(1)", &v, &err), "wrong arity");
mu_assert_eq(v.err, RZ_NUM_ERR_NOT_IMPLEMENTED, "arity -> NOT_IMPLEMENTED");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_assert_false(rz_num_math_value(num, "foo(1, 2)", &v, &err), "unknown");
mu_assert_eq(v.err, RZ_NUM_ERR_NOT_IMPLEMENTED, "unknown -> NOT_IMPLEMENTED");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
// Math and bit functions: log/ln/log2/log10/sqrt (float results),
// floor/ceil/round (float -> ut64 bridge), gcd, clz, ctz. These are
// the domain-relevant additions on top of min/max/abs/popcount/len.
bool test_rz_num_math_value_math_functions() {
num = NULL;
// Exact-valued transcendentals come back as exact floats.
ASSERT_F64("log(1)", 0.0);
ASSERT_F64("ln(1)", 0.0);
ASSERT_F64("log2(256)", 8.0);
ASSERT_F64("log2(1024)", 10.0);
ASSERT_F64("log10(1000)", 3.0);
ASSERT_F64("log10(1)", 0.0);
ASSERT_F64("sqrt(16)", 4.0);
ASSERT_F64("sqrt(0)", 0.0);
ASSERT_F64("sqrt(2)", sqrt(2.0)); // irrational: compare to libm
ASSERT_F64("log(2.718281828459045)", log(2.718281828459045));
// log() is natural; the base-b logarithm is the `b log x` operator.
ASSERT_F64("ln(100)", log(100.0));
// Integers are unsigned here, so sqrt(-1) is sqrt(2^64-1), not a
// domain error (same model that makes 0 - 1 == 2^64-1). Genuine
// domain errors come from negative floats; see below.
ASSERT_F64("sqrt(-1)", sqrt((double)UT64_MAX));
// floor/ceil/round bridge float to ut64 (two's complement for
// negatives). C round() rounds halves away from zero.
ASSERT_U64("floor(3.7)", 3);
ASSERT_U64("ceil(3.2)", 4);
ASSERT_U64("floor(3.0)", 3);
ASSERT_U64("ceil(3.0)", 3);
ASSERT_U64("round(2.5)", 3);
ASSERT_U64("round(2.4)", 2);
ASSERT_U64("round(-2.5)", (ut64)(-3LL));
ASSERT_U64("floor(-1.5)", (ut64)(-2LL));
ASSERT_U64("ceil(-1.5)", (ut64)(-1LL));
// Integer arguments pass straight through, keeping full precision.
ASSERT_U64("floor(42)", 42);
ASSERT_U64("ceil(42)", 42);
ASSERT_U64("round(42)", 42);
// gcd over ut64.
ASSERT_U64("gcd(48, 36)", 12);
ASSERT_U64("gcd(1071, 462)", 21);
ASSERT_U64("gcd(17, 5)", 1);
ASSERT_U64("gcd(100, 0)", 100);
ASSERT_U64("gcd(0, 0)", 0);
// clz / ctz over the 64-bit projection.
ASSERT_U64("clz(1)", 63);
ASSERT_U64("clz(0)", 64);
ASSERT_U64("clz(0x8000000000000000)", 0);
ASSERT_U64("clz(0xff)", 56);
ASSERT_U64("ctz(8)", 3);
ASSERT_U64("ctz(0)", 64);
ASSERT_U64("ctz(1)", 0);
ASSERT_U64("ctz(0x8000000000000000)", 63);
// Compositions: number of bits needed to represent N is
// floor(log2(N)) + 1, a staple of the analysis domain.
ASSERT_U64("floor(log2(1000)) + 1", 10);
ASSERT_U64("floor(log2(255)) + 1", 8);
ASSERT_U64("ceil(log2(1000))", 10);
ASSERT_U64("clz(0xff) + ctz(0x100)", 64); // 56 + 8
// Domain violations are reported, not guessed.
RzNumValue v;
char *err = NULL;
rz_num_value_init(&v);
mu_assert_false(rz_num_math_value(num, "log(0)", &v, &err), "log(0)");
mu_assert_eq(v.err, RZ_NUM_ERR_UNCOMPUTABLE, "log(0) -> UNCOMPUTABLE");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_false(rz_num_math_value(num, "log2(0)", &v, &err), "log2(0)");
mu_assert_eq(v.err, RZ_NUM_ERR_UNCOMPUTABLE, "log2(0) -> UNCOMPUTABLE");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_false(rz_num_math_value(num, "sqrt(-1.5)", &v, &err), "sqrt(-1.5)");
mu_assert_eq(v.err, RZ_NUM_ERR_UNCOMPUTABLE, "sqrt(-1.5) -> UNCOMPUTABLE");
rz_num_value_fini(&v);
free(err);
err = NULL;
rz_num_value_init(&v);
mu_assert_false(rz_num_math_value(num, "log(-2.5)", &v, &err), "log(-2.5)");
mu_assert_eq(v.err, RZ_NUM_ERR_UNCOMPUTABLE, "log(-2.5) -> UNCOMPUTABLE");
rz_num_value_fini(&v);
free(err);
err = NULL;
// Wrong arity still reports NOT_IMPLEMENTED.
rz_num_value_init(&v);
mu_assert_false(rz_num_math_value(num, "gcd(1)", &v, &err), "gcd arity");
mu_assert_eq(v.err, RZ_NUM_ERR_NOT_IMPLEMENTED, "gcd(1) -> NOT_IMPLEMENTED");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
// "Torture" cases: each expression deliberately piles many grammar
// features into a single computation - multiple numeric bases (hex,
// binary, octal, C-style octal, ternary), arithmetic, signed
// operators (sdiv/smod/sar), shifts and rotates, the full bitwise
// precedence ladder (& > ^ > |, all looser than the shifts), unary
// operators, comparisons and equality, nested conditionals,
// assignment/sequencing, fixed-width bit-vectors, and nested function
// calls. Every expected value was cross-checked against an independent
// Python oracle and the sympy-based verifier. Grouping notes are given
// where the precedence is non-obvious (the shift operators - including
// sar - bind looser than + and -, so e.g. `(-100) sar 3 + ...` parses
// as `(-100) sar (3 + ...)`).
bool test_rz_num_math_value_torture() {
// bases + arithmetic + function + shift + full bitwise ladder
ASSERT_U64("(0x10 + 0b110) * 0o3 - gcd(36, 24) << 2 & 0xff | 0t21", 0xdf);
// signed sdiv/smod, comparison, conditional, abs/popcount, unary -
ASSERT_U64("(100 sdiv 7 > 10 ? abs(0 - 50) : popcount(0xff)) + (-20 smod 6)", 0x30);
// rotate, exponent, bitwise xor/and with shift-tighter-than-&
ASSERT_U64("(1 <<< 8 | 0xff) ^ (2 ** 4 - 1) & 0xf0 >>> 4", 0x1f0);
// unary ~, logical/arith shifts, & ^ | ladder, popcount
ASSERT_U64("~0 >> 56 & 0xff ^ 0b1100 | popcount(0xffff) << 2", 0xf3);
// nested gcd/min/max/clz, float-bridging floor(sqrt(...)), octal
ASSERT_U64("max(gcd(48,36), min(20, clz(0x100))) * 2 + floor(sqrt(0x100)) - 0o17", 0x29);
// C-style octal (034, 010), equality-in-conditional, ternary base
ASSERT_U64("(034 + 010) * 2 - 0x10 + (5 > 3 == 1 ? 0b111 : 0t22)", 0x3f);
// large hex, masking, shift, xor with a rotate of the high word
ASSERT_U64("((0xdeadbeef & 0xffff) | (0xcafe << 4)) ^ 0xf0f0 >>> 8", 0xf0000000000cbf1f);
// sar binds looser than +/- : == `(-100) sar (3 + 28 - 2)` == -1
ASSERT_U64("(-100) sar 3 + 100 sdiv 7 * 2 - 5 smod 3", 0xffffffffffffffff);
// right-assoc **, comparison/equality conditionals, clz, len(int)
ASSERT_U64("(2 ** 3 ** 2 > 500 ? 1 : 0) + (popcount(0xff) == 8 ? clz(1) : 0) + len(0x1234)", 0x4d);
// assignment + sequencing, reuse of a bound variable, bitwise
ASSERT_U64("a = 0xff; b = a << 4 | a; b & 0xf0f ^ 0x101", 0xe0e);
// gcd of an exponent and an octal, conditional, abs, rotate
ASSERT_U64("gcd(2 ** 8, 0o1000) + (3 < 2 ? 999 : abs(10 - 0x20)) * (1 <<< 2)", 0x158);
// u8 wraparound inside an equality test, clz/ctz, shift, xor, or
ASSERT_U64("(0xffu8 + 1u8 == 0 ? 0xaa : 0xbb) | (clz(0x8000) << 1) ^ ctz(0x400)", 0xea);
// string-byte len, bit-vector len, popcount, arithmetic + or
ASSERT_U64("(len(\"ABCD\") + len(\"XY\")) * 2 - len(0xffffu16) | popcount(0xf0f0)", 0x58);
// deeply nested conditionals on both sides, right-assoc chaining
ASSERT_U64("(10 > 5 ? (20 < 15 ? 1 : (3 == 3 ? 0xff : 0)) : 0xaa) + (0 ? 1 : 2 ? 3 : 4)", 0x102);
// one long precedence chain: mult>add>shift>&>^>|
ASSERT_U64("1 + 2 * 3 - 4 << 1 | 5 & 6 ^ 7 + 8 >> 2", 0x7);
mu_end;
}
// both variable names and function names. The grammar's identifier
// rule admits any run of non-operator, non-control UTF-8, so these
// work without special-casing; this test pins that behaviour down.
//
// All identifiers are written as \x escapes to keep the source
// ASCII-clean; the comment on each line gives the script and the
// readable form.
bool test_rz_num_math_value_unicode_names() {
num = NULL;
// Variable names
// A name is bound with (name = v) and then read back, so the
// round-trip proves the same byte sequence lexes identically in
// both the assignment target and a later reference.
// Mathematical / Greek letters.
ASSERT_U64("(\xcf\x80 = 3) + \xcf\x80", 6); // "π" (Greek pi)
ASSERT_U64("(\xce\xb1\xce\xb2\xce\xb3 = 2) * \xce\xb1\xce\xb2\xce\xb3", 4); // "αβγ"
ASSERT_U64("(\xe2\x84\x95 = 9) - \xe2\x84\x95", 0); // "" (U+2115, double-struck N)
// Chinese (Han).
ASSERT_U64("(\xe5\x8d\x8a\xe5\xbe\x84 = 5) * \xe5\x8d\x8a\xe5\xbe\x84", 25); // "半径" (radius)
ASSERT_U64("(\xe5\xb9\xb3\xe5\x9d\x87 = 8) + \xe5\xb9\xb3\xe5\x9d\x87", 16); // "平均" (average)
// Japanese (kanji, and a katakana run).
ASSERT_U64("(\xe5\xa4\x89\xe6\x95\xb0 = 7) + 1", 8); // "変数" (variable)
ASSERT_U64("(\xe3\x83\x98\xe3\x83\xb3\xe3\x82\xb9\xe3\x82\xa6 = 4) * 2", 8); // "ヘンスウ"
// Arabic (right-to-left script; stored and compared as bytes).
ASSERT_U64("(\xd9\x85\xd8\xaa\xd8\xba\xd9\x8a\xd8\xb1 = 4) + "
"\xd9\x85\xd8\xaa\xd8\xba\xd9\x8a\xd8\xb1",
8); // "متغير" (variable)
// Cyrillic.
ASSERT_U64("(\xd0\xbd\xd0\xb0\xd1\x87 = 10) + \xd0\xbd\xd0\xb0\xd1\x87", 20); // "нач"
// A bound Unicode name is independent of an ASCII one.
ASSERT_U64("(\xcf\x83 = 1) + (s = 2) + \xcf\x83 + s", 6); // "σ" vs "s"
// Function names
// These map to built-in aliases registered in the evaluator's
// builtin_table (Cyrillic, Chinese/Japanese, Arabic, Greek
// symbol). Each is functionally identical to an English builtin.
ASSERT_U64("\xd0\xbc\xd0\xb8\xd0\xbd\xd0\xb8\xd0\xbc\xd1\x83\xd0\xbc(5, 10)", 5); // "минимум" min
ASSERT_U64("\xd0\xbc\xd0\xb0\xd0\xba\xd1\x81\xd0\xb8\xd0\xbc\xd1\x83\xd0\xbc(5, 10)", 10); // "максимум" max
ASSERT_U64("\xe6\x9c\x80\xe5\xb0\x8f(5, 3)", 3); // "最小" (Chinese/Japanese: min)
ASSERT_U64("\xe6\x9c\x80\xe5\xa4\xa7(5, 3)", 5); // "最大" (Chinese/Japanese: max)
ASSERT_U64("\xd8\xaf\xd8\xa7\xd9\x84\xd8\xa9(0xfffffffffffffffb)", 5); // "دالة" (Arabic) abs(-5)
ASSERT_U64("\xce\xa3(0xff)", 8); // "Σ" (Greek sigma) popcount
// Unicode function name composed with a Unicode variable arg.
ASSERT_U64("(\xe5\x8d\x8a\xe5\xbe\x84 = 12) + \xe6\x9c\x80\xe5\xb0\x8f(\xe5\x8d\x8a\xe5\xbe\x84, 7)",
19); // 半径=12, 12 + 最小(12, 7) = 12 + 7
// An unknown Unicode function name still errors cleanly (it is
// not silently treated as a variable).
RzNumValue v;
char *err = NULL;
rz_num_value_init(&v);
mu_assert_false(
rz_num_math_value(NULL, "\xe9\x96\xa2\xe6\x95\xb0(1, 2)", &v, &err),
"unknown unicode function"); // "関数" (Japanese: function), unregistered
mu_assert_eq(v.err, RZ_NUM_ERR_NOT_IMPLEMENTED,
"unknown unicode fn -> NOT_IMPLEMENTED");
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_math_value_pretty_print() {
// Exercise the per-kind pretty-printer. We don't pin down the
// exact bytes (whitespace and order may be tweaked) but assert
// that each kind's defining tokens appear, that on-error output
// includes the category, and that the one-line form is sensible.
RzNumValue v;
char *err = NULL;
// UT64 case: must include the hex form, signed/unsigned dec,
// the binary token, and the unit conversion.
rz_num_math_value(NULL, "0x100000", &v, &err);
RzStrBuf *sb = rz_strbuf_new(NULL);
rz_num_value_print(&v, sb);
char *out = rz_strbuf_drain(sb);
mu_assert_notnull(out, "ut64 print produced output");
mu_assert_notnull(strstr(out, "0x100000"), "hex");
mu_assert_notnull(strstr(out, "1048576"), "decimal");
mu_assert_notnull(strstr(out, "binary"), "binary line");
mu_assert_notnull(strstr(out, "1M"), "unit");
free(out);
char *one = rz_num_value_tostring(&v);
mu_assert_streq(one, "0x100000", "compact form");
free(one);
rz_num_value_fini(&v);
free(err);
err = NULL;
// DECIMAL case: a decimal literal keeps full precision (BIGDECIMAL),
// so the print shows the exact decimal and its scientific form.
rz_num_math_value(NULL, "3.14159", &v, &err);
sb = rz_strbuf_new(NULL);
rz_num_value_print(&v, sb);
out = rz_strbuf_drain(sb);
mu_assert_notnull(strstr(out, "3.14159"), "decimal value");
mu_assert_notnull(strstr(out, "decimal"), "decimal label");
free(out);
rz_num_value_fini(&v);
free(err);
err = NULL;
// BIG case: decimal + hex + width.
rz_num_math_value(NULL, "0x10000000000000000", &v, &err);
sb = rz_strbuf_new(NULL);
rz_num_value_print(&v, sb);
out = rz_strbuf_drain(sb);
mu_assert_notnull(strstr(out, "decimal"), "decimal label");
mu_assert_notnull(strstr(out, "18446744073709551616"),
"exact decimal form");
mu_assert_notnull(strstr(out, "0x10000000000000000"), "hex form");
mu_assert_notnull(strstr(out, "width"), "width hint");
free(out);
rz_num_value_fini(&v);
free(err);
err = NULL;
// Error case must surface the category name.
rz_num_math_value(NULL, "1 / 0", &v, &err);
sb = rz_strbuf_new(NULL);
rz_num_value_print(&v, sb);
out = rz_strbuf_drain(sb);
mu_assert_notnull(strstr(out, "error"), "error label");
mu_assert_notnull(strstr(out, "division"), "category name");
free(out);
one = rz_num_value_tostring(&v);
mu_assert_notnull(strstr(one, "error"), "compact carries error too");
free(one);
rz_num_value_fini(&v);
free(err);
err = NULL;
// rz_num_error_name is stable and covers every category.
mu_assert_streq(rz_num_error_name(RZ_NUM_ERR_OK), "ok", "ok");
mu_assert_streq(rz_num_error_name(RZ_NUM_ERR_DIV_ZERO),
"division by zero", "div0");
mu_assert_streq(rz_num_error_name(RZ_NUM_ERR_TIMEOUT),
"timeout", "timeout");
mu_assert_streq(rz_num_error_name((RzNumError)9999),
"unknown", "out-of-enum");
mu_end;
}
bool test_rz_num_math_value_bitvector_unicode() {
RzNumValue v;
char *err = NULL;
// 200u8 + 100u8 == 44 (0x2c) at width 8.
bool ok = rz_num_math_value(NULL, "200u8 + 100u8", &v, &err);
mu_assert_true(ok, "bv add");
mu_assert_eq(v.kind, RZ_NUM_KIND_BITVECTOR, "is bitvector");
// ASCII default: plain hex / 0b-prefixed binary, width last, and
// no subscript bytes anywhere.
RzStrBuf *sb = rz_strbuf_new(NULL);
rz_num_value_print(&v, sb);
char *ascii = rz_strbuf_drain(sb);
mu_assert_notnull(strstr(ascii, "hex 0x2c\n"), "ascii hex");
mu_assert_notnull(strstr(ascii, "binary 0b00101100\n"), "ascii binary");
mu_assert_notnull(strstr(ascii, "width 8 bits"), "ascii width");
// U+2088 (subscript 8) is the bytes e2 82 88; must be absent.
mu_assert_null(strstr(ascii, "\u2088"), "ascii has no subscript");
// Width row comes after the value rows.
mu_assert_true(strstr(ascii, "hex") < strstr(ascii, "width"),
"ascii width is below the values");
free(ascii);
// UTF-8 option: the width is folded into the value rows as a
// subscript (0x2c then U+2088), the binary form drops 0b, and
// the width row still comes last.
RzNumPrintOptions opts = { .utf8 = true };
sb = rz_strbuf_new(NULL);
rz_num_value_print_ex(&v, &opts, sb);
char *uni = rz_strbuf_drain(sb);
mu_assert_notnull(strstr(uni, "hex 0x2c\u2088\n"), "utf8 hex subscript");
mu_assert_notnull(strstr(uni, "binary 00101100\u2088\n"), "utf8 binary subscript, no 0b");
mu_assert_null(strstr(uni, "0b"), "utf8 binary has no 0b prefix");
mu_assert_true(strstr(uni, "hex") < strstr(uni, "width"),
"utf8 width is below the values");
free(uni);
rz_num_value_fini(&v);
free(err);
err = NULL;
// A 16-bit value renders a two-digit subscript: U+2081 U+2086.
ok = rz_num_math_value(NULL, "10u16 * 10u16", &v, &err);
mu_assert_true(ok, "bv mul 16");
sb = rz_strbuf_new(NULL);
rz_num_value_print_ex(&v, &opts, sb);
uni = rz_strbuf_drain(sb);
mu_assert_notnull(strstr(uni, "hex 0x0064\u2081\u2086\n"), "16-bit hex subscript");
mu_assert_notnull(strstr(uni, "binary 0000000001100100\u2081\u2086\n"), "16-bit binary subscript");
free(uni);
rz_num_value_fini(&v);
free(err);
err = NULL;
// NULL opts behaves exactly like the ASCII default.
ok = rz_num_math_value(NULL, "5u8", &v, &err);
mu_assert_true(ok, "5u8");
sb = rz_strbuf_new(NULL);
rz_num_value_print_ex(&v, NULL, sb);
uni = rz_strbuf_drain(sb);
mu_assert_notnull(strstr(uni, "0b"), "NULL opts keeps 0b prefix");
mu_assert_null(strstr(uni, "\u2085"), "NULL opts -> no subscript");
free(uni);
rz_num_value_fini(&v);
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_il_lift() {
char *err = NULL;
#define ASSERT_LIFT(expr, want) \
do { \
char *_e = NULL; \
RzILOpPure *_op = rz_il_lift_num(NULL, (expr), NULL, &_e); \
mu_assert_notnull(_op, expr " lifts"); \
RzStrBuf _sb; \
rz_strbuf_init(&_sb); \
RzILStringifyCtx _ctx = { 0 }; \
rz_il_op_pure_stringify_unicode(&_ctx, _op, &_sb); \
rz_il_op_pure_free(_op); \
free(_e); \
char *_s = rz_strbuf_drain_nofree(&_sb); \
mu_assert_streq_free(_s, (want), expr); \
} while (0)
// Arithmetic keeps precedence and uses RzIL glyphs; ut64 literals
// become 64-bit bit-vector constants with a subscript width.
ASSERT_LIFT("1 + 2 * 3", "(0x1\u2086\u2084 + (0x2\u2086\u2084 * 0x3\u2086\u2084))");
ASSERT_LIFT("(1 + 2) * 3", "((0x1\u2086\u2084 + 0x2\u2086\u2084) * 0x3\u2086\u2084)");
ASSERT_LIFT("0x10 & 0xff", "(0x10\u2086\u2084 & 0xff\u2086\u2084)");
// Shifts carry an explicit fill bit as a third operand in RzIL's
// serialization; a logical shift fills with false (\u22a5).
ASSERT_LIFT("1 << 4", "(0x1\u2086\u2084 \u226a 0x4\u2086\u2084 \u22a5)");
ASSERT_LIFT("10 == 10", "(0xa\u2086\u2084 \u2261 0xa\u2086\u2084)");
// Width-suffixed literals keep their bit-vector width.
ASSERT_LIFT("5u8 + 3u8", "(0x5\u2088 + 0x3\u2088)");
// Unary minus / bitwise not render as a prefix glyph directly on the
// operand (no wrapping parentheses), matching RzIL's exporter.
ASSERT_LIFT("-5", "\u22120x5\u2086\u2084");
ASSERT_LIFT("~0xff", "~0xff\u2086\u2084");
// Functions have no structural RzIL form, so they are grounded to
// a concrete bit-vector constant first: min(5,3) == 3.
ASSERT_LIFT("min(5, 3) + 1", "(0x3\u2086\u2084 + 0x1\u2086\u2084)");
// ** is likewise grounded: 2 ** 8 == 256.
ASSERT_LIFT("2 ** 8", "0x100\u2086\u2084");
// Signed division / remainder lift to their RzIL glyphs (the
// superscript plus marks signedness): sdiv -> /+, smod -> %+.
ASSERT_LIFT("10 sdiv 3", "(0xa\u2086\u2084 /\u207a 0x3\u2086\u2084)");
ASSERT_LIFT("10 smod 3", "(0xa\u2086\u2084 %\u207a 0x3\u2086\u2084)");
// Arithmetic shift right is a plain RzIL shiftr whose fill bit is the
// sign bit (msb, the \u2191 glyph) of the shifted value.
ASSERT_LIFT("256 sar 2",
"(0x100\u2086\u2084 \u226b 0x2\u2086\u2084 \u21910x100\u2086\u2084)");
// A ';'-separated sequence is grounded to its final value: the
// last statement wins and earlier bindings are honoured.
ASSERT_LIFT("1 + 2; 3 + 4", "0x7\u2086\u2084");
ASSERT_LIFT("x = 5; x + 1", "0x6\u2086\u2084");
// The ternary lifts structurally to RzIL's ite, rendered exactly
// as the RzIL Unicode exporter prints it: "(cond <ITE> then else)"
// with the two-headed-arrow glyph. This is the one control-flow
// form with a direct RzIL pure-op, so it is not grounded.
ASSERT_LIFT("1 ? 10 : 20",
"(0x1\u2086\u2084 \u21a0 0xa\u2086\u2084 0x14\u2086\u2084)");
ASSERT_LIFT("1 ? 2 + 3 : 4 * 5",
"(0x1\u2086\u2084 \u21a0 (0x2\u2086\u2084 + 0x3\u2086\u2084) (0x4\u2086\u2084 * 0x5\u2086\u2084))");
// Float-pure arithmetic lifts to the RzIL float-binop form with
// the round-nearest-even prefix - "(rne x + y)" - and the operands
// render as the bit-vector representation of the double with the
// ".f64" subscript, matching how RzIL prints a `float
// RZ_FLOAT_IEEE754_BIN_64 <bitv>` op.
ASSERT_LIFT("1.5 + 2.5",
"(rne 0x3ff8000000000000.f\u2086\u2084 + 0x4004000000000000.f\u2086\u2084)");
ASSERT_LIFT("1.0 / 2.0",
"(rne 0x3ff0000000000000.f\u2086\u2084 / 0x4000000000000000.f\u2086\u2084)");
// Mixed integer / float is grounded rather than fabricating an
// implicit cast the user did not write.
ASSERT_LIFT("1 + 2.5", "0x3\u2086\u2084");
// Float-conditioned ternary: the evaluator's "non-zero float is
// true" semantic does not match RzIL's ite (which expects a Bool),
// so the lift wraps the condition in is_fzero and SWAPS the branches
// - "(f \u2261 0 \u21a0 else then)". This makes the lifted form pick the
// same branch as the evaluator: 0.5 -> "then", 0.0 -> "else".
ASSERT_LIFT("0.5 ? 7 : 8",
"(0x3fe0000000000000.f\u2086\u2084 \u2261 0 \u21a0 0x8\u2086\u2084 0x7\u2086\u2084)");
ASSERT_LIFT("0.0 ? 7 : 8",
"(0x0.f\u2086\u2084 \u2261 0 \u21a0 0x8\u2086\u2084 0x7\u2086\u2084)");
// Float-conditioned ternary with float branches: both the
// condition and the branches lift in their float form.
ASSERT_LIFT("0.5 ? 1.5 : 2.5",
"(0x3fe0000000000000.f\u2086\u2084 \u2261 0 \u21a0 "
"0x4004000000000000.f\u2086\u2084 0x3ff8000000000000.f\u2086\u2084)");
#undef ASSERT_LIFT
// A parse error is reported, not lifted.
RzILOpPure *bad = rz_il_lift_num(NULL, "1 +", NULL, &err);
mu_assert_null(bad, "incomplete expression does not lift");
free(err);
err = NULL;
mu_end;
}
bool test_rz_num_math_ut64_legacy_compat() {
// rz_num_math_ut64 routes through the new parser and falls back
// to the legacy parser. Both code paths must agree on simple
// arithmetic.
mu_assert_eq(rz_num_math(num, "1 + 2"), 3, "1 + 2");
mu_assert_eq(rz_num_math(num, "0x10 * 2"), 0x20, "0x10 * 2");
mu_assert_eq(rz_num_math(num, "(1+2)*3"), 9, "(1+2)*3");
mu_assert_eq(rz_num_math(num, "10"), 10, "10");
mu_assert_eq(rz_num_math(num, ""), 0, "empty");
// rz_num_math is the inline alias for rz_num_math_ut64.
mu_assert_eq(rz_num_math_ut64(num, "5 + 5"), 10, "rz_num_math_ut64 5+5");
// Reserved-word fall-through: bare `mod`/`log`/`le`/`be` must
// silently resolve to 0 through this entry point, matching the
// legacy parser's behaviour for unknown identifiers. (The typed
// rz_num_math_value still returns an error - that is what
// test_rz_num_math_value_reserved_words covers.)
mu_assert_eq(rz_num_math(num, "mod"), 0, "bare 'mod' is silent 0");
mu_assert_eq(rz_num_math(num, "log"), 0, "bare 'log' is silent 0");
mu_assert_eq(rz_num_math(num, "le"), 0, "bare 'le' is silent 0");
mu_assert_eq(rz_num_math(num, "be"), 0, "bare 'be' is silent 0");
// num->nc.errors contract: callers such as the seek and write
// commands gate on it, so a successful evaluation must leave it
// clear even if a previous one set it, and a genuine error must
// set it. (Regression: the new-parser path used to leave a stale
// count in place, which made `s main` and friends silently abort.)
RzNum *ncnum = rz_num_new(NULL, NULL, NULL);
ncnum->nc.errors = 7;
mu_assert_eq(rz_num_math(ncnum, "1 + 2"), 3, "value still correct");
mu_assert_eq(ncnum->nc.errors, 0, "success clears stale nc.errors");
ncnum->nc.errors = 7;
mu_assert_eq(rz_num_math(ncnum, "0x1000"), 0x1000, "flagless literal");
mu_assert_eq(ncnum->nc.errors, 0, "success clears nc.errors (literal)");
rz_num_math(ncnum, "1 / 0");
mu_assert_true(ncnum->nc.errors != 0, "division by zero sets nc.errors");
mu_assert_eq(rz_num_math(ncnum, "42"), 42, "recover after error");
mu_assert_eq(ncnum->nc.errors, 0, "next success re-clears nc.errors");
rz_num_free(ncnum);
mu_end;
}
bool test_rz_num_math_ut64_segment() {
// Real-mode segment:offset addresses (x86 16-bit; the same notation
// appears for other segmented targets such as Infineon C166) linearise
// as (seg << 4) + offset. The grammar reserves ':' for typed reads, so
// these are recovered in rz_num_math_ut64()'s parse-error fallback. They
// are a ut64 address concept: the typed rz_num_math_value() entry point
// reports a syntax error for them rather than inventing a kind, so every
// case here goes through rz_num_math().
// Canonical 4-digit forms (segment and offset both 16-bit).
mu_assert_eq(rz_num_math(num, "0xf000:0xfff0"), 0xffff0, "0xf000:0xfff0");
mu_assert_eq(rz_num_math(num, "f000:ffaa"), 0xfffaa, "f000:ffaa (the udis86 seek target)");
mu_assert_eq(rz_num_math(num, "0000:000d"), 0xd, "0000:000d");
mu_assert_eq(rz_num_math(num, "0x07c0:0x0000"), 0x7c00, "0x07c0:0x0000 (boot sector)");
mu_assert_eq(rz_num_math(num, "1234:5678"), 0x179b8, "1234:5678");
// Wrap-around past 1 MiB is intentional - the old parser did not mask
// either, and real A20-style wrap is a property of the machine, not of
// the address notation.
mu_assert_eq(rz_num_math(num, "0xffff:0xffff"), 0x10ffef, "0xffff:0xffff");
// Non-4-digit segments and offsets. The old fixed-width sscanf matched
// exactly four hex digits at a fixed position, so these dropped through
// to the leading-number fallback and returned just the segment
// (e.g. 0x7c0). They now linearise correctly.
mu_assert_eq(rz_num_math(num, "0x7c0:0"), 0x7c00, "0x7c0:0 (3-digit segment)");
mu_assert_eq(rz_num_math(num, "0x7c0:0x0"), 0x7c00, "0x7c0:0x0");
mu_assert_eq(rz_num_math(num, "0:0x7c00"), 0x7c00, "0:0x7c00 (segment 0)");
mu_assert_eq(rz_num_math(num, "0x0:0x7c00"), 0x7c00, "0x0:0x7c00");
// Disambiguation from typed reads: ':' followed by a valid width
// (8/16/32/64) is a typed read, handled by the grammar, and is NOT a
// segment - with no backing IO it yields the address itself, not
// (addr << 4) + width.
mu_assert_eq(rz_num_math(num, "0x1000:8"), 0x1000, "0x1000:8 is a typed read, not a segment");
mu_assert_eq(rz_num_math(num, "0x1000:16"), 0x1000, "0x1000:16 is a typed read");
mu_assert_eq(rz_num_math(num, "0x1000:64"), 0x1000, "0x1000:64 is a typed read");
// A width the grammar does not recognise is not a typed read, so the
// fallback treats it as seg:off.
mu_assert_eq(rz_num_math(num, "0x1000:4"), 0x10004, "0x1000:4 is not a valid width, so seg:off");
mu_end;
}
#undef ASSERT_U64
#undef ASSERT_F64
#undef ASSERT_FAIL
bool test_rz_big_decimal() {
#define BD_STR(input, want) \
do { \
RzBigDecimal *_d = rz_big_decimal_new_from_str(input); \
mu_assert_notnull(_d, input " parses"); \
char *_s = rz_big_decimal_to_str(_d); \
mu_assert_streq_free(_s, want, input); \
rz_big_decimal_free(_d); \
} while (0)
// parse + format round-trips, including exponents and normalisation
BD_STR("3.14", "3.14");
BD_STR("0.1", "0.1");
BD_STR("-2.5", "-2.5");
BD_STR("1500", "1500");
BD_STR("1.5e3", "1500");
BD_STR("1e-3", "0.001");
BD_STR("0.00", "0");
BD_STR("42", "42");
BD_STR("-0.001", "-0.001");
#undef BD_STR
#define BD_OP(op, sa, sb, want) \
do { \
RzBigDecimal *_a = rz_big_decimal_new_from_str(sa); \
RzBigDecimal *_b = rz_big_decimal_new_from_str(sb); \
RzBigDecimal *_r = rz_big_decimal_##op(_a, _b); \
char *_s = rz_big_decimal_to_str(_r); \
mu_assert_streq_free(_s, want, sa " " #op " " sb); \
rz_big_decimal_free(_a); \
rz_big_decimal_free(_b); \
rz_big_decimal_free(_r); \
} while (0)
// Exact addition/subtraction/multiplication - the whole point: 0.1 +
// 0.2 is exactly 0.3, not the 0.30000000000000004 a double gives.
BD_OP(add, "0.1", "0.2", "0.3");
BD_OP(sub, "0.3", "0.1", "0.2");
BD_OP(mul, "1.5", "2.5", "3.75");
BD_OP(mul, "0.1", "0.1", "0.01");
BD_OP(add, "-2.5", "2.5", "0");
BD_OP(sub, "1", "0.0001", "0.9999");
#undef BD_OP
// Division is bounded to N significant digits and rounds half-up.
RzBigDecimal *one = rz_big_decimal_new_from_str("1");
RzBigDecimal *three = rz_big_decimal_new_from_str("3");
RzBigDecimal *four = rz_big_decimal_new_from_str("4");
RzBigDecimal *zero = rz_big_decimal_new_from_str("0");
RzBigDecimal *third = rz_big_decimal_div(one, three, 10);
char *ts = rz_big_decimal_to_str(third);
mu_assert_streq_free(ts, "0.3333333333", "1/3 to 10 digits");
rz_big_decimal_free(third);
RzBigDecimal *quarter = rz_big_decimal_div(one, four, 34);
char *qs = rz_big_decimal_to_str(quarter);
mu_assert_streq_free(qs, "0.25", "1/4 is exact");
rz_big_decimal_free(quarter);
RzBigDecimal *dz = rz_big_decimal_div(one, zero, 34);
mu_assert_null(dz, "division by zero is NULL");
rz_big_decimal_free(one);
rz_big_decimal_free(three);
rz_big_decimal_free(four);
rz_big_decimal_free(zero);
// Comparison aligns scales: 0.30 and 0.3 are equal.
RzBigDecimal *a = rz_big_decimal_new_from_str("0.1");
RzBigDecimal *b = rz_big_decimal_new_from_str("0.2");
RzBigDecimal *c = rz_big_decimal_new_from_str("0.30");
RzBigDecimal *d = rz_big_decimal_new_from_str("0.3");
mu_assert_true(rz_big_decimal_cmp(a, b) < 0, "0.1 < 0.2");
mu_assert_true(rz_big_decimal_cmp(c, d) == 0, "0.30 == 0.3");
rz_big_decimal_free(a);
rz_big_decimal_free(b);
rz_big_decimal_free(c);
rz_big_decimal_free(d);
// Projections to ut64 (truncate toward zero) and double.
RzBigDecimal *pi = rz_big_decimal_new_from_str("3.14159");
mu_assert_eq(rz_big_decimal_to_ut64(pi), 3, "3.14159 -> 3");
double pd = rz_big_decimal_to_double(pi);
mu_assert_true(pd > 3.1415 && pd < 3.1416, "3.14159 -> ~3.14159 double");
rz_big_decimal_free(pi);
mu_end;
}
bool test_rz_num_get_leading() {
// rz_num_get_leading evaluates only the numeric literal at the head of
// the string and reports where it stopped, so a trailing operand,
// separator or comment (which the strict evaluator would reject) is left
// untouched. This is what the disasm / rz-ax number extractors rely on.
const char *end = NULL;
// A hex address followed by disassembly text.
mu_assert_eq(rz_num_get_leading(num, "0x1234 mov r0", &end), 0x1234, "hex + text");
mu_assert_streq(end, " mov r0", "end after 0x1234");
// Comma-separated operands: only the first literal is consumed
// ("0x111,0x222" must not fold into a single value).
mu_assert_eq(rz_num_get_leading(num, "0x111,0x222", &end), 0x111, "comma-separated first");
mu_assert_streq(end, ",0x222", "end at comma");
// Decimal, negative-hex and other-base forms, each with trailing syntax.
mu_assert_eq(rz_num_get_leading(num, "255]", NULL), 255, "decimal + ]");
mu_assert_eq(rz_num_get_leading(num, "-0x20]", NULL), (ut64)-0x20, "negative hex operand");
mu_assert_eq(rz_num_get_leading(num, "0b1010 lsl 2", NULL), 10, "binary + text");
// A full 64-bit literal is well within the extractor's buffer, so a long
// token stays exact rather than truncating.
mu_assert_eq(rz_num_get_leading(num, "0xdeadbeefcafef00d,", NULL), 0xdeadbeefcafef00dULL, "full 64-bit hex");
// No literal at the head: value 0 and the end pointer stays put.
const char *none = "mov r0";
mu_assert_eq(rz_num_get_leading(num, none, &end), 0, "no literal -> 0");
mu_assert_ptreq(end, none, "end unmoved when no literal");
mu_end;
}
bool all_tests() {
mu_run_test(test_rz_num_units);
mu_run_test(test_rz_num_minmax_swap_i);
mu_run_test(test_rz_num_minmax_swap);
mu_run_test(test_rz_num_between);
mu_run_test(test_rz_num_str_len);
mu_run_test(test_rz_num_str_split);
mu_run_test(test_rz_num_str_split_list);
mu_run_test(test_rz_num_align_delta);
mu_run_test(test_rz_num_bitmask);
mu_run_test(test_rz_num_abs);
mu_run_test(test_rz_num_math_value_integer_arith);
mu_run_test(test_rz_num_math_value_bitwise);
mu_run_test(test_rz_num_math_value_rotate);
mu_run_test(test_rz_num_math_value_logical);
mu_run_test(test_rz_num_math_value_unary_signs);
mu_run_test(test_rz_num_math_value_increment);
mu_run_test(test_rz_num_math_value_floats);
mu_run_test(test_rz_num_math_value_bigdecimal);
mu_run_test(test_rz_num_math_value_units);
mu_run_test(test_rz_num_math_value_comparisons);
mu_run_test(test_rz_num_math_value_special_variables);
mu_run_test(test_rz_num_math_value_local_label);
mu_run_test(test_rz_num_math_value_callback_userptr);
mu_run_test(test_rz_num_math_value_string_bytes);
mu_run_test(test_rz_num_math_value_reserved_words);
mu_run_test(test_rz_num_math_value_errors);
mu_run_test(test_rz_num_math_value_assignment);
mu_run_test(test_rz_num_math_value_sequence);
mu_run_test(test_rz_num_math_value_conditional);
mu_run_test(test_rz_num_math_value_signed_arith);
mu_run_test(test_rz_num_math_value_len);
mu_run_test(test_rz_num_math_value_persistent_vars);
mu_run_test(test_rz_num_math_value_address_typed);
mu_run_test(test_rz_num_math_value_bignum);
mu_run_test(test_rz_num_math_value_bitvector);
mu_run_test(test_rz_num_math_value_bignum_arith);
mu_run_test(test_rz_num_math_value_complex_exprs);
mu_run_test(test_rz_num_math_value_timeout);
mu_run_test(test_rz_num_math_value_error_codes);
mu_run_test(test_rz_num_math_value_builtins);
mu_run_test(test_rz_num_math_value_math_functions);
mu_run_test(test_rz_num_math_value_torture);
mu_run_test(test_rz_num_math_value_unicode_names);
mu_run_test(test_rz_num_math_value_custom_funcs);
mu_run_test(test_rz_num_math_value_io_read);
mu_run_test(test_rz_num_math_value_typed_reads);
mu_run_test(test_rz_num_math_value_bitvector_extended_ops);
mu_run_test(test_rz_num_math_value_pretty_print);
mu_run_test(test_rz_num_math_value_bitvector_unicode);
mu_run_test(test_rz_num_math_ut64_legacy_compat);
mu_run_test(test_rz_num_math_ut64_segment);
mu_run_test(test_rz_num_il_lift);
mu_run_test(test_rz_big_decimal);
mu_run_test(test_rz_num_get_leading);
return tests_passed != tests_run;
}
int main(int argc, char **argv) {
num = rz_num_new(NULL, NULL, NULL);
num_with_cb = rz_num_new(test_var_cb, NULL, NULL);
return all_tests();
}