From d3860590ee4b37305030bfe01544da3e398ca1d9 Mon Sep 17 00:00:00 2001 From: NOT XVilka Date: Fri, 29 May 2026 02:02:58 +0800 Subject: [PATCH] librz/util: shared Unicode subscript formatting for bit-vectors and floats (#6418) Consolidate the Unicode subscript notation used when rendering bit-vector and float values (the subscript width on a bit-vector constant, e.g. 0x2c followed by a subscript 8, and the format width on a float, e.g. .f followed by a subscript 32) into one place, so the RzIL Unicode exporter, the RzNum value printer, and the RzNum->RzIL lift cannot drift apart. RzUtil gains the single source of truth: * rz_str_append_subscript() / rz_str_append_superscript() / rz_str_subscript() render a number as Unicode subscript or superscript digits; * rz_bv_width_subscript() / rz_bv_as_unicode_string() build a bit-vector's width subscript on top of the str helper; * rz_float_format_subscript() renders a float format's width subscript (16/32/64/80/128, with the decimal-format marker), reusing the same digit renderer. The RzIL Unicode exporter (il_export_string_unicode.c) is switched fully onto these: every append_subscript() call site (bit-vector constant width, cast length, memory indices) now goes through rz_str_append_subscript(), and the hardcoded per-format subscript macro is replaced by rz_float_format_subscript(). The exporter's private subscript-digit table and ut32 glyph helper are removed, so there is no longer a second, parallel implementation to keep in sync. Signed-off-by: Anton Kochkov Co-authored-by: Anton Kochkov --- librz/il/il_export_string_unicode.c | 87 +++++++++++++++------------- librz/include/rz_util/rz_bitvector.h | 2 + librz/include/rz_util/rz_float.h | 1 + librz/include/rz_util/rz_str.h | 4 ++ librz/util/bitvector.c | 41 +++++++++++++ librz/util/float/float.c | 53 +++++++++++++++++ librz/util/str.c | 79 +++++++++++++++++++++++++ 7 files changed, 228 insertions(+), 39 deletions(-) diff --git a/librz/il/il_export_string_unicode.c b/librz/il/il_export_string_unicode.c index ab694d45f9..17157413fb 100644 --- a/librz/il/il_export_string_unicode.c +++ b/librz/il/il_export_string_unicode.c @@ -6,8 +6,6 @@ static bool il_op_pure_string_resolve(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf *sb); static bool il_op_effect_string_resolve(RzILStringifyCtx *ctx, const RzILOpEffect *op, RzStrBuf *sb); -static const char *const subscript_digits[10] = { "₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉" }; - #define UCD_ITE "↠" #define UCD_LET "=" #define UCD_BOOL_FALSE "⊥" @@ -180,34 +178,25 @@ static const char *const subscript_digits[10] = { "₀", "₁", "₂", "₃", " return rz_strbuf_append(sb, ")"); \ } while (0); -#define sym_with_float_format(x, y) \ - (x) == RZ_FLOAT_IEEE754_BIN_32 ? y "₃₂" : (x) == RZ_FLOAT_IEEE754_BIN_64 ? y "₆₄" \ - : (x) == RZ_FLOAT_IEEE754_BIN_80 ? y "₈₀" \ - : (x) == RZ_FLOAT_IEEE754_BIN_128 ? y "₁₂₈" \ - : (x) == RZ_FLOAT_IEEE754_BIN_16 ? y "₁₆" \ - : (x) == RZ_FLOAT_IEEE754_DEC_64 ? y "ᵈ₆₄" \ - : (x) == RZ_FLOAT_IEEE754_DEC_128 ? y "ᵈ₁₂₈" \ - : "" - -static bool append_ut32_glyph(RzStrBuf *sb, ut32 n, const char *const digits[10]) { - char buffer[32] = { 0 }; - if (rz_strf(buffer, "%u", n) == NULL) { - return false; +// Combine a prefix with the float format's Unicode width subscript, +// via the shared RzUtil renderer (rz_float_format_subscript) so the +// notation matches the rest of the codebase. Returns a caller-owned +// string; the caller must free it. +static char *sym_with_float_format(RzFloatFormat format, const char *prefix) { + char *sub = rz_float_format_subscript(format); + if (!sub) { + return NULL; } - - /* Each unicode superscript/subscript character is at most 3 bytes */ - const size_t len = strlen(buffer); - for (size_t i = 0; i < len; ++i) { - int digit = buffer[i] - '0'; - if (!rz_strbuf_append(sb, digits[digit])) { - return false; - } - } - return true; + char *out = rz_str_newf("%s%s", prefix, sub); + free(sub); + return out; } +// Append a number as Unicode subscript digits, via the shared RzUtil +// renderer so this exporter and the bit-vector / float value +// formatters stay byte-for-byte identical. static bool append_subscript(RzStrBuf *sb, ut32 n) { - return append_ut32_glyph(sb, n, subscript_digits); + return rz_str_append_num_subscript(sb, n); } static bool il_opdmp_var(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf *sb) { @@ -270,14 +259,15 @@ static bool il_opdmp_bitv(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf const RzILOpArgsBv *opx = &op->op.bitv; char *num = rz_bv_as_hex_string(opx->value, false); return_false_if_fail(num); - goto_if_fail(rz_strbuf_appendf(sb, "%s", num), fini); - goto_if_fail(append_subscript(sb, opx->value->len), fini); + // The hex value plus the width as a Unicode subscript, via the + // shared RzUtil formatter (also used by the RzNum value printer) + // so the two renderings of a bit-vector constant stay identical. + char *uni = rz_bv_as_unicode_string(opx->value, num); free(num); - return true; - -fini: - free(num); - return false; + return_false_if_fail(uni); + bool ok = rz_strbuf_append(sb, uni); + free(uni); + return ok; } static bool il_opdmp_msb(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf *sb) { @@ -380,8 +370,13 @@ static bool il_opdmp_float(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf switch (opx->bv->code) { default: return il_op_pure_string_resolve(ctx, opx->bv, sb); - case RZ_IL_OP_BITV: - return il_opdmp_bitv_float(ctx, opx->bv, sb, sym_with_float_format(opx->r, ".f")); + case RZ_IL_OP_BITV: { + char *sym = sym_with_float_format(opx->r, ".f"); + return_false_if_fail(sym); + bool ok = il_opdmp_bitv_float(ctx, opx->bv, sb, sym); + free(sym); + return ok; + } } } @@ -433,14 +428,28 @@ static bool il_opdmp_fcast_sint(RzILStringifyCtx *ctx, const RzILOpPure *op, RzS static bool il_opdmp_fcast_float(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf *sb) { const RzILOpArgsFCastfloat *opx = &op->op.fcast_float; - const char *sym = sym_with_float_format(opx->format, UCD_FCAST_FLOAT); - il_op_param_1_with_mode_format(sym, opx, bv, pure, mode, format); + char *sym = sym_with_float_format(opx->format, UCD_FCAST_FLOAT); + return_false_if_fail(sym); + bool ok = rz_strbuf_append(sb, "(") && + il_op_pure_string_resolve(ctx, opx->bv, sb) && + rz_strbuf_appendf(sb, " %s ", sym) && + rz_strbuf_append(sb, rz_il_float_stringify_rmode(opx->mode)) && + rz_strbuf_append(sb, ")"); + free(sym); + return ok; } static bool il_opdmp_fcast_sfloat(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf *sb) { const RzILOpArgsFCastsfloat *opx = &op->op.fcast_sfloat; - const char *sym = sym_with_float_format(opx->format, UCD_FCAST_SFLOAT); - il_op_param_1_with_mode_format(sym, opx, bv, pure, mode, format); + char *sym = sym_with_float_format(opx->format, UCD_FCAST_SFLOAT); + return_false_if_fail(sym); + bool ok = rz_strbuf_append(sb, "(") && + il_op_pure_string_resolve(ctx, opx->bv, sb) && + rz_strbuf_appendf(sb, " %s ", sym) && + rz_strbuf_append(sb, rz_il_float_stringify_rmode(opx->mode)) && + rz_strbuf_append(sb, ")"); + free(sym); + return ok; } static bool il_opdmp_fconvert(RzILStringifyCtx *ctx, const RzILOpPure *op, RzStrBuf *sb) { diff --git a/librz/include/rz_util/rz_bitvector.h b/librz/include/rz_util/rz_bitvector.h index 3a6890cec1..a6ddd365e5 100644 --- a/librz/include/rz_util/rz_bitvector.h +++ b/librz/include/rz_util/rz_bitvector.h @@ -130,6 +130,8 @@ RZ_API void rz_bv_set_to_bytes_le(RZ_NONNULL const RzBitVector *bv, RZ_OUT RZ_NO RZ_API void rz_bv_set_to_bytes_be(RZ_NONNULL const RzBitVector *bv, RZ_OUT RZ_NONNULL ut8 *buf); RZ_API RZ_OWN char *rz_bv_as_string(RZ_NONNULL const RzBitVector *bv); RZ_API RZ_OWN char *rz_bv_as_hex_string(RZ_NONNULL const RzBitVector *bv, bool pad); +RZ_API RZ_OWN char *rz_bv_width_subscript(ut32 width); +RZ_API RZ_OWN char *rz_bv_as_unicode_string(RZ_NONNULL const RzBitVector *bv, RZ_NONNULL const char *value); RZ_API ut32 rz_bv_len(RZ_NONNULL const RzBitVector *bv); RZ_API ut32 rz_bv_len_bytes(RZ_NONNULL const RzBitVector *bv); diff --git a/librz/include/rz_util/rz_float.h b/librz/include/rz_util/rz_float.h index 146bc2f4ad..255eed5ee0 100644 --- a/librz/include/rz_util/rz_float.h +++ b/librz/include/rz_util/rz_float.h @@ -126,6 +126,7 @@ static inline bool rz_float_is_neg_zero_long_double(long double zero) { #define IS_NEG_ZEROLD(z) rz_float_is_neg_zero_long_double(z) RZ_API ut32 rz_float_get_format_info(RzFloatFormat format, RzFloatInfo which_info); +RZ_API RZ_OWN char *rz_float_format_subscript(RzFloatFormat format); RZ_API void rz_float_fini(RZ_NONNULL RzFloat *f); RZ_API void rz_float_free(RZ_NULLABLE RzFloat *f); RZ_API bool rz_float_init(RZ_NONNULL RzFloat *f, RzFloatFormat format); diff --git a/librz/include/rz_util/rz_str.h b/librz/include/rz_util/rz_str.h index 3ad2a86941..2f1f643791 100644 --- a/librz/include/rz_util/rz_str.h +++ b/librz/include/rz_util/rz_str.h @@ -7,6 +7,7 @@ #include "rz_list.h" #include #include "rz_types.h" +#include "rz_strbuf.h" #ifdef __cplusplus extern "C" { @@ -255,6 +256,9 @@ RZ_API RZ_OWN char *rz_str_append(RZ_OWN RZ_NULLABLE char *ptr, const char *stri RZ_API char *rz_str_append_owned(char *ptr, char *string); RZ_API RZ_OWN char *rz_str_appendf(RZ_OWN RZ_NULLABLE char *ptr, const char *fmt, ...) RZ_PRINTF_CHECK(2, 3); RZ_API char *rz_str_appendch(char *x, char y); +RZ_API bool rz_str_append_num_subscript(RZ_NONNULL RzStrBuf *sb, ut32 n); +RZ_API bool rz_str_append_num_superscript(RZ_NONNULL RzStrBuf *sb, ut32 n); +RZ_API RZ_OWN char *rz_str_num_subscript(ut32 n); RZ_API void rz_str_case(char *str, bool up); RZ_API void rz_str_trim_path(char *s); RZ_API ut8 rz_str_contains_macro(const char *input_value); diff --git a/librz/util/bitvector.c b/librz/util/bitvector.c index ff858678b6..b50bd2a69e 100644 --- a/librz/util/bitvector.c +++ b/librz/util/bitvector.c @@ -188,6 +188,47 @@ RZ_API RZ_OWN char *rz_bv_as_hex_string(RZ_NONNULL const RzBitVector *bv, bool p return str; } +/** + * Render a width as a run of Unicode subscript digits. + * + * This is the bit-width annotation used when rendering a bit-vector + * in Unicode form (e.g. the subscript 8 in 0x2c with a trailing 8). + * Shared so the RzIL Unicode export and the RzNum value printer + * cannot drift apart. + * + * \param width The width to render. + * \return A freshly-allocated, caller-owned string, or NULL on + * allocation failure. + */ +RZ_API RZ_OWN char *rz_bv_width_subscript(ut32 width) { + return rz_str_num_subscript(width); +} + +/** + * Render a pre-formatted bit-vector \p value followed by the + * bit-vector's width as a Unicode subscript. + * + * \p value is the already-stringified value (for instance the output + * of rz_bv_as_hex_string() or rz_bv_as_string()); only the width + * subscript is appended here, so the caller controls the value's + * base and padding. The result is a freshly-allocated, caller-owned + * string, e.g. "0x2c" followed by a subscript 8. + * + * \param bv The bit-vector whose width is annotated. Must be non-NULL. + * \param value The pre-formatted value string. Must be non-NULL. + * \return The combined string, or NULL on allocation failure. + */ +RZ_API RZ_OWN char *rz_bv_as_unicode_string(RZ_NONNULL const RzBitVector *bv, RZ_NONNULL const char *value) { + rz_return_val_if_fail(bv && value, NULL); + char *sub = rz_bv_width_subscript(rz_bv_len(bv)); + if (!sub) { + return NULL; + } + char *out = rz_str_newf("%s%s", value, sub); + free(sub); + return out; +} + /** * Clone a bitvector * \param bv RzBitVector, pointer to the source bitvector diff --git a/librz/util/float/float.c b/librz/util/float/float.c index d666f2b2be..02bc7e62be 100644 --- a/librz/util/float/float.c +++ b/librz/util/float/float.c @@ -1932,3 +1932,56 @@ RZ_API RZ_OWN RzBitVector *rz_float_round_significant(bool sign, RzBitVector *si RZ_API RZ_OWN RzFloat *rz_float_round_bv_and_pack(bool sign, st32 exp, RzBitVector *sig, RzFloatFormat format, RzFloatRMode mode) { return round_float_bv_new(sign, exp, sig, format, format, mode); } + +/** + * \brief Render a float format's width as a Unicode subscript string. + * + * Mirrors the bit-vector width subscript (rz_bv_width_subscript): the + * total bit width of \p format is rendered as Unicode subscript + * digits, with a leading "d" subscript marker for the decimal + * formats. For example IEEE-754 binary32 yields the subscript "32" + * and decimal64 yields "d64". This is the single source of truth for + * the float-format subscript shared by value formatting and the RzIL + * Unicode exporter. + * + * \param format The float format to annotate. + * \return A freshly-allocated, caller-owned string, or NULL on + * allocation failure or an unknown format. + */ +RZ_API RZ_OWN char *rz_float_format_subscript(RzFloatFormat format) { + // The decimal formats are not fully implemented in RzFloat + // (rz_float_get_format_info returns 0 for them), so their widths + // are spelled out here; they render with a leading "d" marker. + ut32 total; + bool is_decimal = false; + switch (format) { + case RZ_FLOAT_IEEE754_DEC_64: + total = 64; + is_decimal = true; + break; + case RZ_FLOAT_IEEE754_DEC_128: + total = 128; + is_decimal = true; + break; + default: + total = rz_float_get_format_info(format, RZ_FLOAT_INFO_TOTAL_LEN); + break; + } + if (!total) { + return NULL; + } + RzStrBuf sb; + rz_strbuf_init(&sb); + // Decimal formats carry a "d" marker (U+1D48 modifier letter + // small d) before the width to distinguish them from the binary + // formats, matching the RzIL Unicode exporter's notation. + if (is_decimal && !rz_strbuf_append(&sb, "\u1d48")) { + rz_strbuf_fini(&sb); + return NULL; + } + if (!rz_str_append_num_subscript(&sb, total)) { + rz_strbuf_fini(&sb); + return NULL; + } + return rz_strbuf_drain_nofree(&sb); +} diff --git a/librz/util/str.c b/librz/util/str.c index b744702060..bbc90e0721 100644 --- a/librz/util/str.c +++ b/librz/util/str.c @@ -4610,3 +4610,82 @@ RZ_API bool rz_string_enc_requires_scanning(RzStrEnc enc) { rz_warn_if_reached(); return true; } + +// Unicode subscript digits U+2080..U+2089 and superscript digits +// (U+2070, U+00B9, U+00B2, U+00B3, U+2074..U+2079), indexed by the +// digit value 0..9. These are the single source of truth for the +// subscript/superscript number rendering shared by the bit-vector and +// float formatters and the RzIL Unicode exporter. +static const char *const rz_str_subscript_digits[10] = { + "\u2080", "\u2081", "\u2082", "\u2083", "\u2084", + "\u2085", "\u2086", "\u2087", "\u2088", "\u2089" +}; + +static const char *const rz_str_superscript_digits[10] = { + "\u2070", "\u00b9", "\u00b2", "\u00b3", "\u2074", + "\u2075", "\u2076", "\u2077", "\u2078", "\u2079" +}; + +static bool str_append_glyph_digits(RzStrBuf *sb, ut32 n, + const char *const digits[10]) { + char buf[16]; + rz_strf(buf, "%u", n); + for (const char *d = buf; *d; d++) { + if (!rz_strbuf_append(sb, digits[*d - '0'])) { + return false; + } + } + return true; +} + +/** + * \brief Append \p n rendered as Unicode subscript digits to \p sb. + * + * For example 32 becomes the subscript "32" (U+2083 U+2082). This is + * the shared renderer for the bit-width/format-width subscripts used + * by bit-vector and float value formatting and by the RzIL Unicode + * exporter, so all of those stay byte-for-byte identical. + * + * \param sb Destination string buffer. + * \param n The number to render. + * \return true on success, false on allocation failure. + */ +RZ_API bool rz_str_append_num_subscript(RZ_NONNULL RzStrBuf *sb, ut32 n) { + rz_return_val_if_fail(sb, false); + return str_append_glyph_digits(sb, n, rz_str_subscript_digits); +} + +/** + * \brief Append \p n rendered as Unicode superscript digits to \p sb. + * + * The superscript counterpart of rz_str_append_num_subscript(); shared so + * the RzIL Unicode exporter and any other consumer render run-length + * style annotations identically. + * + * \param sb Destination string buffer. + * \param n The number to render. + * \return true on success, false on allocation failure. + */ +RZ_API bool rz_str_append_num_superscript(RZ_NONNULL RzStrBuf *sb, ut32 n) { + rz_return_val_if_fail(sb, false); + return str_append_glyph_digits(sb, n, rz_str_superscript_digits); +} + +/** + * \brief Render \p n as a freshly-allocated Unicode subscript string. + * + * Convenience wrapper around rz_str_append_num_subscript() for callers + * that want an owned string rather than appending to a buffer. + * + * \param n The number to render. + * \return A caller-owned string, or NULL on allocation failure. + */ +RZ_API RZ_OWN char *rz_str_num_subscript(ut32 n) { + RzStrBuf sb; + rz_strbuf_init(&sb); + if (!rz_str_append_num_subscript(&sb, n)) { + rz_strbuf_fini(&sb); + return NULL; + } + return rz_strbuf_drain_nofree(&sb); +}