rz-util: skip the expression parser for bare integer literals

This commit is contained in:
Anton Kochkov
2026-07-26 00:00:50 +08:00
committed by NOT XVilka
parent 78b903c5f7
commit f5bb3a9a41
3 changed files with 86 additions and 35 deletions
+2
View File
@@ -449,6 +449,8 @@ typedef enum {
RZ_API RzNumBasePrefix rz_num_base_prefix(RZ_NONNULL const char *p, RZ_NULLABLE RZ_OUT ut32 *base,
RZ_NULLABLE RZ_OUT size_t *prefix_len);
RZ_API bool rz_num_is_int_literal(RZ_NONNULL const char *str, RZ_NULLABLE RZ_OUT ut64 *value,
RZ_NULLABLE RZ_OUT bool *overflow);
/**
* \brief Absolute value of a 64-bit number. Store result in `ut64`
+20 -35
View File
@@ -216,45 +216,30 @@ static bool literal_is_float(const char *t) {
// Parse an unsigned integer literal. Returns true on success. On
// ERANGE, sets *overflow=true so the caller can promote to bignum.
// Thin wrapper over rz_num_is_int_literal() adding the evaluator's own
// diagnostics. The three outcomes the caller distinguishes are: nothing after
// the base prefix, no valid digit at all, and a digit run followed by a digit
// invalid for the base (the '8' in 08 / 0187, which the grammar's permissive
// decimal digit class lets through to here). The last one leaves *err unset so
// the caller categorises it as a plain parse error.
static bool parse_int_literal(const char *t, ut64 *out, bool *overflow, char **err) {
const char *p = t;
if (rz_num_is_int_literal(t, out, overflow)) {
return true;
}
if (!err || *err || (overflow && *overflow)) {
return false;
}
ut32 base = 10;
size_t prefix_len = 0;
// A C style leading zero reports prefix_len 0: the zero is an octal
// digit, so strtoull() must still see it.
rz_num_base_prefix(p, &base, &prefix_len);
p += prefix_len;
if (!*p) {
if (err && !*err) {
*err = rz_str_newf("empty integer literal: %s", t);
}
return false;
rz_num_base_prefix(t, &base, &prefix_len);
const char *digits = t + prefix_len;
ut8 first = 0;
if (!*digits) {
*err = rz_str_newf("empty integer literal: %s", t);
} else if (rz_hex_to_byte(&first, (ut8)*digits) || first >= base) {
*err = rz_str_newf("not an integer: %s", t);
}
errno = 0;
char *endp = NULL;
unsigned long long v = strtoull(p, &endp, base);
if (errno == ERANGE) {
if (overflow) {
*overflow = true;
}
return false;
}
if (endp == p) {
if (err && !*err) {
*err = rz_str_newf("not an integer: %s", t);
}
return false;
}
if (*endp != '\0') {
// Trailing junk after the digit run: a digit not valid for the
// detected base. This catches C-style octal with an out-of-range
// digit (the '8' in 08 / 0187), which the permissive decimal
// digit class in the grammar lets through to here. Leave *err
// unset so the caller can categorise it as a parse error.
return false;
}
*out = (ut64)v;
return true;
return false;
}
static bool parse_float_literal(const char *t, double *out, char **err) {
+64
View File
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2007-2020 pancake <[email protected]>
// SPDX-License-Identifier: LGPL-3.0-only
#include <errno.h>
#include <math.h> /* for ceill */
#include <rz_util.h>
@@ -580,6 +581,59 @@ static bool num_is_flagname_token(const char *str) {
return has_colon;
}
/**
* \brief Check whether \p str is a bare integer literal, and parse it.
*
* A bare integer literal is an optional base prefix (see \ref rz_num_base_prefix)
* followed only by digits valid in that base, and nothing else. Signs,
* whitespace, operators, radix points, exponents, bit-vector widths, unit
* suffixes and the deprecated trailing base suffixes all make \p str not a
* bare literal.
*
* This is the cheap path around rz_num_math(): it needs no expression parser.
*
* \param str Text to examine.
* \param value Set to the parsed value when the function returns true.
* \param overflow Set to true when \p str is a valid integer literal too large
* for a ut64. The function still returns false; a caller that
* supports arbitrary precision can use this to promote it.
*
* \return true if \p str is a bare integer literal that fits in a ut64.
*/
RZ_API bool rz_num_is_int_literal(RZ_NONNULL const char *str, RZ_NULLABLE RZ_OUT ut64 *value,
RZ_NULLABLE RZ_OUT bool *overflow) {
rz_return_val_if_fail(str, false);
ut32 base = 10;
size_t prefix_len = 0;
rz_num_base_prefix(str, &base, &prefix_len);
const char *digits = str + prefix_len;
if (!*digits) {
return false;
}
for (const char *p = digits; *p; p++) {
ut8 v = 0;
if (rz_hex_to_byte(&v, (ut8)*p) || v >= base) {
return false;
}
}
errno = 0;
char *endp = NULL;
unsigned long long v = strtoull(digits, &endp, base);
if (errno == ERANGE) {
if (overflow) {
*overflow = true;
}
return false;
}
if (*endp) {
return false;
}
if (value) {
*value = (ut64)v;
}
return true;
}
/**
* \brief Compute a numerical expression yielding a ut64.
*
@@ -621,6 +675,16 @@ RZ_API ut64 rz_num_math_ut64(RzNum *num, const char *str) {
num->nc.calc_buf = str;
}
ut64 plain = 0;
// Bare integer literals are the common input here and need no
// expression parser; see rz_num_is_int_literal().
if (rz_num_is_int_literal(str, &plain, NULL)) {
if (num) {
num->value = plain;
}
return plain;
}
// Legacy `[addr]` dereference. The bracket read predates the typed
// `addr:width` syntax but is still widely used - both on its own
// (`s [ptr]`) and as a sub-term (`[0]/2`, `5+[0]`, `[0]+[4]`) - and