rizin/librz/type
NOT XVilka 06217cd05e
librz/type: fix forward type for enums (#6539)
Co-authored-by: Anton Kochkov <anton.kochkov@gmail.com>
2026-06-20 17:32:06 +08:00
..
parser librz/type: fix forward type for enums (#6539) 2026-06-20 17:32:06 +08:00
pf librz/type: fix orphan '*' when emitting pf format for typedef'd void*/char* (#6420) 2026-05-29 09:06:28 +08:00
base.c librz/type: add tr command to rename a type and its references (#4016) 2026-06-01 05:04:05 +08:00
format.c librz/type: support C bitfield members in structs and unions (#1240, #314) (#6439) 2026-06-01 05:03:31 +08:00
function.c librz/type: fix struct/union and RzCallable memory leaks (#6423) 2026-05-29 23:58:34 +08:00
helpers.c Expose constrained types to user and analysis (#6480) 2026-06-12 02:36:35 +08:00
meson.build Rewrite the pf parser and rework the grammar (#6410) 2026-05-29 03:58:58 +08:00
path.c Fix the missing release of RZ_OWN parameters (#5641) 2025-12-21 13:48:48 +08:00
README.md type: add a pf-format to C-declaration converter (#6422) 2026-05-30 00:29:06 +08:00
serialize_functions.c Fix the missing release of RZ_OWN parameters (#5641) 2025-12-21 13:48:48 +08:00
serialize_types.c librz/type: support C bitfield members in structs and unions (#1240, #314) (#6439) 2026-06-01 05:03:31 +08:00
type.c librz/arch: render flag-enum operands as an OR of members (#2344) (#6450) 2026-06-02 04:22:25 +08:00
typeclass.c type: add tk commands to list and manage typeclasses (#3381) 2026-06-02 13:43:11 +08:00

Type database and pf format engine

This library owns Rizin's representation of C-style types (structs, unions, enums, typedefs, callables) and the pf print-format language for decoding raw bytes through that type information.

Public headers

Header Surface
<rz_type.h> RzTypeDB, base types, callables, format storage
<rz_pf.h> RzPfFormat, RzPfValue, RzPfCtx, parser/reader/renderer

<rz_pf.h> is included transitively from <rz_type.h>, so callers that already include the latter need not include the former explicitly.

File map

base.c                  RzBaseType (struct/union/enum/typedef) lifecycle
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
serialize_*.c           Project (de)serialization of types and functions
parser/                 Tree-sitter based C type parser

pf/                     The pf (print-format) engine. All sources live
                        under this directory; only format.c stays at the
                        librz/type/ top level.
pf/pf_parser.c          Parse driver, type-spec dispatcher, reader core,
                        parsing context, public utility surface
pf/pf_parser.h          Internal alias header -- includes <rz_pf.h>, plus
                        helpers shared with pf_render.c (is_string_type,
                        is_raw_type, endian_str, pf_vasprintf) as inlines
pf/pf_internal.h        Cross-TU glue: PF_DIAG diagnostic macro, the
                        shared ReadState, and declarations of every
                        function that crosses a pf file boundary
pf/pf_parser_string.c   String/encoding spec parsing + reading (z / s / Z)
pf/pf_parser_bitfield.c Inline + typed bitfield parsing (B...)
pf/pf_parser_bitvec.c   Bitvector parsing + reading (v(N))
pf/pf_parser_array.c    Array-count resolution ([N] / [@name])
pf/pf_parser_struct.c   Nested struct / union reading (?)
pf/pf_parser_time.c     Timestamp wire-format decoders
                        (filetime, dos, hfs, oletime, webkit, cocoa, ...)
pf/pf_parser_time.h     Private API between pf_parser.c and pf_parser_time.c
pf/pf_parser_tlv.c      TLV (Tag-Length-Value) record parsing and dispatch
pf/pf_render.c          Shared render helpers (pf_field_matches,
                        pf_scalar_text, pf_render_guid) and the
                        rz_pf_render() mode dispatcher
pf/pf_render.h          Cross-TU glue for the renderers: the RenderCtx
                        record and shared-helper / per-mode declarations
pf/pf_render_text.c     Text + quiet renderers
pf/pf_render_json.c     JSON renderer (+ rz_pf_render_json entry point)
pf/pf_render_cstruct.c  C-struct renderer
pf/pf_render_dot.c      Graphviz DOT renderer
pf/pf_render_sd.c       RzStructuredData renderer (rz_pf_render_sd)

pf architecture

The pf format engine is a three-stage pipeline. Each stage is a public API entry point so callers can mix and match:

   parse                read                       render
  +--------+         +------------------+         +---------------------+
  | source |  -->    | format -> values |  -->    | values -> string    |
  | string |         | (RzPfFormat,     |         | (text / json /      |
  |        |         |  RzPfValue[])    |         |  cstruct / quiet /  |
  +--------+         +------------------+         |  dot / write)       |
                                                  +---------------------+
Stage Entry point Inputs Output
parse rz_pf_parse format string RzPfFormat *
read rz_pf_read format, buffer, base addr, ctx RzPfValue *
render rz_pf_render values, count, mode, opts char *
all-in-one rz_pf_format source + buf + ctx + mode + opts char *

parse

The parser is a single-pass recursive-descent walker over the format string. For each token it decides the field type and any per-field metadata (endianness, array count, encoding, timestamp format, GUID layout, TLV spec, etc.). The token shape is the source of truth -- there is no separate lexer.

Diagnostics are recorded both on the RzPfFormat::errors[] array (with positioned messages) and on RZ_LOG_WARN (for backward compatibility). The parser uses two file-scoped pointers (g_current_fmt, g_current_src) to let leaf helpers emit positioned diagnostics without threading a context pointer through every signature; these are set on entry and restored on exit so the parser is safe to call recursively from read_nested_struct.

TLV specs (V(t=...,l=...,e=...,h=...,d=...)) and TLV value reads live in pf_parser_tlv.c. The two TUs cross-call through a small set of internal helpers (pf_emit_error, pf_current_fmt, pf_current_src, pf_parse_tlv_spec, pf_tlv_read) declared extern in pf_parser_tlv.c and defined in pf_parser.c.

read

The reader walks the parsed format over a byte buffer and produces one RzPfValue per top-level field, with nested struct children attached. The read pass uses a per-instance ReadState:

typedef struct {
    int bit_cursor;             /* 0..7 for :N bit fields */
    const RzPfValue *siblings;  /* for [@name] lookups */
    int n_siblings;
} ReadState;

The bit cursor is scoped to a single top-level repetition and a single nested-struct instance. When a non-bit field is read while bit_cursor != 0, the cursor snaps to the next byte boundary; this is the "snap-flush" rule.

Bitvector fields (v(N)) intentionally bypass the bit cursor: each v(N) reads exactly ceil(N/8) whole bytes and unpacks them into N individual 0/1 scalars. They do not pack with neighbouring :N fields, and reading a v(N) field next to a :N field will flush any partially-consumed bit cursor first. The bit-order knob (lsb vs msb) governs the per-byte unpacking, not the byte order itself.

[@name] length-by-reference works by scanning siblings[0..n_siblings) for an earlier field with the matching name. Lookups never cross struct boundaries.

Pointer dereference is delegated to the caller via RzPfCtx::read_at so the type subsystem stays I/O-agnostic; the disasm and core printers forward to rz_io_nread_at.

render

Rendering is a separate pass over the RzPfValue[]. Most modes produce a string; WRITE is handled by the bridge in librz/core rather than rz_pf_render, and the structured-data renderer returns a tree rather than text (see below):

  • text -- <offset> <name> : [endian] <value> one line per field. Optionally colorised via RzPfRenderOpts::palette; the palette holds ANSI-escape strings indexed by role (offset / name / endian / hex_literal / label / reset).
  • json -- JSON array (via PJ), with nested struct children under "fields".
  • cstruct -- C struct[ <name>] { ... } mirror with decoded values in comments. The format name (when invoked as pfc <name>) follows the struct keyword; anonymous formats render as struct { ... }.
  • quiet -- value-only, no offsets or names.
  • dot -- Graphviz digraph record node with four column-aligned rows (offset / type-glyph / name / value), one column per visible field. The graph label is taken from RzPfRenderOpts::graph_label.
  • structured data -- not a string mode: rz_pf_render_sd() returns an RzStructuredData tree (the generic key/value document model shared with rz_bin, ASN.1 and PKCS#7), which the caller can serialise to JSON or YAML or walk with the generic iterator.

RzPfRenderOpts additionally holds an optional field_filter (skip fields whose name does not match) used by pf.<name>.<field> selectors.

The renderer dispatcher is rz_pf_render() in pf_render.c, which fans out to one translation unit per mode (pf_render_text.c, pf_render_json.c, pf_render_cstruct.c, pf_render_dot.c, pf_render_sd.c). pf_render.c keeps only the dispatcher and the helpers shared across modes (pf_field_matches, pf_scalar_text, pf_render_guid); pf_render.h declares those plus the shared RenderCtx record and the per-mode entry points.

Typedb integration

Named formats are stored in RzTypeDB::formats (HtSS). rz_pf_resolve_name is the canonical resolver and the only function inside the type subsystem that callers should use to look up a format by name. The TLV dispatch table uses a tlv.<name>.<hex-tag> key convention in the same hash.

When a format references a typename via ?(Name) / E (Name) f / B (Name) f, the reader consults typedb->types (for enums and bitfields) 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:

  • severity: RZ_PF_ERR_WARN (recoverable) or RZ_PF_ERR_ERROR (structural problem)
  • category: SYNTAX, SEMANTIC, RANGE, DEPRECATED, DATA, DEPTH
  • pos: 0-based column into the source format string
  • message: human-readable diagnostic

rz_pf_format_errors_to_string() renders the array with caret-position lines pointing at the offending column.

Reference

See doc/pf.md for the user-facing pf DSL reference.