mirror of
https://github.com/rizinorg/rizin.git
synced 2026-10-12 02:21:57 +00:00
336 lines
9.8 KiB
C
336 lines
9.8 KiB
C
// SPDX-FileCopyrightText: 2026 RizinOrg <[email protected]>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
/**
|
|
* \file
|
|
* \brief Per-kind pretty-printing for RzNumValue.
|
|
*
|
|
* Renders an RzNumValue into a multi-line, human-readable block whose
|
|
* layout depends on the value's kind (ut64 multi-base table, float
|
|
* scientific + bit pattern, big number decimal + hex + width). Kept
|
|
* separate from the evaluator so consumers that only need to display
|
|
* a value do not pull in the tree-sitter machinery.
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <rz_types.h>
|
|
#include <rz_util/rz_num.h>
|
|
#include <rz_util/rz_str.h>
|
|
#include <rz_util/rz_strbuf.h>
|
|
|
|
/**
|
|
* \brief Human-readable name of an RzNumError category.
|
|
*
|
|
* Stable string suitable for diagnostics. Returns "unknown" for
|
|
* values outside the enum.
|
|
*/
|
|
RZ_API const char *rz_num_error_name(RzNumError err) {
|
|
switch (err) {
|
|
case RZ_NUM_ERR_OK: return "ok";
|
|
case RZ_NUM_ERR_PARSE: return "parse";
|
|
case RZ_NUM_ERR_EMPTY: return "empty";
|
|
case RZ_NUM_ERR_RESERVED_WORD: return "reserved word";
|
|
case RZ_NUM_ERR_UNDEFINED_VAR: return "undefined variable";
|
|
case RZ_NUM_ERR_DIV_ZERO: return "division by zero";
|
|
case RZ_NUM_ERR_OVERFLOW: return "overflow";
|
|
case RZ_NUM_ERR_TYPE_MISMATCH: return "type mismatch";
|
|
case RZ_NUM_ERR_TIMEOUT: return "timeout";
|
|
case RZ_NUM_ERR_UNCOMPUTABLE: return "uncomputable";
|
|
case RZ_NUM_ERR_NOT_IMPLEMENTED: return "not implemented";
|
|
case RZ_NUM_ERR_OUT_OF_MEMORY: return "out of memory";
|
|
case RZ_NUM_ERR_DEPTH: return "nesting too deep";
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
// Per-kind formatters
|
|
|
|
// Convert a ut64 to a 4-byte ASCII representation. Only emits a
|
|
// string line when every byte is printable; otherwise returns false
|
|
// and the caller skips that line.
|
|
static bool ut64_as_ascii(ut64 n, char *out, size_t outlen) {
|
|
if (outlen < 8) {
|
|
return false;
|
|
}
|
|
// Walk the bytes from low to high: that mirrors the legacy
|
|
// `?` output's "string" line which reads back ASCII tokens
|
|
// loaded from memory at that address.
|
|
ut8 bytes[8];
|
|
bool any = false;
|
|
for (int i = 0; i < 8; i++) {
|
|
bytes[i] = (ut8)((n >> (i * 8)) & 0xff);
|
|
if (bytes[i] != 0) {
|
|
any = true;
|
|
}
|
|
}
|
|
if (!any) {
|
|
return false;
|
|
}
|
|
int op = 0;
|
|
out[op++] = '"';
|
|
for (int i = 0; i < 8 && op + 2 < (int)outlen; i++) {
|
|
ut8 b = bytes[i];
|
|
if (b == 0) {
|
|
break;
|
|
}
|
|
if (b < 0x20 || b >= 0x7f) {
|
|
return false;
|
|
}
|
|
out[op++] = (char)b;
|
|
}
|
|
out[op++] = '"';
|
|
out[op] = 0;
|
|
return op > 2; // need at least one character between the quotes
|
|
}
|
|
|
|
static void print_ut64(const RzNumValue *v, RzStrBuf *sb) {
|
|
ut64 n = v->val.n;
|
|
char bits[65];
|
|
char trits[42];
|
|
char units[16];
|
|
|
|
// Signed / unsigned interpretation in two widths. The split at
|
|
// >> 32 mirrors the legacy `?` output: values that fit in 32
|
|
// bits get their 32-bit signed/unsigned forms shown, otherwise
|
|
// the 64-bit forms.
|
|
if (n >> 32) {
|
|
rz_strbuf_appendf(sb, "int64 %" PFMT64d "\n", (st64)n);
|
|
rz_strbuf_appendf(sb, "uint64 %" PFMT64u "\n", (ut64)n);
|
|
} else {
|
|
rz_strbuf_appendf(sb, "int32 %d\n", (st32)n);
|
|
rz_strbuf_appendf(sb, "uint32 %u\n", (ut32)n);
|
|
}
|
|
rz_strbuf_appendf(sb, "hex 0x%" PFMT64x "\n", n);
|
|
rz_strbuf_appendf(sb, "octal 0%" PFMT64o "\n", n);
|
|
|
|
rz_num_units(units, sizeof(units), n);
|
|
rz_strbuf_appendf(sb, "unit %s\n", units);
|
|
|
|
// Segment:offset (real-mode style 16:12-bit split).
|
|
ut32 seg = (ut32)(n >> 16) << 12;
|
|
ut32 off = (ut32)(n & 0x0fff);
|
|
rz_strbuf_appendf(sb, "segment %04x:%04x\n", seg, off);
|
|
|
|
char ascii[24];
|
|
if (ut64_as_ascii(n, ascii, sizeof(ascii))) {
|
|
rz_strbuf_appendf(sb, "string %s\n", ascii);
|
|
}
|
|
|
|
rz_str_bits64(bits, n);
|
|
rz_strbuf_appendf(sb, "binary 0b%s\n", bits);
|
|
rz_num_to_trits(trits, n);
|
|
rz_strbuf_appendf(sb, "trits 0t%s\n", trits);
|
|
}
|
|
|
|
static void print_float(const RzNumValue *v, RzStrBuf *sb) {
|
|
double d = v->val.d;
|
|
// Sign-of-NaN is implementation-defined; normalise so the
|
|
// printed form is identical across libcs.
|
|
if (isnan(d) && signbit(d)) {
|
|
d = -d;
|
|
}
|
|
rz_strbuf_appendf(sb, "float %g\n", d);
|
|
rz_strbuf_appendf(sb, "scifmt %.17g\n", d);
|
|
// IEEE-754 binary64 bit pattern (handy for back-conversion).
|
|
union {
|
|
double d;
|
|
ut64 u;
|
|
} u = { .d = d };
|
|
rz_strbuf_appendf(sb, "hex 0x%016" PFMT64x "\n", u.u);
|
|
}
|
|
|
|
static void print_big(const RzNumValue *v, RzStrBuf *sb) {
|
|
// Decimal output now goes through rz_big_to_decstr(), which
|
|
// gives the exact base-10 form. The hex form is still emitted
|
|
// alongside it: every other kind shows its hex pattern (as
|
|
// "hex" for ut64, "hex" bit pattern for float, "hex" digest
|
|
// for bitvector), and for symmetry the big print does too.
|
|
char *dec = rz_big_to_decstr(v->val.big);
|
|
if (dec) {
|
|
rz_strbuf_appendf(sb, "decimal %s\n", dec);
|
|
free(dec);
|
|
}
|
|
char *hex = rz_big_to_hexstr(v->val.big);
|
|
if (hex) {
|
|
// rz_big_to_hexstr already prefixes with "0x".
|
|
rz_strbuf_appendf(sb, "hex %s\n", hex);
|
|
free(hex);
|
|
}
|
|
// Width hint: number of hex nibbles (and therefore bits) the
|
|
// value actually occupies, useful for the reader who wants to
|
|
// know whether this is a 65-bit value (one bit over ut64) or
|
|
// a genuine multi-hundred-bit one.
|
|
if (hex || dec) {
|
|
// (already freed; recompute purely for the bit-width
|
|
// estimate, which is cheap)
|
|
char *h = rz_big_to_hexstr(v->val.big);
|
|
if (h) {
|
|
const char *p = h;
|
|
if (*p == '-')
|
|
p++;
|
|
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
|
|
p += 2;
|
|
}
|
|
int nibbles = (int)strlen(p);
|
|
rz_strbuf_appendf(sb, "width %d bits (approx)\n",
|
|
nibbles * 4);
|
|
free(h);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void print_bitvector(const RzNumValue *v, RzStrBuf *sb, bool utf8) {
|
|
RzBitVector *bv = v->val.bv;
|
|
if (!bv) {
|
|
rz_strbuf_appendf(sb, "bitvec (null)\n");
|
|
return;
|
|
}
|
|
ut32 len = rz_bv_len(bv);
|
|
char *hex = rz_bv_as_hex_string(bv, true);
|
|
char *bin = rz_bv_as_string(bv);
|
|
// In UTF-8 mode the width is folded into the value rows as a
|
|
// subscript (the notation RzIL uses for bit-vector constants, via
|
|
// the shared rz_bv_width_subscript formatter) and the binary form
|
|
// drops the 0b prefix in favour of the subscript. The value rows
|
|
// come first and the width row last in both modes.
|
|
char *sub = utf8 ? rz_bv_width_subscript(len) : NULL;
|
|
if (hex) {
|
|
rz_strbuf_appendf(sb, "hex %s%s\n", hex, sub ? sub : "");
|
|
}
|
|
if (bin) {
|
|
if (utf8) {
|
|
rz_strbuf_appendf(sb, "binary %s%s\n", bin, sub ? sub : "");
|
|
} else {
|
|
rz_strbuf_appendf(sb, "binary 0b%s\n", bin);
|
|
}
|
|
}
|
|
rz_strbuf_appendf(sb, "width %u bits\n", len);
|
|
free(sub);
|
|
free(hex);
|
|
free(bin);
|
|
}
|
|
|
|
// Public entry points
|
|
|
|
/**
|
|
* \brief Format an RzNumValue into a multi-representation textual block.
|
|
*
|
|
* Each kind produces a different layout:
|
|
* UT64 hex / dec (signed + unsigned) / octal / binary /
|
|
* IEC byte unit / segment:offset / character form
|
|
* FLOAT decimal scientific / shortest / hex bit pattern
|
|
* BIG decimal + hex (lossless)
|
|
* BITVECTOR bit-width / hex / binary
|
|
*
|
|
* On a value with v->err != RZ_NUM_ERR_OK, a single `error <category>`
|
|
* line is appended instead.
|
|
*
|
|
* \param v The value to format.
|
|
* \param sb Destination buffer.
|
|
*/
|
|
RZ_API void rz_num_value_print(RZ_NONNULL const RzNumValue *v, RZ_NONNULL RzStrBuf *sb) {
|
|
rz_num_value_print_ex(v, NULL, sb);
|
|
}
|
|
|
|
// Exact base-10 decimal: the full value plus its double projection.
|
|
static void print_bigdecimal(const RzNumValue *v, RzStrBuf *sb) {
|
|
char *s = v->val.bigdec ? rz_big_decimal_to_str(v->val.bigdec) : NULL;
|
|
rz_strbuf_appendf(sb, "decimal %s\n", s ? s : "0");
|
|
double d = v->val.bigdec ? rz_big_decimal_to_double(v->val.bigdec) : 0.0;
|
|
rz_strbuf_appendf(sb, "scifmt %.17g\n", d);
|
|
free(s);
|
|
}
|
|
|
|
/**
|
|
* \brief Like rz_num_value_print() but with explicit formatting options.
|
|
*
|
|
* With opts->utf8 set, a bit-vector additionally renders a compact
|
|
* Unicode form (hex value with the bit-width as a subscript, e.g.
|
|
* `0x2c\u2088`), mirroring how RzIL renders bit-vectors in its Unicode
|
|
* export. Other kinds are unaffected for now.
|
|
*
|
|
* \param v The value to format. Must be a valid RzNumValue.
|
|
* \param opts Formatting options. NULL selects the ASCII defaults.
|
|
* \param sb Destination buffer.
|
|
*/
|
|
RZ_API void rz_num_value_print_ex(RZ_NONNULL const RzNumValue *v,
|
|
RZ_NULLABLE const RzNumPrintOptions *opts, RZ_NONNULL RzStrBuf *sb) {
|
|
rz_return_if_fail(v && sb);
|
|
bool utf8 = opts && opts->utf8;
|
|
if (v->err != RZ_NUM_ERR_OK) {
|
|
rz_strbuf_appendf(sb, "error %s\n", rz_num_error_name(v->err));
|
|
return;
|
|
}
|
|
switch (v->kind) {
|
|
case RZ_NUM_KIND_UT64:
|
|
print_ut64(v, sb);
|
|
break;
|
|
case RZ_NUM_KIND_FLOAT:
|
|
print_float(v, sb);
|
|
break;
|
|
case RZ_NUM_KIND_BIGDECIMAL:
|
|
print_bigdecimal(v, sb);
|
|
break;
|
|
case RZ_NUM_KIND_BIG:
|
|
print_big(v, sb);
|
|
break;
|
|
case RZ_NUM_KIND_BITVECTOR:
|
|
print_bitvector(v, sb, utf8);
|
|
break;
|
|
case RZ_NUM_KIND_NONE:
|
|
rz_strbuf_appendf(sb, "kind none\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* \brief Compact one-line stringification of an RzNumValue.
|
|
*
|
|
* Suitable for inline display where the multi-line print would be
|
|
* too noisy.
|
|
* On error returns NULL.
|
|
*/
|
|
RZ_API RZ_OWN char *rz_num_value_tostring(RZ_NONNULL const RzNumValue *v) {
|
|
rz_return_val_if_fail(v, NULL);
|
|
if (v->err != RZ_NUM_ERR_OK) {
|
|
return rz_str_newf("error: %s", rz_num_error_name(v->err));
|
|
}
|
|
switch (v->kind) {
|
|
case RZ_NUM_KIND_UT64:
|
|
return rz_str_newf("0x%" PFMT64x, v->val.n);
|
|
case RZ_NUM_KIND_FLOAT: {
|
|
double d = v->val.d;
|
|
if (isnan(d) && signbit(d)) {
|
|
d = -d;
|
|
}
|
|
return rz_str_newf("%g", d);
|
|
}
|
|
case RZ_NUM_KIND_BIGDECIMAL: {
|
|
char *s = v->val.bigdec ? rz_big_decimal_to_str(v->val.bigdec) : NULL;
|
|
return s ? s : rz_str_dup("0");
|
|
}
|
|
case RZ_NUM_KIND_BIG: {
|
|
char *hex = rz_big_to_hexstr(v->val.big);
|
|
// rz_big_to_hexstr already includes the "0x" prefix.
|
|
if (!hex) {
|
|
return rz_str_dup("0x0");
|
|
}
|
|
return hex;
|
|
}
|
|
case RZ_NUM_KIND_BITVECTOR: {
|
|
char *hex = rz_bv_as_hex_string(v->val.bv, true);
|
|
if (!hex) {
|
|
return rz_str_dup("0x0");
|
|
}
|
|
return hex;
|
|
}
|
|
case RZ_NUM_KIND_NONE:
|
|
return rz_str_dup("(none)");
|
|
}
|
|
return rz_str_dup("(none)");
|
|
}
|