type: add a pf-format to C-declaration converter (#6422)

format.c can already turn an RzType into a pf format string. Add the
inverse in the same file, next to its counterparts:
rz_type_format_to_c_declaration() parses a pf format string with
rz_pf_parse() and emits an equivalent C struct/union declaration built
from the standard fixed-width types (uint8_t, int32_t, float, ...).

The conversion is structural: it consumes only the parsed RzPfFormat
(field kinds, widths, array counts, pointer flags), never a byte buffer,
so it runs without any target data. Every field kind the engine produces
is mapped; a handful of specifiers with no exact static C form are mapped
best-effort (documented inline): @N alignment is dropped, an
unknown-length inline z string becomes char *, LEB128 widens to its
largest decoded integer, and ?(Name) / E(Name) emit struct/enum
references that must themselves be defined to parse.

Added new 'tdf' command that exposes that conversion to the user

Co-authored-by: Anton Kochkov <anton.kochkov@gmail.com>
This commit is contained in:
NOT XVilka 2026-05-30 00:29:06 +08:00 committed by GitHub
parent 06fb439bb6
commit fbc0c2ccc1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 574 additions and 18 deletions

View file

@ -299,6 +299,49 @@ as rizin commands) is no longer needed and is not supported -- the new
`pfw` writes directly. Likewise, the legacy `.pf*` "execute the rendered
output as commands" form is gone.
## Defining types from formats (`tdf`)
The **cstruct** output mode above only *prints* a C `struct`; the type is
not added to the type database, so it does not drive type analysis. To turn
a `pf` format into a real, analysis-backed type, use the `tdf` command
("type define from format"):
```text
tdf <name> <format>
```
`<format>` is either a literal `pf` format string or the name of a format
already saved with `pfn` / `pf.<name>`. The format name is resolved first
(exactly like `pf <name>`), then falls back to being parsed as a literal
format. The new type is registered through the same C-type parser as `td`,
so afterwards it behaves like any other `t` type -- it shows up in `ts` /
`tsc`, can be cast with `tp`, linked to addresses, and used as a member of
further `td` / `tdf` definitions.
```text
[0x00000000]> tdf rgba "x1x1x1x1 r g b a"
[0x00000000]> tsc rgba
struct rgba {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
[0x00000000]> pfn pixel x1x1x1x1 r g b a # save a named format ...
[0x00000000]> tdf pixel_t pixel # ... then promote it to a type
```
A leading `0` in the format makes the result a `union` instead of a
`struct`. Specifiers are mapped to the standard fixed-width types
(`x4`/`u4` → `uint32_t`, `d2``int16_t`, `c``char`, `f4``float`,
`p``void *`, `s``char *`, `G``uint8_t[16]`, and so on). A few
`pf` specifiers have no exact static C form and are converted best-effort:
`@N` alignment is dropped (use `.` / `[N].` to materialise padding bytes),
an unknown-length inline `z` string becomes `char *` (a fixed `[N]z`
becomes `char[N]`), LEB128 widens to its largest decoded integer, and
`?(Name)` / `E(Name)` references emit `struct Name` / `enum Name` -- which
must themselves already be defined for the new type to register.
## Deprecated single-letter aliases
For backward compatibility, the parser still accepts a handful of bare

View file

