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:
parent
06fb439bb6
commit
fbc0c2ccc1
10 changed files with 574 additions and 18 deletions
43
doc/pf.md
43
doc/pf.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue