Skip to main content

weir/
def_use.rs

1//! DF-11  -  Def-use chain query over a constructed SSA form.
2//!
3//! For a given SSA-renamed definition site (say `%tmp.3 = …`),
4//! return the bitset of nodes that USE that definition. This is the
5//! canonical "what reads this def" query that CodeQL exposes as
6//! `DataFlow::Node::getUses()` and that every taint rule reaches for
7//! when answering "did the value defined here reach a sink without
8//! being overwritten."
9//!
10//! Composes:
11//!   * [`super::ssa::SsaForm::def_use_chains`]  -  the per-SSA-version
12//!     use list built by Cytron + variable renaming during SSA
13//!     construction. We index it by the SSA version of the supplied
14//!     definition site and emit a bitset over node ids.
15//!
16//! Soundness: [`Exact`](super::soundness::Soundness::Exact). The
17//! def-use chain is computed by SSA construction, which is sound by
18//! construction; this primitive is a pure query against that
19//! pre-built table.
20
21use super::ssa::SsaForm;
22use vyre::ir::Program;
23use vyre_primitives::bitset::and::bitset_and;
24use vyre_primitives::graph::csr_forward_traverse::bitset_words;
25
26pub(crate) const OP_ID: &str = "weir::def_use";
27
28/// Look up uses of an SSA definition version. Returns a sorted dense
29/// vector of `node_id` values for every use of `def_version`.
30///
31/// Missing definition versions are rejected because treating an
32/// absent SSA definition as "known but unused" creates silent
33/// false-negatives in taint and def-use queries.
34pub fn try_def_use_chain(form: &SsaForm, def_version: u32) -> Result<Vec<u32>, String> {
35    let Some(recorded_uses) = form.def_use_chains.get(&def_version) else {
36        return Err(format!(
37            "unknown SSA definition version {def_version}. Fix: query only versions emitted by SsaForm::def_use_chains; an absent definition is not an empty use-set."
38        ));
39    };
40    let mut uses = recorded_uses.clone();
41    uses.sort_unstable();
42    uses.dedup();
43    Ok(uses)
44}
45
46/// Look up uses of an SSA definition version.
47///
48/// Panics with an actionable diagnostic when `def_version` is absent.
49/// Use [`try_def_use_chain`] when caller-controlled versions must be
50/// reported as recoverable input errors.
51#[must_use]
52#[cfg(any(test, feature = "legacy-infallible"))]
53pub fn def_use_chain(form: &SsaForm, def_version: u32) -> Vec<u32> {
54    try_def_use_chain(form, def_version)
55        .expect("weir::def_use_chain received an unknown SSA definition version")
56}
57
58/// Pack the use-set for `def_version` into a bitset over `node_count`.
59/// Each bit `i` is `1` iff node `i` is recorded as a use of the
60/// supplied definition version. Bits beyond `node_count` are zero.
61pub fn try_def_use_chain_bitset(
62    form: &SsaForm,
63    def_version: u32,
64    node_count: u32,
65) -> Result<Vec<u32>, String> {
66    let uses = try_def_use_chain(form, def_version)?;
67    let words = crate::dispatch_decode::bitset_word_capacity("def_use", node_count)?;
68    let mut bits = vec![0u32; words];
69    for use_id in uses {
70        if use_id < node_count {
71            let w = crate::dispatch_decode::u32_to_usize(use_id / 32, "def_use bitset word")?;
72            let b = use_id % 32;
73            bits[w] |= 1u32 << b;
74        }
75    }
76    Ok(bits)
77}
78
79/// Pack the use-set for `def_version` into a bitset over `node_count`.
80///
81/// Panics with an actionable diagnostic when `def_version` is absent.
82/// Use [`try_def_use_chain_bitset`] for caller-controlled query ids.
83#[must_use]
84#[cfg(any(test, feature = "legacy-infallible"))]
85pub fn def_use_chain_bitset(form: &SsaForm, def_version: u32, node_count: u32) -> Vec<u32> {
86    try_def_use_chain_bitset(form, def_version, node_count)
87        .expect("weir::def_use_chain_bitset received an unknown SSA definition version")
88}
89
90/// Build a def-use query Program over a precomputed use bitset.
91///
92/// `def_use_buf` is the bitset produced by [`def_use_chain_bitset`]
93/// or an equivalent frontend/importer fact buffer. `query_set` is the
94/// set of nodes being queried. The output bitset contains nodes that
95/// both read the selected SSA definition and are in the query set.
96#[must_use]
97pub fn def_use_query(node_count: u32, def_use_buf: &str, query_set: &str, out: &str) -> Program {
98    let words = bitset_words(node_count);
99    vyre_harness::region::tag_program(OP_ID, bitset_and(def_use_buf, query_set, out, words))
100}
101
102/// reference oracle for [`def_use_query`].
103#[must_use]
104#[cfg(any(test, feature = "cpu-parity"))]
105#[deprecated(
106    note = "reference oracle only; production code must dispatch the Weir Program on a concrete GPU backend or use weir::oracle for parity evidence"
107)]
108pub(crate) fn cpu_ref(def_use_buf: &[u32], query_set: &[u32]) -> Vec<u32> {
109    vyre_primitives::bitset::and::cpu_ref(def_use_buf, query_set)
110}
111
112/// Marker type for the def-use dataflow primitive.
113#[derive(Clone, Copy, Debug, PartialEq, Eq)]
114pub struct DefUse;
115
116impl DefUse {
117    /// Stable operation id for the def-use primitive.
118    #[must_use]
119    pub const fn op_id(&self) -> &'static str {
120        OP_ID
121    }
122}
123
124impl super::soundness::SoundnessTagged for DefUse {
125    fn soundness(&self) -> super::soundness::Soundness {
126        super::soundness::Soundness::Exact
127    }
128}
129
130inventory::submit! {
131    vyre_harness::OpEntry::new(
132        OP_ID,
133        || def_use_query(4, "def_use", "query", "out"),
134        Some(|| {
135            let u32s = crate::dispatch_decode::pack_u32;
136            vec![vec![u32s(&[0b1110]), u32s(&[0b1010]), u32s(&[0])]]
137        }),
138        Some(|| {
139            let u32s = crate::dispatch_decode::pack_u32;
140            vec![vec![u32s(&[0b1010])]]
141        }),
142    )
143}
144
145#[cfg(test)]
146#[allow(deprecated)]
147mod tests {
148    use super::*;
149    use std::collections::HashMap;
150
151    fn form_with(def_use: &[(u32, &[u32])]) -> SsaForm {
152        let mut chains = HashMap::new();
153        for (def, uses) in def_use {
154            chains.insert(*def, uses.to_vec());
155        }
156        SsaForm {
157            phi_nodes: HashMap::new(),
158            renamed_usages: HashMap::new(),
159            def_use_chains: chains,
160        }
161    }
162
163    #[test]
164    fn known_definition_returns_sorted_dedup_uses() {
165        let form = form_with(&[(7, &[5, 3, 5, 9, 3])]);
166        assert_eq!(def_use_chain(&form, 7), vec![3, 5, 9]);
167    }
168
169    #[test]
170    fn known_definition_with_no_uses_remains_empty() {
171        let form = form_with(&[(7, &[])]);
172        assert_eq!(
173            try_def_use_chain(&form, 7).expect("known empty definition"),
174            Vec::<u32>::new()
175        );
176    }
177
178    #[test]
179    fn unknown_definition_is_rejected_not_treated_as_empty() {
180        let form = form_with(&[(7, &[3, 5])]);
181        let error = try_def_use_chain(&form, 99).expect_err("unknown definition must fail");
182        assert!(
183            error.contains("unknown SSA definition version 99"),
184            "{error}"
185        );
186    }
187
188    #[test]
189    fn bitset_packs_use_ids() {
190        let form = form_with(&[(7, &[1, 33, 65])]);
191        let bits = def_use_chain_bitset(&form, 7, 96);
192        // Bit 1 in word 0; bit 1 in word 1 (offset 33-32); bit 1 in word 2 (65-64).
193        assert_eq!(bits, vec![1u32 << 1, 1u32 << 1, 1u32 << 1]);
194    }
195
196    #[test]
197    fn bitset_skips_uses_beyond_node_count() {
198        let form = form_with(&[(7, &[3, 200])]);
199        let bits = def_use_chain_bitset(&form, 7, 64);
200        // Only bit 3 in word 0 is set; the 200 use exceeds node_count.
201        assert_eq!(bits, vec![1u32 << 3, 0]);
202    }
203
204    #[test]
205    fn empty_form_rejects_unknown_definition() {
206        let form = form_with(&[]);
207        let err = try_def_use_chain_bitset(&form, 7, 64)
208            .expect_err("unknown definition in empty form must fail loudly");
209        assert!(
210            err.contains("unknown SSA definition version 7"),
211            "unexpected diagnostic: {err}"
212        );
213    }
214
215    #[test]
216    fn def_use_query_cpu_ref_intersects_query() {
217        assert_eq!(cpu_ref(&[0b1110], &[0b1010]), vec![0b1010]);
218    }
219}