@ -357,6 +357,57 @@ RZ_IPI RzCmdStatus rz_type_define_handler(RzCore *core, int argc, const char **a
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_type_define_from_format_handler(RzCore *core, int argc, const char **argv) {
const char *name = argv[1];
const char *format_arg = argv[2];
RzTypeDB *typedb = rz_analysis_get_type_db(core->analysis);
/* Resolve the second argument. It may be either the name of a
* format saved with pfn / pf.<name> or a literal `pf` format string.
* A saved name is looked up first (a pure read of the formats hash);
* otherwise the argument is parsed as a literal format. This is the
* "including from existing saved ones" half of the feature, and it
* deliberately uses rz_type_db_format_get rather than
* rz_pf_resolve_name so that nothing but `tdf` ever writes into the
* type database -- and only with the final registered type. */
const char *fmt_str = rz_type_db_format_get(typedb, format_arg);
if (!fmt_str) {
fmt_str = format_arg;
}
char *error = NULL;
char *c_decl = rz_type_format_to_c_declaration(name, fmt_str, &error);
if (!c_decl) {
RZ_LOG_ERROR("Cannot build a type from format \"%s\": %s\n",
format_arg, error ? error : "unknown error");
free(error);
return RZ_CMD_STATUS_ERROR;
}
/* Register the synthesised declaration through the regular C type
* parser, exactly as `td` does, so the new type becomes a first-class
* RzBaseType that participates in type analysis. */
char *parse_error = NULL;
int rc = rz_type_parse_string_stateless(typedb->parser, c_decl, &parse_error);
if (rc && parse_error) {
rz_str_trim_tail(parse_error);
RZ_LOG_ERROR("Failed to define type \"%s\": %s\n", name, parse_error);
free(parse_error);
free(c_decl);
return RZ_CMD_STATUS_ERROR;
}
free(parse_error);
/* Confirm the type actually landed in the database. */
if (!rz_type_db_get_base_type(typedb, name)) {
RZ_LOG_ERROR("Type \"%s\" was not registered (check the format)\n", name);
free(c_decl);
return RZ_CMD_STATUS_ERROR;
}
free(c_decl);
return RZ_CMD_STATUS_OK;
}
RZ_IPI RzCmdStatus rz_type_list_enum_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode) {
if (argc > 1) {
if (argc > 2) {

View file

@ -94,6 +94,7 @@ static const RzCmdDescDetail print_function_rzil_enriched_details[6];
static const RzCmdDescDetail print_string_details[2];
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 interactive_visual_details[2];
static const RzCmdDescDetail write_details[3];
static const RzCmdDescDetail write_bits_details[2];
@ -876,6 +877,7 @@ static const RzCmdDescArg type_list_c_nl_args[2];
static const RzCmdDescArg type_cc_list_args[2];
static const RzCmdDescArg type_cc_del_args[2];
static const RzCmdDescArg type_define_args[2];
static const RzCmdDescArg type_define_from_format_args[3];
static const RzCmdDescArg type_list_enum_args[3];
static const RzCmdDescArg type_enum_bitfield_args[3];
static const RzCmdDescArg type_enum_c_args[2];
@ -19367,6 +19369,9 @@ static const RzCmdDescHelp type_cc_del_all_help = {
.args = type_cc_del_all_args,
};
static const RzCmdDescHelp td_help = {
.summary = "Define types from a C definition or a pf format string",
};
static const RzCmdDescArg type_define_args[] = {
{
.name = "type",
@ -19381,6 +19386,35 @@ static const RzCmdDescHelp type_define_help = {
.args = type_define_args,
};
static const RzCmdDescDetailEntry type_define_from_format_Examples_detail_entries[] = {
{ .text = "tdf", .arg_str = " rgba \"x1x1x1x1 r g b a\"", .comment = "define struct rgba from an inline pf format" },
{ .text = "tdf", .arg_str = " elf_header elf_header", .comment = "define a type from the saved pf.elf_header format" },
{ 0 },
};
static const RzCmdDescDetail type_define_from_format_details[] = {
{ .name = "Examples", .entries = type_define_from_format_Examples_detail_entries },
{ 0 },
};
static const RzCmdDescArg type_define_from_format_args[] = {
{
.name = "name",
.type = RZ_CMD_ARG_TYPE_STRING,
},
{
.name = "format",
.type = RZ_CMD_ARG_TYPE_STRING,
.flags = RZ_CMD_ARG_FLAG_LAST,
},
{ 0 },
};
static const RzCmdDescHelp type_define_from_format_help = {
.summary = "Define a type from a pf format string or a saved pf.<name>",
.details = type_define_from_format_details,
.args = type_define_from_format_args,
};
static const RzCmdDescHelp te_help = {
.summary = "List loaded enums",
};
@ -25682,8 +25716,10 @@ RZ_IPI void rzshell_cmddescs_init(RzCore *core) {
RzCmdDesc *type_cc_del_all_cd = rz_cmd_desc_argv_new(core->rcmd, tcc_cd, "tcc-*", rz_type_cc_del_all_handler, &type_cc_del_all_help);
rz_warn_if_fail(type_cc_del_all_cd);
RzCmdDesc *type_define_cd = rz_cmd_desc_argv_new(core->rcmd, t_cd, "td", rz_type_define_handler, &type_define_help);
rz_warn_if_fail(type_define_cd);
RzCmdDesc *td_cd = rz_cmd_desc_group_new(core->rcmd, t_cd, "td", rz_type_define_handler, &type_define_help, &td_help);
rz_warn_if_fail(td_cd);
RzCmdDesc *type_define_from_format_cd = rz_cmd_desc_argv_new(core->rcmd, td_cd, "tdf", rz_type_define_from_format_handler, &type_define_from_format_help);
rz_warn_if_fail(type_define_from_format_cd);
RzCmdDesc *te_cd = rz_cmd_desc_group_modes_new(core->rcmd, t_cd, "te", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_JSON, rz_type_list_enum_handler, &type_list_enum_help, &te_help);
rz_warn_if_fail(te_cd);

View file

@ -2511,6 +2511,8 @@ RZ_IPI RzCmdStatus rz_type_cc_del_handler(RzCore *core, int argc, const char **a
RZ_IPI RzCmdStatus rz_type_cc_del_all_handler(RzCore *core, int argc, const char **argv);
// "td"
RZ_IPI RzCmdStatus rz_type_define_handler(RzCore *core, int argc, const char **argv);
// "tdf"
RZ_IPI RzCmdStatus rz_type_define_from_format_handler(RzCore *core, int argc, const char **argv);
// "te"
RZ_IPI RzCmdStatus rz_type_list_enum_handler(RzCore *core, int argc, const char **argv, RzOutputMode mode);
// "teb"

View file

@ -70,11 +70,31 @@ commands:
summary: Remove all calling conventions
args: []
- name: td
cname: type_define
summary: Define type from C definition
args:
- name: type
type: RZ_CMD_ARG_TYPE_STRING
summary: Define types from a C definition or a pf format string
subcommands:
- name: td
cname: type_define
summary: Define type from C definition
args:
- name: type
type: RZ_CMD_ARG_TYPE_STRING
- name: tdf
cname: type_define_from_format
summary: Define a type from a pf format string or a saved pf.<name>
details:
- name: "Examples"
entries:
- text: "tdf"
arg_str: ' rgba "x1x1x1x1 r g b a"'
comment: "define struct rgba from an inline pf format"
- text: "tdf"
arg_str: " elf_header elf_header"
comment: "define a type from the saved pf.elf_header format"
args:
- name: name
type: RZ_CMD_ARG_TYPE_STRING
- name: format
type: RZ_CMD_ARG_TYPE_STRING
- name: te
summary: List loaded enums
subcommands:

View file

@ -410,6 +410,7 @@ RZ_API void rz_type_db_format_purge(RzTypeDB *typedb);
RZ_API RZ_OWN char *rz_base_type_as_format(const RzTypeDB *typedb, RZ_NONNULL RzBaseType *type);
RZ_API RZ_OWN char *rz_type_format(RZ_NONNULL const RzTypeDB *typedb, RZ_NONNULL const char *type);
RZ_API RZ_OWN char *rz_type_format_to_c_declaration(RZ_NONNULL const char *name, RZ_NONNULL const char *fmt_str, RZ_NULLABLE char **error);
RZ_API int rz_type_format_struct_size(const RzTypeDB *typedb, const char *f, int mode, int n);
RZ_API RZ_OWN char *rz_type_as_format(const RzTypeDB *typedb, RZ_NONNULL RzType *type);
RZ_API RZ_OWN char *rz_type_as_format_pair(const RzTypeDB *typedb, RZ_NONNULL RzType *type);

View file

@ -18,7 +18,8 @@ already include the latter need not include the former explicitly.
```
base.c RzBaseType (struct/union/enum/typedef) lifecycle
format.c Format-string codegen from RzType; named-format storage
format.c pf format-string codegen from RzType, the inverse
pf -> C-declaration converter, named-format storage
function.c RzCallable definitions (function prototypes)
helpers.c Type comparison, size, attribute helpers
path.c Path-style access into nested types
@ -183,6 +184,25 @@ or recursively `rz_pf_resolve_name + rz_pf_parse` (for nested struct
formats). Recursion is bounded by `RzPfCtx::max_depth` (default 32) to
catch self-referential structs cleanly.
## Defining types from `pf` formats (`pf` → C)
`format.c` also hosts the reverse helper
`rz_type_format_to_c_declaration(name, fmt_str, &error)`: it parses a
`pf` format with `rz_pf_parse()` and emits an equivalent C `struct`/`union`
declaration built from the standard fixed-width types (`uint8_t`, `int32_t`,
`float`, ...). Feeding that declaration to the C type parser (as the `tdf`
command does) registers the format as a first-class `RzBaseType`, so it then
participates in type analysis -- which a bare `pf` format never does.
The conversion is purely *structural*: it consumes only the parsed
`RzPfFormat` (field kinds, widths, array counts, pointer flags) and never a
byte buffer, so it can run without any target data. A few specifiers have no
exact static C form and are mapped best-effort: `@N` alignment carries no
storage and is dropped; an unknown-length inline string `z` becomes
`char *` (a fixed `[N]z` becomes `char[N]`); LEB128 widens to its largest
decoded integer; and `?(Name)` / `E(Name)` emit `struct Name` / `enum Name`,
which must themselves be defined types for the declaration to parse.
## Error reporting
Each `RzPfError` carries:

View file

@ -4,19 +4,18 @@
/**
* \file format.c
* \brief Convert RzType / RzBaseType values into pf format strings.
* \brief Convert between RzType / RzBaseType values and `pf` format strings.
*
* This file is the *producer* side: it walks an in-memory RzType tree
* (the AST built from C declarations parsed by tree-sitter) and emits
* the corresponding pf format string plus the matching " name1 name2..."
* tail.
* The main direction is the *producer* side: walk an in-memory RzType
* tree and emit the corresponding pf format string plus the matching
* " name1 name2..." tail.
*
* The *consumer* side -- parsing a pf string and using it to interpret
* bytes -- lives in pf_parser.c.
* The reverse helper rz_type_format_to_c_declaration() takes a pf format
* string and emits an equivalent C struct/union declaration, so a format
* can be promoted to a registered RzBaseType (used by the `tdf` command).
*
* The two sides share a single textual format, which is the DSL
* documented in pf_parser.h, so any new specifier added to the parser
* must also be wired in here when it has a meaningful RzType mapping.
* The byte-level consumer -- parsing a pf string and interpreting bytes
* through it -- lives in the pf engine under librz/type/pf/.
*/
#include "rz_util/rz_str_util.h"
@ -24,6 +23,7 @@
#include <rz_util/rz_print.h>
#include <rz_reg.h>
#include <rz_type.h>
#include <rz_pf.h>
/* Every format string essentially contains two parts:
* 1. The format (`pf` string) itself
@ -256,6 +256,190 @@ RZ_API RZ_OWN char *rz_type_format(RZ_NONNULL const RzTypeDB *typedb, RZ_NONNULL
return rz_base_type_as_format(typedb, btype);
}
static const char *uint_ctype_for_bytes(int nbytes) {
if (nbytes <= 1) {
return "uint8_t";
}
if (nbytes <= 2) {
return "uint16_t";
}
if (nbytes <= 4) {
return "uint32_t";
}
return "uint64_t";
}
static const char *uint_ctype_for_bits(int nbits) {
if (nbits <= 8) {
return "uint8_t";
}
if (nbits <= 16) {
return "uint16_t";
}
if (nbits <= 32) {
return "uint32_t";
}
return "uint64_t";
}
// Fixed-width integer the timestamp wire-format is decoded from.
static const char *pf_timefmt_ctype(RzPfTimeFmt tf) {
switch (tf) {
case RZ_PF_TIMEFMT_UNIX32:
case RZ_PF_TIMEFMT_DOS:
case RZ_PF_TIMEFMT_HFS:
return "uint32_t";
case RZ_PF_TIMEFMT_OLETIME:
case RZ_PF_TIMEFMT_COCOA:
return "double";
default:
return "uint64_t";
}
}
// Append a `<ctype> <name>;` member, or `<ctype> <name>[count];` when count > 1.
static void pf_emit_member(RzStrBuf *sb, const char *ctype, const char *name, int count) {
if (count > 1) {
rz_strbuf_appendf(sb, "\t%s %s[%d];\n", ctype, name, count);
} else {
rz_strbuf_appendf(sb, "\t%s %s;\n", ctype, name);
}
}
static void pf_field_to_member(RzStrBuf *sb, const RzPfField *fld, int idx) {
char namebuf[32];
const char *name = fld->name;
if (RZ_STR_ISEMPTY(name)) {
snprintf(namebuf, sizeof(namebuf), "field_%d", idx);
name = namebuf;
}
int count = fld->array_count;
switch (fld->type) {
case RZ_PF_ALIGN: // cursor alignment: no storage
case RZ_PF_TLV: // variable, self-describing: not expressible statically
return;
case RZ_PF_BITS: {
int w = fld->bit_width > 0 ? fld->bit_width : 1;
rz_strbuf_appendf(sb, "\t%s %s : %d;\n", uint_ctype_for_bits(w), name, w);
return;
}
case RZ_PF_SKIP:
case RZ_PF_HEXDUMP:
pf_emit_member(sb, "uint8_t", name, count > 0 ? count : 1);
return;
case RZ_PF_GUID:
case RZ_PF_UINT128:
pf_emit_member(sb, "uint8_t", name, 16);
return;
case RZ_PF_BITVEC:
pf_emit_member(sb, "uint8_t", name, fld->bit_width > 0 ? (fld->bit_width + 7) / 8 : 1);
return;
case RZ_PF_ZSTRING:
// only a fixed-length [N]z can be sized; bare z is best-effort char *
if (fld->str_fixed_len > 0) {
rz_strbuf_appendf(sb, "\tchar %s[%d];\n", name, fld->str_fixed_len);
} else {
rz_strbuf_appendf(sb, "\tchar *%s;\n", name);
}
return;
case RZ_PF_STRPTR:
rz_strbuf_appendf(sb, "\tchar *%s;\n", name);
return;
case RZ_PF_POINTER:
if (count > 1) {
rz_strbuf_appendf(sb, "\tvoid *%s[%d];\n", name, count);
} else {
rz_strbuf_appendf(sb, "\tvoid *%s;\n", name);
}
return;
case RZ_PF_STRUCT:
if (RZ_STR_ISEMPTY(fld->type_name)) {
pf_emit_member(sb, "uint8_t", name, count); // anonymous: placeholder byte
} else if (count > 1) {
rz_strbuf_appendf(sb, "\tstruct %s %s[%d];\n", fld->type_name, name, count);
} else {
rz_strbuf_appendf(sb, "\tstruct %s %s;\n", fld->type_name, name);
}
return;
case RZ_PF_ENUM:
if (RZ_STR_ISEMPTY(fld->type_name)) {
pf_emit_member(sb, uint_ctype_for_bytes(fld->bit_width > 0 ? fld->bit_width : 4), name, count);
} else if (count > 1) {
rz_strbuf_appendf(sb, "\tenum %s %s[%d];\n", fld->type_name, name, count);
} else {
rz_strbuf_appendf(sb, "\tenum %s %s;\n", fld->type_name, name);
}
return;
case RZ_PF_BITFIELD:
pf_emit_member(sb, uint_ctype_for_bytes(fld->bitfield_size > 0 ? fld->bitfield_size : 4), name, count);
return;
case RZ_PF_TIMESTAMP:
pf_emit_member(sb, pf_timefmt_ctype(fld->timefmt), name, count);
return;
case RZ_PF_CHAR:
pf_emit_member(sb, "char", name, count);
return;
case RZ_PF_ULEB128: // variable length on the wire; modelled by widest value
pf_emit_member(sb, "uint64_t", name, count);
return;
case RZ_PF_SLEB128:
pf_emit_member(sb, "int64_t", name, count);
return;
case RZ_PF_FLOAT16: // no standard 2-byte float type; model storage width
pf_emit_member(sb, "uint16_t", name, count);
return;
default: // hex / signed / unsigned / octal / binary scalars
pf_emit_member(sb, rz_pf_field_ctype(fld->type), name, count);
return;
}
}
/**
* \brief Convert a `pf` format string into an equivalent C declaration
*
* Parses \p fmt_str and renders a C `struct` (or `union`, when the format
* begins with the `0` union marker) named \p name, using standard
* fixed-width types. The result is a complete declaration ending in `;`,
* ready to pass to rz_type_parse_string_stateless() so the format becomes
* a registered RzBaseType. The conversion is structural: it consumes only
* the parsed format, never a byte buffer. Specifiers with no exact static
* C form are mapped best-effort (`@N` dropped, unsized `z` -> char *,
* LEB128 widened, `?(Name)`/`E(Name)` -> struct/enum references).
*
* \param name Identifier for the generated struct/union
* \param fmt_str A `pf` format string (the `fmt fieldnames` form)
* \param error Optional; set to an owned error message on failure
* \return Owned C declaration string, or NULL on failure
*/
RZ_API RZ_OWN char *rz_type_format_to_c_declaration(RZ_NONNULL const char *name,
RZ_NONNULL const char *fmt_str, RZ_NULLABLE char **error) {
rz_return_val_if_fail(name && fmt_str, NULL);
if (RZ_STR_ISEMPTY(name) || RZ_STR_ISEMPTY(fmt_str)) {
if (error) {
*error = rz_str_dup("empty type name or format string");
}
return NULL;
}
RzPfFormat *fmt = rz_pf_parse(fmt_str);
if (!fmt || fmt->nfields <= 0) {
if (error) {
char *diag = fmt ? rz_pf_format_errors_to_string(fmt) : NULL;
*error = diag ? diag : rz_str_dup("pf format defined no fields");
}
rz_pf_format_free(fmt);
return NULL;
}
RzStrBuf *sb = rz_strbuf_new(NULL);
rz_strbuf_appendf(sb, "%s %s {\n", fmt->is_union ? "union" : "struct", name);
for (int i = 0; i < fmt->nfields; i++) {
pf_field_to_member(sb, &fmt->fields[i], i);
}
rz_strbuf_append(sb, "};");
rz_pf_format_free(fmt);
return rz_strbuf_drain(sb);
}
/* True iff `type` is a POINTER whose pointee resolves -- by walking
* through typedef chains in the typedb -- to an atomic base type named
* exactly `atomic_name`. This is the typedb-aware counterpart of the

View file

@ -8472,3 +8472,123 @@ struct foo {
};
EOF
RUN
NAME=tdf define struct from inline pf format
FILE==
CMDS=<<EOF
tdf rgba "x1x1x1x1 r g b a"
tsc rgba
EOF
EXPECT=<<EOF
struct rgba {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
EOF
RUN
NAME=tdf maps pf specifiers to fixed-width C types
FILE==
CMDS=<<EOF
tdf widths "x1d2u4x8 hx d16 u32 q64"
tsc widths
tdf scal "cf4psGx8 ch flt ptr str guid big"
tsc scal
EOF
EXPECT=<<EOF
struct widths {
uint8_t hx;
int16_t d16;
uint32_t u32;
uint64_t q64;
};
struct scal {
char ch;
float flt;
void *ptr;
char *str;
uint8_t guid[16];
uint64_t big;
};
EOF
RUN
NAME=tdf define fixed-size array field from pf format
FILE==
CMDS=<<EOF
tdf arr3 "[4]d4 vals"
tsc arr3
EOF
EXPECT=<<EOF
struct arr3 {
int32_t vals[4];
};
EOF
RUN
NAME=tdf define union from pf union format
FILE==
CMDS=<<EOF
tdf onion "0d4d4d4 a b c"
tuc onion
EOF
EXPECT=<<EOF
union onion {
int32_t a;
int32_t b;
int32_t c;
};
EOF
RUN
NAME=tdf define type from a saved pf format
FILE==
CMDS=<<EOF
pfn rgbfmt x1x1x1x1 r g b a
tdf rgbsaved rgbfmt
tsc rgbsaved
EOF
EXPECT=<<EOF
struct rgbsaved {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
EOF
RUN
NAME=tdf produces a first-class base type usable in further definitions
FILE==
CMDS=<<EOF
tdf inner "d4d4 x y"
td "struct outer { struct inner pt; int32_t flag; }"
tsc outer
EOF
EXPECT=<<EOF
struct outer {
struct inner pt;
int32_t flag;
};
EOF
RUN
NAME=pf and pfn do not register types, only tdf does
FILE==
CMDS=<<EOF
pfn pixfmt x1x1x1x1 r g b a
t~pixfmt
tdf pixtype pixfmt
tsc pixtype
EOF
EXPECT=<<EOF
struct pixtype {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
EOF
RUN

View file

@ -1878,7 +1878,86 @@ bool test_callable_unspecified_parameters(void) {
mu_end;
}
static bool test_type_format_to_c_declaration_struct(void) {
char *s = rz_type_format_to_c_declaration("rgba", "x1x1x1x1 r g b a", NULL);
mu_assert_streq_free(s,
"struct rgba {\n\tuint8_t r;\n\tuint8_t g;\n\tuint8_t b;\n\tuint8_t a;\n};",
"struct from inline pf format");
mu_end;
}
static bool test_type_format_to_c_declaration_union(void) {
char *s = rz_type_format_to_c_declaration("onion", "0d4d4d4 a b c", NULL);
mu_assert_streq_free(s,
"union onion {\n\tint32_t a;\n\tint32_t b;\n\tint32_t c;\n};",
"union from leading-0 pf format");
mu_end;
}
static bool test_type_format_to_c_declaration_pointers_and_strings(void) {
char *s = rz_type_format_to_c_declaration("ps", "ps ptr str", NULL);
mu_assert_streq_free(s,
"struct ps {\n\tvoid *ptr;\n\tchar *str;\n};",
"pointer and string fields");
mu_end;
}
static bool test_type_format_to_c_declaration_array(void) {
char *s = rz_type_format_to_c_declaration("arr", "[4]d4 vals", NULL);
mu_assert_streq_free(s,
"struct arr {\n\tint32_t vals[4];\n};",
"fixed-size array field");
mu_end;
}
static bool test_type_format_to_c_declaration_invalid(void) {
char *err = NULL;
char *s = rz_type_format_to_c_declaration("", "x4 a", &err);
mu_assert_null(s, "empty name rejected");
free(err);
err = NULL;
s = rz_type_format_to_c_declaration("foo", "", &err);
mu_assert_null(s, "empty format rejected");
free(err);
mu_end;
}
static bool test_type_format_to_c_declaration_registers_base_type(void) {
RzTypeDB *typedb = rz_type_db_new();
mu_assert_notnull(typedb, "Couldn't create new RzTypeDB");
const char *types_dir = TEST_BUILD_TYPES_DIR;
rz_type_db_init(typedb, types_dir, "x86", 64, "linux");
char *decl = rz_type_format_to_c_declaration("point", "d4d4 x y", NULL);
mu_assert_notnull(decl, "converted format to declaration");
char *error_msg = NULL;
rz_type_parse_string_stateless(typedb->parser, decl, &error_msg);
free(decl);
RzBaseType *base = rz_type_db_get_base_type(typedb, "point");
mu_assert_notnull(base, "type registered in the database");
mu_assert_eq(RZ_BASE_TYPE_KIND_STRUCT, base->kind, "registered as struct");
mu_assert_eq(rz_vector_len(&base->struct_data.members), 2, "two members");
RzTypeStructMember *m = rz_vector_index_ptr(&base->struct_data.members, 0);
mu_assert_true(rz_type_atomic_str_eq(typedb, m->type, "int32_t"), "first member type");
mu_assert_streq(m->name, "x", "first member name");
m = rz_vector_index_ptr(&base->struct_data.members, 1);
mu_assert_true(rz_type_atomic_str_eq(typedb, m->type, "int32_t"), "second member type");
mu_assert_streq(m->name, "y", "second member name");
rz_type_db_free(typedb);
mu_end;
}
int all_tests() {
mu_run_test(test_type_format_to_c_declaration_struct);
mu_run_test(test_type_format_to_c_declaration_union);
mu_run_test(test_type_format_to_c_declaration_pointers_and_strings);
mu_run_test(test_type_format_to_c_declaration_array);
mu_run_test(test_type_format_to_c_declaration_invalid);
mu_run_test(test_type_format_to_c_declaration_registers_base_type);
mu_run_test(test_types_get_base_type_struct);
mu_run_test(test_types_get_base_type_union);
mu_run_test(test_types_get_base_type_enum);