Skip to main content

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}