qcode/value/registry.rs
1use crate::{
2 assumption::{KnownContradiction, Proposition, Truth, Violation},
3 types::TypeId,
4 value::{
5 bytes::{Bytes, BytesId},
6 function::FunctionId,
7 interner::{Interner, LiteralInterner},
8 literal::{Literal, LiteralId},
9 varnode::{Varnode, VarnodeId},
10 },
11};
12// NOTE (IR-ownership refactor): instruction/block/param/edge *storage* lives in
13// each `FunctionBody` (see `FunctionBody::insns/blocks/params/edges`), and the function
14// bodies/interfaces now live directly on [`Context`](crate::context::Context)
15// (`bodies`/`interfaces`). This registry keeps only the global value arenas
16// (literals, bytes, varnodes) plus semantic cross-function data
17// (`synthetic_callees`, truths). The composite-id routing accessors that consult
18// the function bodies moved onto `Context` in the context-split reshape.
19use jstd::registry::Registry;
20use rustc_hash::FxHashMap as HashMap;
21use std::collections::BTreeSet;
22
23/// Central storage arena for all IR values in a [`Context`](crate::context::Context).
24///
25/// Each field is a typed arena ([`Registry`]) keyed by the corresponding ID
26/// type. Values are append-only: once pushed, their ID is stable for the
27/// lifetime of the registry and their data is never moved.
28///
29/// # Invariants
30///
31/// - **`push_insn` is final.** Instructions are immutable after insertion.
32/// The owning function's `users` map is populated at push time from the
33/// instruction's operands and is not updated if operands are later altered via
34/// interior mutation. Use
35/// `Context::replace_all_uses_with`
36/// to rewrite operands while keeping `users` consistent.
37///
38/// - **`users` is managed internally.** The reverse use-def map now lives in
39/// each [`FunctionBody`](crate::value::FunctionBody) (function-scoped; see
40/// [`FunctionBody::users_of`](crate::value::FunctionBody::users_of)). Do not mutate
41/// it directly. Read it through
42/// [`FunctionRef::users_of`](crate::value::FunctionRef::users_of) /
43/// [`Context::users`](crate::context::Context::users), and remove dead
44/// instructions via
45/// `Context::remove_instructions`.
46#[derive(Clone, Default, serde::Serialize, serde::Deserialize)]
47pub struct ValueRegistry<'str> {
48 /// Literal (constant) interner. Behind an `RwLock` (see
49 /// [`LiteralInterner`]) so constants can be minted through a shared `&`; the
50 /// dedup cache lives inside it.
51 pub literals: LiteralInterner,
52
53 /// Opaque byte-blob constant interner (constants wider than a `u64`).
54 #[serde(default)]
55 pub bytes: Interner<BytesId, Bytes>,
56
57 /// Poison-value interner (argpromote v2). Each poison is a *distinct*
58 /// interned value (never deduped), so two poisons are never congruent.
59 #[serde(default)]
60 pub poisons: Interner<crate::value::PoisonId, crate::value::Poison>,
61
62 /// User-forced rendering overrides for `Bytes` blobs (e.g. from the GUI
63 /// Strings pane). Absent entries render under [`BytesDisplay::Auto`].
64 #[serde(default)]
65 pub(crate) bytes_display: HashMap<BytesId, crate::value::BytesDisplay>,
66
67 /// Varnode storage.
68 pub varnodes: Registry<VarnodeId, Varnode<'str>>,
69
70 /// Per-varnode type overrides. A varnode is normally typed `Int(size)`; an
71 /// entry here gives it a richer *global* type instead (e.g. the `FS_OFFSET`
72 /// register typed `PtrTo<TEB>` by the TEB-seeding pass). Consulted by
73 /// [`Context::type_of`](crate::context::Context::type_of) /
74 /// [`stored_type_of`](crate::context::Context::stored_type_of). Set via
75 /// [`Context::set_varnode_type`](crate::context::Context::set_varnode_type).
76 #[serde(default)]
77 pub(crate) varnode_types: HashMap<VarnodeId, TypeId>,
78
79 /// Truth map of the assumption system: what each [`Proposition`] is
80 /// currently assumed or known to be (see [`crate::assumption`]). Accessed
81 /// through [`Context::assume_true`](crate::context::Context::assume_true)
82 /// and friends.
83 pub(crate) truths: HashMap<Proposition, Truth>,
84
85 /// Proven facts that contradicted an assumption this round; non-empty means
86 /// the checkpoint+replay driver must discard this working copy and replay.
87 pub(crate) violations: Vec<Violation>,
88
89 /// Proven facts that contradicted an existing *known* fact this round (e.g. a
90 /// user override the analysis disproved). A hard error for the driver, not a
91 /// replay signal. Transient per round, so not serialized.
92 #[serde(default, skip)]
93 pub(crate) known_contradictions: Vec<KnownContradiction>,
94
95 /// Synthetic forward call-graph edges that are not backed by a direct `Call`
96 /// instruction: `caller FunctionId → set of callee entry addresses`. Used for
97 /// relationships a pass recovers but the IR can't express as a direct call —
98 /// e.g. `entry → main`, where `main` is passed to `__libc_start_main` as a
99 /// pointer argument rather than called. Keyed by address so the edge resolves
100 /// once a function exists at the callee, independent of when it materializes.
101 /// Consumed by the analysis-owned derived call graph.
102 #[serde(default)]
103 pub(crate) synthetic_callees: HashMap<FunctionId, BTreeSet<u64>>,
104}
105
106impl<'str> ValueRegistry<'str> {
107 /// Returns a canonical [`LiteralId`] for the given typed constant.
108 ///
109 /// The value is masked to `type_id`'s size before lookup. Symbolic literals
110 /// (created via [`push_literal`](Self::push_literal)) are not included in
111 /// the intern cache and will not alias with constants produced here.
112 ///
113 /// Call [`Context::get_const`](crate::context::Context::get_const) for the common `Int(size)` case; use this
114 /// method directly when you need to preserve a non-`Int` type (e.g.
115 /// `StackAddress`) through folding.
116 pub fn get_or_make_typed_literal(&self, value: u64, type_id: TypeId, size: usize) -> LiteralId {
117 self.literals
118 .get_or_make_typed_literal(value, type_id, size)
119 }
120
121 /// Pushes a [`Literal`] with arbitrary fields (e.g. with a symbolic ref)
122 /// without interning. Use [`get_or_make_typed_literal`](Self::get_or_make_typed_literal)
123 /// for plain integer constants.
124 pub fn push_literal(&self, literal: Literal) -> LiteralId {
125 self.literals.push_literal(literal)
126 }
127
128 /// Records a synthetic forward call-graph edge `caller → callee_addr` (see
129 /// `synthetic_callees`). Returns `true` if the
130 /// edge was newly added, so callers can drive a fixpoint without spinning.
131 pub fn add_synthetic_callee(&mut self, caller: FunctionId, callee_addr: u64) -> bool {
132 self.synthetic_callees
133 .entry(caller)
134 .or_default()
135 .insert(callee_addr)
136 }
137
138 /// Returns the synthetic callee entry addresses recorded for `caller`.
139 pub fn synthetic_callees_of(&self, caller: FunctionId) -> impl Iterator<Item = u64> + '_ {
140 self.synthetic_callees
141 .get(&caller)
142 .into_iter()
143 .flatten()
144 .copied()
145 }
146
147 pub fn push_varnode(&mut self, varnode: Varnode<'str>) -> VarnodeId {
148 self.varnodes.push(varnode)
149 }
150
151 /// Mints a fresh typed poison value. Never deduped: each call yields a
152 /// distinct [`PoisonId`](crate::value::PoisonId) so GVN keeps every poison in
153 /// its own congruence class.
154 pub fn push_poison(&self, type_id: TypeId) -> crate::value::PoisonId {
155 self.poisons.push(crate::value::Poison { type_id })
156 }
157}