From db40fae469736482627d572b52ec96be3e000444 Mon Sep 17 00:00:00 2001 From: IndAlok <152197161+IndAlok@users.noreply.github.com> Date: Tue, 7 Apr 2026 10:19:36 +0530 Subject: [PATCH 1/2] table-only --- librz/util/table.c | 315 +++++++++++++++++++++++++++++++++-------- test/db/cmd/tables | 27 ++++ test/unit/test_table.c | 82 +++++++++++ 3 files changed, 364 insertions(+), 60 deletions(-) diff --git a/librz/util/table.c b/librz/util/table.c index 0a6bdbddbea..e2f54bf2995 100644 --- a/librz/util/table.c +++ b/librz/util/table.c @@ -125,6 +125,174 @@ static bool table_index_of_column(RzTable *t, const char *name, size_t *index) { return false; } +static bool table_query_index_of_column(RzTable *t, const char *name, size_t *index) { + static const char *const addr_aliases[] = { + "vaddr", + "address", + "paddr", + NULL + }; + static const char *const length_aliases[] = { + "len", + NULL + }; + if (table_index_of_column(t, name, index)) { + return true; + } + // Keep stable query names working across tables with slightly different headers. + if (RZ_STR_EQ(name, "addr")) { + for (size_t i = 0; addr_aliases[i]; i++) { + if (table_index_of_column(t, addr_aliases[i], index)) { + return true; + } + } + } + if (RZ_STR_EQ(name, "length")) { + for (size_t i = 0; length_aliases[i]; i++) { + if (table_index_of_column(t, length_aliases[i], index)) { + return true; + } + } + } + return false; +} + +static bool table_query_resolve_column(RzTable *t, const char *name, size_t *index) { + if (table_query_index_of_column(t, name, index)) { + return true; + } + if (name[0] == '[') { + int signed_col = atoi(name + 1); + // Bracketed indexes are query-time column selectors, not unchecked fallbacks. + if (signed_col >= 0 && (size_t)signed_col < rz_vector_len(t->cols)) { + *index = (size_t)signed_col; + return true; + } + } + if ((RZ_STR_ISEMPTY(name) || RZ_STR_EQ(name, "*")) && rz_vector_len(t->cols) > 0) { + *index = 0; + return true; + } + return false; +} + +static bool table_query_resolve_column_or_log(RzTable *t, const char *name, size_t *index) { + if (table_query_resolve_column(t, name, index)) { + return true; + } + RZ_LOG_ERROR("table: Invalid column (%s)\n", name); + return false; +} + +static void table_filter(RZ_NONNULL RzTable *t, size_t nth, int op, RZ_NONNULL const char *un); + +static bool table_query_select_column(RzTable *t, const char *column_name) { + size_t col = 0; + if (!table_query_resolve_column_or_log(t, column_name, &col)) { + return false; + } + RzTableColumn *column = rz_vector_index_ptr(t->cols, col); + if (!column) { + return false; + } + RzList *list = rz_list_newf(free); + char *name = rz_str_dup(column->name); + if (!list || !name) { + rz_list_free(list); + free(name); + return false; + } + rz_list_append(list, name); + // Select using the canonical table header, so aliases and [n] behave like real columns. + rz_table_columns_select(t, list); + rz_list_free(list); + return true; +} + +static bool table_query_select_columns(RzTable *t, const char *column_name, const char *operand) { + char *op = rz_str_dup(operand ? operand : ""); + if (!op) { + return false; + } + RzList *list = rz_str_split_list(op, "/", 0); + if (!list) { + free(op); + return false; + } + size_t col = 0; + if (table_query_resolve_column(t, column_name, &col)) { + RzTableColumn *column = rz_vector_index_ptr(t->cols, col); + if (!column) { + rz_list_free(list); + free(op); + return false; + } + char *name = rz_str_dup(column->name); + if (!name) { + rz_list_free(list); + free(op); + return false; + } + // Normalize the first selected column before passing the list to the generic selector. + rz_list_prepend(list, name); + } else if (RZ_STR_ISNOTEMPTY(column_name)) { + char *name = rz_str_dup(column_name); + if (!name) { + rz_list_free(list); + free(op); + return false; + } + rz_list_prepend(list, name); + } + rz_table_columns_select(t, list); + rz_list_free(list); + free(op); + return true; +} + +static bool table_query_filter(RzTable *t, const char *column_name, const char *operand, int op) { + if (!operand) { + return true; + } + size_t col = 0; + if (!table_query_resolve_column_or_log(t, column_name, &col)) { + return false; + } + table_filter(t, col, op, operand); + return true; +} + +static bool table_query_sort(RzTable *t, const char *column_name, const char *operand, bool by_len) { + size_t col = 0; + if (!table_query_resolve_column_or_log(t, column_name, &col)) { + return false; + } + if (by_len) { + rz_table_sortlen(t, col, operand && RZ_STR_EQ(operand, "rev")); + } else { + rz_table_sort(t, col, operand && RZ_STR_EQ(operand, "rev")); + } + return true; +} + +static bool table_query_group(RzTable *t, const char *column_name) { + size_t col = 0; + if (!table_query_resolve_column_or_log(t, column_name, &col)) { + return false; + } + rz_table_group(t, col, NULL); + return true; +} + +static bool table_query_sum(RzTable *t, const char *column_name, const char *operand) { + if (!table_query_select_columns(t, column_name, operand)) { + return false; + } + // sum works on the reordered first col after selection, like the orig path expected + table_filter(t, 0, '+', operand ? operand : ""); + return true; +} + static bool table_add_column(RzTable *t, const RzTableColumnType type, RzTableColumnTypeComparator cmp, const char *name) { if (table_index_of_column(t, name, NULL)) { return false; @@ -807,11 +975,35 @@ static void table_filter(RZ_NONNULL RzTable *t, size_t nth, int op, RZ_NONNULL c return; } + RzTableColumn *col = rz_vector_index_ptr(t->cols, nth); + if (!col) { + return; + } + RzTableRow *row; - ut64 uv = rz_num_math(NULL, un); + ut64 uv = 0; ut64 sum = 0; size_t page = 0, page_items = 0; size_t lrow = 0; + // only num operators should parse operands as math expr + switch (op) { + case 'h': + case 't': + case '>': + case ')': + case '<': + case '(': + uv = rz_num_math(NULL, un); + break; + case '=': + case '!': + if (col->type != RZ_TABLE_COLUMN_TYPE_STRING) { + uv = rz_num_math(NULL, un); + } + break; + default: + break; + } if (op == 't') { size_t ll = rz_vector_len(t->rows); if (ll > uv) { @@ -831,13 +1023,11 @@ static void table_filter(RZ_NONNULL RzTable *t, size_t nth, int op, RZ_NONNULL c for (i = 0; i < rz_vector_len(t->rows); i++) { row = rz_vector_index_ptr(t->rows, i); const char *nn = rz_pvector_at(row->items, nth); - ut64 nv = rz_num_math(NULL, nn); bool match = true; - RzTableRow *del_row = RZ_NEW(RzTableRow); - if (!del_row) { - RZ_LOG_ERROR("Failed to allocate memory.\n"); - return; - } + bool use_nv = op == '+' || op == '>' || op == ')' || op == '<' || op == '(' || + (col->type != RZ_TABLE_COLUMN_TYPE_STRING && (op == '=' || op == '!')); + // avoid feeding arbitrary str cells into rz_num_math() unless the opr is num + ut64 nv = use_nv ? rz_num_math(NULL, nn) : 0; switch (op) { case 'p': nrow++; @@ -880,15 +1070,16 @@ static void table_filter(RZ_NONNULL RzTable *t, size_t nth, int op, RZ_NONNULL c match = (nv <= uv); break; case '=': - if (nv == 0 && nn != NULL) { - match = !strcmp(nn, un); + // str cols should keep str eq even for num looking txt + if (col->type == RZ_TABLE_COLUMN_TYPE_STRING) { + match = nn && !strcmp(nn, un); } else { match = (nv == uv); } break; case '!': - if (nv == 0) { - match = strcmp(nn, un); + if (col->type == RZ_TABLE_COLUMN_TYPE_STRING) { + match = !nn || strcmp(nn, un); } else { match = (nv != uv); } @@ -917,10 +1108,11 @@ static void table_filter(RZ_NONNULL RzTable *t, size_t nth, int op, RZ_NONNULL c break; } if (!match) { - rz_vector_remove_at(t->rows, i--, del_row); - table_row_fini(del_row); + RzTableRow del_row = { 0 }; + // remove rows w/o heap allocating a temp. wrapper for each filtered row + rz_vector_remove_at(t->rows, i--, &del_row); + table_row_fini(&del_row); } - RZ_FREE(del_row); } if (op == '+') { rz_table_add_rowf(t, "u", sum); @@ -1168,7 +1360,7 @@ RZ_API void rz_table_columns_select(RZ_NONNULL RzTable *t, RZ_NONNULL RzList /*< size_t new_count = 0; rz_list_foreach (col_names, it, col_name) { size_t index = 0; - if (!table_index_of_column(t, col_name, &index)) { + if (!table_query_index_of_column(t, col_name, &index)) { continue; } col_sources[new_count].prev_index = index; @@ -1332,79 +1524,82 @@ RZ_API bool rz_table_query(RZ_NONNULL RzTable *t, RZ_NULLABLE const char *q) { const char *operation = rz_list_get_n(q, 1); const char *operand = rz_list_get_n(q, 2); - size_t col = 0; - if (!table_index_of_column(t, columnName, &col)) { - if (*columnName == '[') { - int signed_col = atoi(columnName + 1); - if (signed_col >= 0) { - col = signed_col; - } - } - } if (!operation) { - RzList *list = rz_list_new(); - if (list) { - rz_list_append(list, rz_str_dup(columnName)); - rz_table_columns_select(t, list); - rz_list_free(list); + if (!table_query_select_column(t, columnName)) { + rz_list_free(q); + continue; } } else if (!strcmp(operation, "sort")) { - rz_table_sort(t, col, operand && RZ_STR_EQ(operand, "rev")); + if (!table_query_sort(t, columnName, operand, false)) { + rz_list_free(q); + continue; + } } else if (!strcmp(operation, "uniq")) { - rz_table_group(t, col, NULL); + if (!table_query_group(t, columnName)) { + rz_list_free(q); + continue; + } } else if (!strcmp(operation, "sortlen")) { - rz_table_sortlen(t, col, operand && RZ_STR_EQ(operand, "rev")); + if (!table_query_sort(t, columnName, operand, true)) { + rz_list_free(q); + continue; + } } else if (!strcmp(operation, "join")) { // TODO: implement join operation with other command's tables } else if (!strcmp(operation, "sum")) { - char *op = rz_str_dup(operand ? operand : ""); - RzList *list = rz_str_split_list(op, "/", 0); - rz_list_prepend(list, rz_str_dup(columnName)); - rz_table_columns_select(t, list); // select/reorder columns - rz_list_free(list); - table_filter(t, 0, '+', op); - free(op); + if (!table_query_sum(t, columnName, operand)) { + rz_list_free(q); + continue; + } } else if (!strcmp(operation, "strlen")) { - if (operand) { - table_filter(t, col, 's', operand); + if (!table_query_filter(t, columnName, operand, 's')) { + rz_list_free(q); + continue; } } else if (!strcmp(operation, "minlen")) { - if (operand) { - table_filter(t, col, 'l', operand); + if (!table_query_filter(t, columnName, operand, 'l')) { + rz_list_free(q); + continue; } } else if (!strcmp(operation, "maxlen")) { - if (operand) { - table_filter(t, col, 'L', operand); + if (!table_query_filter(t, columnName, operand, 'L')) { + rz_list_free(q); + continue; } } else if (!strcmp(operation, "page")) { - if (operand) { - table_filter(t, col, 'p', operand); + if (!table_query_filter(t, columnName, operand, 'p')) { + rz_list_free(q); + continue; } } else if (!strcmp(operation, "tail")) { - if (operand) { - table_filter(t, col, 't', operand); + if (!table_query_filter(t, columnName, operand, 't')) { + rz_list_free(q); + continue; } } else if (!strcmp(operation, "head")) { - if (operand) { - table_filter(t, col, 'h', operand); + if (!table_query_filter(t, columnName, operand, 'h')) { + rz_list_free(q); + continue; } } else if (!strcmp(operation, "str")) { - if (operand) { - table_filter(t, col, '~', operand); + if (!table_query_filter(t, columnName, operand, '~')) { + rz_list_free(q); + continue; } } else if (!strcmp(operation, "cols")) { - char *op = rz_str_dup(operand ? operand : ""); - RzList *list = rz_str_split_list(op, "/", 0); - rz_list_prepend(list, rz_str_dup(columnName)); - rz_table_columns_select(t, list); // select/reorder columns - rz_list_free(list); - free(op); + if (!table_query_select_columns(t, columnName, operand)) { + rz_list_free(q); + continue; + } } else { int op = table_resolve_operation(operation); if (op == -1) { RZ_LOG_ERROR("table: Invalid operation (%s)\n", operation); } else { - table_filter(t, col, op, operand); + if (!table_query_filter(t, columnName, operand, op)) { + rz_list_free(q); + continue; + } } } rz_list_free(q); diff --git a/test/db/cmd/tables b/test/db/cmd/tables index c2aa1094fd9..cd8003daeae 100644 --- a/test/db/cmd/tables +++ b/test/db/cmd/tables @@ -246,3 +246,30 @@ EXPECT=< Date: Tue, 7 Apr 2026 10:59:04 +0530 Subject: [PATCH 2/2] as per suggestion, also updated tests with book ex --- librz/util/table.c | 24 ++++++++++++++++++++++-- test/unit/test_table.c | 32 +++++++++++++++++++++++++------- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/librz/util/table.c b/librz/util/table.c index e2f54bf2995..513b44c6f28 100644 --- a/librz/util/table.c +++ b/librz/util/table.c @@ -125,6 +125,26 @@ static bool table_index_of_column(RzTable *t, const char *name, size_t *index) { return false; } +static bool table_column_is_numeric(const RzTableColumn *col) { + // query aliases like addr/length are meant for num cols only. + return col && (col->type == RZ_TABLE_COLUMN_TYPE_NUMBER || col->type == RZ_TABLE_COLUMN_TYPE_BOOL); +} + +static bool table_query_index_of_numeric_column(RzTable *t, const char *name, size_t *index) { + size_t col = 0; + if (!table_index_of_column(t, name, &col)) { + return false; + } + RzTableColumn *column = rz_vector_index_ptr(t->cols, col); + if (!table_column_is_numeric(column)) { + return false; + } + if (index) { + *index = col; + } + return true; +} + static bool table_query_index_of_column(RzTable *t, const char *name, size_t *index) { static const char *const addr_aliases[] = { "vaddr", @@ -142,14 +162,14 @@ static bool table_query_index_of_column(RzTable *t, const char *name, size_t *in // Keep stable query names working across tables with slightly different headers. if (RZ_STR_EQ(name, "addr")) { for (size_t i = 0; addr_aliases[i]; i++) { - if (table_index_of_column(t, addr_aliases[i], index)) { + if (table_query_index_of_numeric_column(t, addr_aliases[i], index)) { return true; } } } if (RZ_STR_EQ(name, "length")) { for (size_t i = 0; length_aliases[i]; i++) { - if (table_index_of_column(t, length_aliases[i], index)) { + if (table_query_index_of_numeric_column(t, length_aliases[i], index)) { return true; } } diff --git a/test/unit/test_table.c b/test/unit/test_table.c index c7bf2fde784..97329c51ac5 100644 --- a/test/unit/test_table.c +++ b/test/unit/test_table.c @@ -384,10 +384,10 @@ bool test_rz_table_query(void) { } bool test_rz_table_query_regressions(void) { - // str filters must not parse string payloads as num, throws rz_num_calc error + // book ex 2: length alias should stay str-based for minlen filtering RzTable *t = rz_table_new(); rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_STRING, "string"); - rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_NUMBER, "length"); + rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_NUMBER, "len"); rz_table_add_row(t, "abcdefgh", "8", NULL); rz_table_add_row(t, "(([]A\\A])", "9", NULL); @@ -397,10 +397,10 @@ bool test_rz_table_query_regressions(void) { mu_assert_true(qr, "table filter by string length"); char *s = rz_table_tostring(t); mu_assert_streq(s, - "string,length\n" + "string,len\n" "longer_string,13\n" "(([]A\\A]),9\n", - "filter table by string length without parsing strings as math"); + "book length alias should filter str without parsing them as math"); free(s); rz_table_free(t); @@ -442,7 +442,7 @@ bool test_rz_table_query_regressions(void) { free(s); rz_table_free(t); - // should resolve to the table address col + // book ex 3: exact vaddr filters and addr alias sorts by the num addr col t = rz_table_new(); rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_NUMBER, "nth"); rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_NUMBER, "vaddr"); @@ -453,12 +453,30 @@ bool test_rz_table_query_regressions(void) { rz_table_add_row(t, "3", "0x1400", "bar_init", NULL); rz_table_add_row(t, "4", "0x2000", "foo_init", NULL); - qr = rz_table_query(t, "name/uniq:addr/gt/0x1000:name/str/init:addr/sort:json"); + qr = rz_table_query(t, "name/uniq:vaddr/gt/0x1000:name/str/init:addr/sort:json"); mu_assert_true(qr, "table filter by addr alias"); s = rz_table_tostring(t); mu_assert_streq(s, "[{\"nth\":3,\"vaddr\":5120,\"name\":\"bar_init\"},{\"nth\":2,\"vaddr\":5376,\"name\":\"foo_init\"}]\n", - "resolve addr alias to the vaddr column"); + "handbook addr alias should sort by the numeric vaddr column"); + free(s); + rz_table_free(t); + + // addr aliases must not bind to str headers when a num addr col exists + t = rz_table_new(); + rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_STRING, "address"); + rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_NUMBER, "paddr"); + rz_table_add_column(t, RZ_TABLE_COLUMN_TYPE_STRING, "name"); + + rz_table_add_row(t, "alpha", "0x10", "late", NULL); + rz_table_add_row(t, "zeta", "0x2", "early", NULL); + + qr = rz_table_query(t, "addr/sort:json"); + mu_assert_true(qr, "table sort by addr alias should prefer numeric address columns"); + s = rz_table_tostring(t); + mu_assert_streq(s, + "[{\"address\":\"zeta\",\"paddr\":2,\"name\":\"early\"},{\"address\":\"alpha\",\"paddr\":16,\"name\":\"late\"}]\n", + "addr alias should skip string address headers and use numeric address columns"); free(s); rz_table_free(t); mu_end;