qcode/value/function/signature.rs
1use crate::value::VarnodeId;
2
3/// Per-parameter pointer attributes, LLVM-style, inferred (or read from a C
4/// prototype) and consumed at call sites to relax the default "every pointer
5/// argument aliases everything and is written through by the callee" assumption.
6///
7/// The two bits are deliberately independent: `readonly` proves only that the
8/// callee does not write through the pointer *during this call*, which is enough
9/// to stop a call from clobbering the cells the argument reaches (see
10/// `mem_forward`'s call-kill). It does *not* prove the pointer is safe to reason
11/// about across the call: a captured pointer can be written through later, so
12/// frame-freshness reasoning additionally requires `nocapture`. Both bits default
13/// to `false` (fully conservative); an inference/extern pass sets them.
14#[derive(Clone, Copy, Default, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
15pub struct ParamAttrs {
16 /// The callee never writes through this pointer parameter (C `*const`
17 /// semantics, one level deep: a store through a pointer *loaded from* the
18 /// param does not clear this — only stores whose address is affine-derived
19 /// from the param itself do).
20 #[serde(default)]
21 pub readonly: bool,
22 /// The callee does not retain this pointer beyond the call, except by
23 /// returning it (capture-by-return does not clear this: the returned pointer
24 /// is still tracked by the caller, so it is not an unbounded escape).
25 #[serde(default)]
26 pub nocapture: bool,
27}
28
29impl ParamAttrs {
30 /// The fully permissive attribute set (both bits): the optimistic starting
31 /// point of the bottom-up inference fixpoint, whittled down as the body walk
32 /// finds writes/captures/escapes.
33 pub const OPTIMISTIC: Self = Self {
34 readonly: true,
35 nocapture: true,
36 };
37}
38
39/// Where one external call argument is loaded from at the call site.
40///
41/// Planned once by `external_sigs` from the C prototype + calling convention;
42/// consumed by `argpromote_external`, which turns each slot into an SSA value at
43/// every direct caller (a register reload or an SP-relative stack load).
44#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
45pub enum ExternSlot {
46 /// A register argument: reload the register (`size` bytes) live at the call.
47 Reg(VarnodeId, usize),
48 /// A stack argument at `offset` bytes above the call-site stack pointer.
49 Stack { offset: i64, size: usize },
50}
51
52/// One planned positional argument of an external call.
53#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
54pub struct ExternArg {
55 /// Where the argument value is loaded from at the call site.
56 pub slot: ExternSlot,
57 /// The display name (the C prototype parameter name, or `return_address`
58 /// for the synthesized `stdcall`/`cdecl` slot). `None` leaves it unnamed.
59 #[serde(default)]
60 pub name: Option<Box<str>>,
61 /// Per-argument pointer attributes (chiefly `readonly` from a `const`
62 /// pointee). Defaults fully conservative for non-pointer arguments.
63 #[serde(default)]
64 pub attrs: ParamAttrs,
65}
66
67/// The memory kind of one prototyped-external parameter, as seen by the RAM
68/// argmem model. An external can only touch memory *we* model through pointers
69/// *we* pass it (its own libc-internal state lives outside the lifted image), so
70/// each pointer parameter bounds a whole-object effect on the caller's argument.
71///
72/// The variants are ordered so the analysis layer needs no C-type data of its
73/// own: it reads this per-input kind (kept in lockstep with the materialized
74/// register-interface inputs) plus the function-level variadic flag.
75#[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
76pub enum ArgMemKind {
77 /// A non-pointer scalar (integer/float): the external cannot reach any memory
78 /// we model through it. No effect.
79 NonPtr,
80 /// A mutable data pointer (`char *`, `void *`, `struct S *`): a whole-object
81 /// **read+write** effect on the addressed object — the callee may both read
82 /// the pre-call contents and clobber them (`strcat`'s dest, `realloc`). The
83 /// read half means a frame-local landing goes ⊤ (freshness), so such a
84 /// pointer is *not* memory-free-composable through an uninitialized local.
85 MutPtr,
86 /// A `const`-qualified data pointer (`const char *`): a whole-object read-only
87 /// effect on the addressed object.
88 ConstPtr,
89 /// A pointer the shallow whole-object model cannot bound: a function/callback
90 /// pointer (re-enters our code — `qsort`'s comparator), or a pointer to
91 /// another pointer / an unmodeled pointee (a transitive write escapes the
92 /// addressed object). Its presence sends the whole external footprint to ⊤.
93 Opaque,
94 /// A **write-only** destination pointer (`memset`/`memcpy` dest): the callee
95 /// never reads the pre-call contents, only clobbers them. A pure whole-object
96 /// *write*, so a frame-local landing is contained (the `memset(&local)` fold).
97 /// Minted only for symbols the `extern_argmem` write-only table vouches for
98 /// (libc semantics guarantee the destination is never read before write).
99 ///
100 /// Appended last on purpose: bincode encodes a fieldless enum by variant
101 /// **index**, so keeping `NonPtr`/`MutPtr`/`ConstPtr`/`Opaque` at their old
102 /// indices lets pre-`OutPtr` `.harbinger` snapshots still decode.
103 OutPtr,
104}
105
106/// C-prototype-derived argmem summary for a prototyped external: the ordered
107/// per-parameter [`ArgMemKind`]s (in lockstep with the materialized register
108/// interface inputs, so `params[i]` describes the `i`-th positional argument /
109/// `Param(i)`) and whether the callee is variadic. Read by the RAM effect
110/// channel's `external_leaf` to derive a bounded argmem footprint in place of ⊤.
111/// `None` on the signature for non-externals and un-prototyped externals.
112/// Serde-defaulted, so older `.harbinger` snapshots load with it absent.
113#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
114pub struct ExternArgmem {
115 /// Per-input parameter kinds, lockstep with the register-interface inputs.
116 pub params: Vec<ArgMemKind>,
117 /// Whether the prototype takes a trailing `...` (unknowable pointer args).
118 #[serde(default)]
119 pub variadic: bool,
120}
121
122/// C-prototype-derived call interface for an external (imported, bodyless)
123/// function, planned once by `external_sigs` and consumed by
124/// `argpromote_external`, which needs neither the binary nor `cabi` afterwards.
125#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
126pub struct ExternInterface {
127 /// The ordered call slots, one per positional argument.
128 pub args: Vec<ExternArg>,
129}
130
131/// Optional ABI description attached to a function.
132/// All fields are `Option` — only provided fields affect analysis.
133#[derive(Clone, Default, serde::Serialize, serde::Deserialize)]
134pub struct FunctionSignature {
135 /// `true` when this function performs an unresolved/dynamic memory access, or
136 /// forwards a stack-typed pointer into a callee that does. A caller that hands
137 /// a pointer into its own frame to such a function cannot bound which of its
138 /// stack slots the callee reads, so it must keep its whole frame in memory
139 /// (no stack promotion). See the stack-escape handling in `mem2reg`.
140 #[serde(default)]
141 pub reads_unbounded_stack: bool,
142 /// `true` when this function passes a pointer into its *own* stack frame to a
143 /// callee that may read it unboundedly (a callee with
144 /// [`reads_unbounded_stack`](Self::reads_unbounded_stack), an external, or an
145 /// indirect call). Such a callee may clobber any of this function's stack
146 /// slots, so its frame must stay in memory — `mem2reg` disables all stack
147 /// promotion for it. Computed at bind time (while `StackAddress` types are
148 /// still present) and seeded across checkpoint+replay rounds.
149 #[serde(default)]
150 pub frame_escapes_to_unbounded: bool,
151 /// `true` once this function's returned values are a deterministic function of
152 /// its by-value params, with no value flowing in from outside the SSA graph:
153 /// no loads (an untracked memory read), no calls, no architecture p-code ops,
154 /// and no raw register/global reads. Stores are permitted — they produce no
155 /// value, so they cannot feed a returned field. Strictly stronger than a
156 /// materialized register interface (`is_reg_materialized`), which only asserts the
157 /// register channel is functionalized. Pure-function emulation in constant propagation gates on
158 /// this (see `PURE_EMULATION_DESIGN.md`): such a callee may be emulated to
159 /// harvest constant return-tuple fields, with the call left in place. Asserted
160 /// by argpromote's `mark_pure`; checked by a `verify/` rule.
161 #[serde(default)]
162 pub is_pure: bool,
163 /// Per-parameter pointer attributes ([`readonly`](ParamAttrs::readonly) /
164 /// [`nocapture`](ParamAttrs::nocapture)), indexed like the positional call
165 /// arguments (`Call.args`) — which for a functionalized (`pure_reg`) callee
166 /// align with its root block params, and for an external align with the
167 /// materialized register interface.
168 ///
169 /// `None` means "not analyzed" (fully conservative — every pointer arg
170 /// escapes and is written through). A present vector may still be shorter than
171 /// the argument list; a missing entry is also treated conservatively. Set by
172 /// the extern C-prototype path ([`readonly`](ParamAttrs::readonly) only) and
173 /// the bottom-up `param_attrs` inference pass. Dropped (and re-inferred) when
174 /// argpromote/`dead_signature` rewrites the parameter list.
175 #[serde(default)]
176 pub param_attrs: Option<Vec<ParamAttrs>>,
177 /// C-prototype-derived call interface for an **external** callee: the ordered
178 /// argument slots (register or stack) and variadic flag, planned once by
179 /// `external_sigs`. `argpromote_external` reads this to rewrite call sites
180 /// without re-consulting `cabi` or the binary. `None` for non-externals and
181 /// externals with no known prototype. Serde-defaulted, so older `.harbinger`
182 /// snapshots load with it absent.
183 #[serde(default)]
184 pub extern_interface: Option<ExternInterface>,
185 /// C-prototype-derived argmem summary for a prototyped **external** callee:
186 /// the ordered per-input pointer kinds and variadic flag, planned once by
187 /// `external_sigs`. The RAM effect channel's `external_leaf` reads this to
188 /// bound the external's memory footprint through the pointers we pass it,
189 /// instead of treating every external as unbounded (⊤). `None` for
190 /// non-externals and externals with no known prototype. Serde-defaulted, so
191 /// older `.harbinger` snapshots load with it absent.
192 #[serde(default)]
193 pub argmem: Option<ExternArgmem>,
194}