mirror of
https://github.com/rizinorg/rizin
synced 2026-08-22 20:26:16 -04:00
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.
2675 lines
94 KiB
C
2675 lines
94 KiB
C
// 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();
|
||
}
|