From f2f4543a2674251acd959ae8381bf906f1dc53fa Mon Sep 17 00:00:00 2001 From: Anton Kochkov Date: Tue, 2 Jun 2026 13:43:11 +0800 Subject: [PATCH] type: add `tk` commands to list and manage typeclasses (#3381) Expose the typeclasses of types through the new `tk` commands: - `tk ` shows the typeclass of a type; - `tkl` lists the available typeclasses, while `tkll` (verbose), `tklt` (table) and `tklj` (JSON) additionally list the types belonging to each typeclass; - `tks ` sets the typeclass of a type. A new rz_base_type_set_typeclass() API backs the `tks` command. Since a typedef without its own typeclass inherits the one of the type it points to, setting the typeclass of an atomic type is automatically reflected on the typedefs resolving to it. The `tk` help also explains what typeclasses are and lists the available ones. Closes #3371 --- librz/core/cmd/cmd_type.c | 38 ++++++++++++++++ librz/core/cmd_descs/cmd_descs.c | 65 ++++++++++++++++++++++++++ librz/core/cmd_descs/cmd_descs.h | 6 +++ librz/core/cmd_descs/cmd_type.yaml | 44 ++++++++++++++++++ librz/core/core_private.h | 1 + librz/core/ctypes.c | 73 ++++++++++++++++++++++++++++++ librz/include/rz_type.h | 1 + librz/type/typeclass.c | 20 ++++++++ test/db/cmd/cmd_typeclass | 53 ++++++++++++++++++++++ 9 files changed, 301 insertions(+) create mode 100644 test/db/cmd/cmd_typeclass diff --git a/librz/core/cmd/cmd_type.c b/librz/core/cmd/cmd_type.c index ac14000dc3..5bdb6ee200 100644 --- a/librz/core/cmd/cmd_type.c +++ b/librz/core/cmd/cmd_type.c @@ -865,3 +865,41 @@ RZ_IPI RzCmdStatus rz_type_xrefs_list_all_handler(RzCore *core, int argc, const types_xrefs_all(core); return RZ_CMD_STATUS_OK; } + +RZ_IPI RzCmdStatus rz_type_typeclass_handler(RzCore *core, int argc, const char **argv) { + RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis); + RzBaseType *btype = rz_type_db_get_base_type(typedb, argv[1]); + if (!btype) { + RZ_LOG_ERROR("Type \"%s\" does not exist\n", argv[1]); + return RZ_CMD_STATUS_ERROR; + } + RzTypeTypeclass typeclass = rz_base_type_typeclass(typedb, btype); + rz_cons_println(rz_type_typeclass_as_string(typeclass)); + return RZ_CMD_STATUS_OK; +} + +RZ_IPI RzCmdStatus rz_type_typeclass_list_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) { + rz_core_types_typeclass_print_all(core, mode); + return RZ_CMD_STATUS_OK; +} + +RZ_IPI RzCmdStatus rz_type_typeclass_set_handler(RzCore *core, int argc, const char **argv) { + RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis); + RzBaseType *btype = rz_type_db_get_base_type(typedb, argv[1]); + if (!btype) { + RZ_LOG_ERROR("Type \"%s\" does not exist\n", argv[1]); + return RZ_CMD_STATUS_ERROR; + } + RzTypeTypeclass typeclass = rz_type_typeclass_from_string(argv[2]); + // rz_type_typeclass_from_string returns RZ_TYPE_TYPECLASS_NONE both for the + // "None" typeclass and for an unknown string, so reject unknown ones here. + if (typeclass == RZ_TYPE_TYPECLASS_NONE && strcmp(argv[2], "None")) { + RZ_LOG_ERROR("Unknown typeclass \"%s\"\n", argv[2]); + return RZ_CMD_STATUS_ERROR; + } + if (!rz_base_type_set_typeclass(btype, typeclass)) { + RZ_LOG_ERROR("Cannot set typeclass \"%s\" on type \"%s\"\n", argv[2], argv[1]); + return RZ_CMD_STATUS_ERROR; + } + return RZ_CMD_STATUS_OK; +} diff --git a/librz/core/cmd_descs/cmd_descs.c b/librz/core/cmd_descs/cmd_descs.c index e1a3762674..4d2b90ddc7 100644 --- a/librz/core/cmd_descs/cmd_descs.c +++ b/librz/core/cmd_descs/cmd_descs.c @@ -96,6 +96,7 @@ static const RzCmdDescDetail print_hexdump_format_details[4]; static const RzCmdDescDetail print_rising_and_falling_entropy_details[2]; static const RzCmdDescDetail type_define_from_format_details[2]; static const RzCmdDescDetail type_rename_details[2]; +static const RzCmdDescDetail type_typeclass_set_details[2]; static const RzCmdDescDetail interactive_visual_details[2]; static const RzCmdDescDetail write_details[3]; static const RzCmdDescDetail write_bits_details[2]; @@ -906,6 +907,8 @@ static const RzCmdDescArg type_union_c_args[2]; static const RzCmdDescArg type_union_c_nl_args[2]; static const RzCmdDescArg type_xrefs_list_args[2]; static const RzCmdDescArg type_xrefs_function_args[2]; +static const RzCmdDescArg type_typeclass_args[2]; +static const RzCmdDescArg type_typeclass_set_args[3]; static const RzCmdDescArg interactive_visual_args[2]; static const RzCmdDescArg interactive_panel_load_args[2]; static const RzCmdDescArg interactive_panel_store_args[2]; @@ -19903,6 +19906,60 @@ static const RzCmdDescHelp type_xrefs_list_all_help = { .args = type_xrefs_list_all_args, }; +static const RzCmdDescHelp tk_help = { + .summary = "Manage the typeclasses of types", + .description = "Typeclasses classify atomic types by the general kind of value they hold, independently of their concrete name or size, so the analysis can pick a suitable type generically (for example any signed integer). The available typeclasses are Num (any number), Integral (any integer), Floating (any floating point number), Address (integer types used to work with pointers), Signed Integral and Unsigned Integral (the signed and unsigned subclasses of Integral) and None (the most generic one, used when no specific typeclass applies).", +}; +static const RzCmdDescArg type_typeclass_args[] = { + { + .name = "type", + .type = RZ_CMD_ARG_TYPE_ANY_TYPE, + + }, + { 0 }, +}; +static const RzCmdDescHelp type_typeclass_help = { + .summary = "Show the typeclass of the given type", + .args = type_typeclass_args, +}; + +static const RzCmdDescArg type_typeclass_list_args[] = { + { 0 }, +}; +static const RzCmdDescHelp type_typeclass_list_help = { + .summary = "List all typeclasses, or the types belonging to each of them", + .args = type_typeclass_list_args, +}; + +static const RzCmdDescDetailEntry type_typeclass_set_Examples_detail_entries[] = { + { .text = "tks", .arg_str = " int Floating", .comment = "treat the int type as the Floating typeclass" }, + { .text = "tks", .arg_str = " int \"Signed Integral\"", .comment = "set a typeclass whose name contains a space (quote it)" }, + { 0 }, +}; +static const RzCmdDescDetail type_typeclass_set_details[] = { + { .name = "Examples", .entries = type_typeclass_set_Examples_detail_entries }, + { 0 }, +}; +static const RzCmdDescArg type_typeclass_set_args[] = { + { + .name = "type", + .type = RZ_CMD_ARG_TYPE_ANY_TYPE, + + }, + { + .name = "typeclass", + .type = RZ_CMD_ARG_TYPE_STRING, + .flags = RZ_CMD_ARG_FLAG_LAST, + + }, + { 0 }, +}; +static const RzCmdDescHelp type_typeclass_set_help = { + .summary = "Set the typeclass of a type", + .details = type_typeclass_set_details, + .args = type_typeclass_set_args, +}; + static const RzCmdDescHelp V_help = { .summary = "Interactive mode", }; @@ -25835,6 +25892,14 @@ RZ_IPI void rzshell_cmddescs_init(RzCore *core) { RzCmdDesc *type_xrefs_list_all_cd = rz_cmd_desc_argv_new(core->rcmd, tx_cd, "txl", rz_type_xrefs_list_all_handler, &type_xrefs_list_all_help); rz_warn_if_fail(type_xrefs_list_all_cd); + RzCmdDesc *tk_cd = rz_cmd_desc_group_new(core->rcmd, t_cd, "tk", rz_type_typeclass_handler, &type_typeclass_help, &tk_help); + rz_warn_if_fail(tk_cd); + RzCmdDesc *type_typeclass_list_cd = rz_cmd_desc_argv_modes_new(core->rcmd, tk_cd, "tkl", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_LONG | RZ_OUTPUT_MODE_TABLE | RZ_OUTPUT_MODE_JSON, rz_type_typeclass_list_handler, &type_typeclass_list_help); + rz_warn_if_fail(type_typeclass_list_cd); + + RzCmdDesc *type_typeclass_set_cd = rz_cmd_desc_argv_new(core->rcmd, tk_cd, "tks", rz_type_typeclass_set_handler, &type_typeclass_set_help); + rz_warn_if_fail(type_typeclass_set_cd); + RzCmdDesc *V_cd = rz_cmd_desc_group_new(core->rcmd, root_cd, "V", rz_interactive_visual_handler, &interactive_visual_help, &V_help); rz_warn_if_fail(V_cd); RzCmdDesc *interactive_visual_help_cd = rz_cmd_desc_argv_new(core->rcmd, V_cd, "VH", rz_interactive_visual_help_handler, &interactive_visual_help_help); diff --git a/librz/core/cmd_descs/cmd_descs.h b/librz/core/cmd_descs/cmd_descs.h index 3078b77083..e3eabb43ec 100644 --- a/librz/core/cmd_descs/cmd_descs.h +++ b/librz/core/cmd_descs/cmd_descs.h @@ -2575,6 +2575,12 @@ RZ_IPI RzCmdStatus rz_type_xrefs_function_handler(RzCore *core, int argc, const RZ_IPI RzCmdStatus rz_type_xrefs_graph_handler(RzCore *core, int argc, const char **argv); // "txl" RZ_IPI RzCmdStatus rz_type_xrefs_list_all_handler(RzCore *core, int argc, const char **argv); +// "tk" +RZ_IPI RzCmdStatus rz_type_typeclass_handler(RzCore *core, int argc, const char **argv); +// "tkl" +RZ_IPI RzCmdStatus rz_type_typeclass_list_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode); +// "tks" +RZ_IPI RzCmdStatus rz_type_typeclass_set_handler(RzCore *core, int argc, const char **argv); // "V" RZ_IPI RzCmdStatus rz_interactive_visual_handler(RzCore *core, int argc, const char **argv); // "VH" diff --git a/librz/core/cmd_descs/cmd_type.yaml b/librz/core/cmd_descs/cmd_type.yaml index a8b0540b46..4c39578bb0 100644 --- a/librz/core/cmd_descs/cmd_type.yaml +++ b/librz/core/cmd_descs/cmd_type.yaml @@ -360,3 +360,47 @@ commands: cname: type_xrefs_list_all summary: List all types used by any function args: [] + - name: tk + summary: Manage the typeclasses of types + description: > + Typeclasses classify atomic types by the general kind of value they hold, + independently of their concrete name or size, so the analysis can pick a + suitable type generically (for example any signed integer). The available + typeclasses are Num (any number), Integral (any integer), Floating (any + floating point number), Address (integer types used to work with + pointers), Signed Integral and Unsigned Integral (the signed and unsigned + subclasses of Integral) and None (the most generic one, used when no + specific typeclass applies). + subcommands: + - name: tk + cname: type_typeclass + summary: Show the typeclass of the given type + args: + - name: type + type: RZ_CMD_ARG_TYPE_ANY_TYPE + - name: tkl + cname: type_typeclass_list + summary: List all typeclasses, or the types belonging to each of them + modes: + - RZ_OUTPUT_MODE_STANDARD + - RZ_OUTPUT_MODE_LONG + - RZ_OUTPUT_MODE_TABLE + - RZ_OUTPUT_MODE_JSON + args: [] + - name: tks + cname: type_typeclass_set + summary: Set the typeclass of a type + args: + - name: type + type: RZ_CMD_ARG_TYPE_ANY_TYPE + - name: typeclass + type: RZ_CMD_ARG_TYPE_STRING + details: + - name: Examples + entries: + - text: "tks" + arg_str: " int Floating" + comment: "treat the int type as the Floating typeclass" + - text: "tks" + arg_str: ' int "Signed Integral"' + comment: "set a typeclass whose name contains a space (quote it)" diff --git a/librz/core/core_private.h b/librz/core/core_private.h index ce939c48ed..45534b5de7 100644 --- a/librz/core/core_private.h +++ b/librz/core/core_private.h @@ -112,6 +112,7 @@ RZ_IPI void rz_core_types_struct_print_format_all(RzCore *core); RZ_IPI void rz_core_types_union_print_format_all(RzCore *core); RZ_IPI void rz_core_types_print_all(RzCore *core, RzOutputMode mode); RZ_IPI bool rz_core_types_rename(RzAnalysis *analysis, RZ_NONNULL const char *from, RZ_NONNULL const char *to); +RZ_IPI void rz_core_types_typeclass_print_all(RzCore *core, RzOutputMode mode); RZ_IPI void rz_types_define(RzCore *core, const char *type); RZ_IPI bool rz_types_open_file(RzCore *core, const char *path); RZ_IPI bool rz_types_open_editor(RzCore *core, RZ_NONNULL const char *typename); diff --git a/librz/core/ctypes.c b/librz/core/ctypes.c index e5d26feae2..b9a78bdb19 100644 --- a/librz/core/ctypes.c +++ b/librz/core/ctypes.c @@ -1078,3 +1078,76 @@ RZ_IPI bool rz_core_types_rename(RzAnalysis *analysis, RZ_NONNULL const char *fr } return true; } + +/** + * \brief Prints all typeclasses, or the types belonging to each typeclass + * + * \param core RzCore instance + * \param mode Output mode: STANDARD lists the typeclass names, LONG/TABLE/JSON + * additionally list the atomic types belonging to each typeclass + */ +RZ_IPI void rz_core_types_typeclass_print_all(RzCore *core, RzOutputMode mode) { + rz_return_if_fail(core && core->analysis); + RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis); + RzTypeTypeclass tc; + switch (mode) { + case RZ_OUTPUT_MODE_STANDARD: + for (tc = RZ_TYPE_TYPECLASS_NUM; tc < RZ_TYPE_TYPECLASS_INVALID; tc++) { + rz_cons_println(rz_type_typeclass_as_string(tc)); + } + break; + case RZ_OUTPUT_MODE_LONG: + for (tc = RZ_TYPE_TYPECLASS_NUM; tc < RZ_TYPE_TYPECLASS_INVALID; tc++) { + rz_cons_printf("%s:\n", rz_type_typeclass_as_string(tc)); + RzList *types = rz_type_typeclass_get_all(typedb, tc); + RzListIter *it; + RzBaseType *btype; + rz_list_foreach (types, it, btype) { + rz_cons_printf(" %s\n", btype->name); + } + rz_list_free(types); + } + break; + case RZ_OUTPUT_MODE_TABLE: { + RzTable *table = rz_table_new(); + rz_table_set_columnsf(table, "ss", "typeclass", "type"); + for (tc = RZ_TYPE_TYPECLASS_NUM; tc < RZ_TYPE_TYPECLASS_INVALID; tc++) { + const char *tcname = rz_type_typeclass_as_string(tc); + RzList *types = rz_type_typeclass_get_all(typedb, tc); + RzListIter *it; + RzBaseType *btype; + rz_list_foreach (types, it, btype) { + rz_table_add_rowf(table, "ss", tcname, btype->name); + } + rz_list_free(types); + } + char *s = rz_table_tostring(table); + rz_cons_print(s); + free(s); + rz_table_free(table); + break; + } + case RZ_OUTPUT_MODE_JSON: { + PJ *pj = pj_new(); + pj_o(pj); + for (tc = RZ_TYPE_TYPECLASS_NUM; tc < RZ_TYPE_TYPECLASS_INVALID; tc++) { + pj_ka(pj, rz_type_typeclass_as_string(tc)); + RzList *types = rz_type_typeclass_get_all(typedb, tc); + RzListIter *it; + RzBaseType *btype; + rz_list_foreach (types, it, btype) { + pj_s(pj, btype->name); + } + rz_list_free(types); + pj_end(pj); + } + pj_end(pj); + rz_cons_println(pj_string(pj)); + pj_free(pj); + break; + } + default: + rz_warn_if_reached(); + break; + } +} diff --git a/librz/include/rz_type.h b/librz/include/rz_type.h index 26c190e93c..3a5b927e89 100644 --- a/librz/include/rz_type.h +++ b/librz/include/rz_type.h @@ -318,6 +318,7 @@ RZ_API int rz_type_kind(RzTypeDB *typedb, RZ_NONNULL const char *name); RZ_API RZ_BORROW const char *rz_type_typeclass_as_string(RzTypeTypeclass typeclass); RZ_API RzTypeTypeclass rz_type_typeclass_from_string(RZ_NONNULL const char *typeclass); RZ_API RzTypeTypeclass rz_base_type_typeclass(const RzTypeDB *typedb, RZ_NONNULL const RzBaseType *type); +RZ_API bool rz_base_type_set_typeclass(RZ_NONNULL RzBaseType *type, RzTypeTypeclass typeclass); RZ_API RzTypeTypeclass rz_type_typeclass(const RzTypeDB *typedb, RZ_NONNULL const RzType *type); RZ_API bool rz_base_type_is_num(const RzTypeDB *typedb, RZ_NONNULL const RzBaseType *type); RZ_API bool rz_type_is_num(const RzTypeDB *typedb, RZ_NONNULL const RzType *type); diff --git a/librz/type/typeclass.c b/librz/type/typeclass.c index a37be2ac32..12ac00c6e0 100644 --- a/librz/type/typeclass.c +++ b/librz/type/typeclass.c @@ -151,6 +151,26 @@ RZ_API RzTypeTypeclass rz_base_type_typeclass(const RzTypeDB *typedb, RZ_NONNULL return tclass; } +/** + * \brief Sets the typeclass of an atomic base type + * + * The typeclass is stored in the type attributes. Setting it on an atomic type + * also affects every typedef that resolves to it, since a typedef without its + * own typeclass inherits the one of the type it points to. + * + * \param type The base type to change + * \param typeclass The new typeclass to assign + * \return true on success, false if \p typeclass is not a valid typeclass + */ +RZ_API bool rz_base_type_set_typeclass(RZ_NONNULL RzBaseType *type, RzTypeTypeclass typeclass) { + rz_return_val_if_fail(type, false); + if (typeclass >= RZ_TYPE_TYPECLASS_INVALID) { + return false; + } + type->attrs = (type->attrs & ~((RzTypeAttribute)RZ_TYPE_ATTRIBUTE_TYPECLASS_MASK)) | typeclass; + return true; +} + /** * \brief Gets the type class * diff --git a/test/db/cmd/cmd_typeclass b/test/db/cmd/cmd_typeclass new file mode 100644 index 0000000000..7fc0491cae --- /dev/null +++ b/test/db/cmd/cmd_typeclass @@ -0,0 +1,53 @@ +NAME=tk typeclass commands +FILE== +CMDS=<