Skip to main content

luaur_analysis/methods/
txn_log_empty.rs

1use crate::records::txn_log::TxnLog;
2use alloc::boxed::Box;
3use alloc::vec::Vec;
4use luaur_common::records::dense_hash_map::DenseHashMap;
5use std::sync::OnceLock;
6
7/// Holds the process-lifetime empty `TxnLog` so it stays *reachable* from the
8/// static (LeakSanitizer traces it). `TxnLog` contains raw pointers and so isn't
9/// `Sync`; the empty log is immutable after construction and only ever read, so
10/// sharing it across threads is sound. The previous `OnceLock<usize>` stored the
11/// pointer as an integer (to dodge the `Sync` bound), which LSan cannot trace —
12/// reported as a leak by the fuzz suite.
13struct SyncTxnLog(Box<TxnLog>);
14// SAFETY: the empty log is never mutated after `get_or_init`; reads are safe to
15// share. (It is also never freed — a deliberate process-lifetime singleton.)
16// `OnceLock<T>: Sync` requires `T: Send + Sync`, so both are needed; `TxnLog`'s
17// raw pointers make neither automatic.
18unsafe impl Sync for SyncTxnLog {}
19unsafe impl Send for SyncTxnLog {}
20
21impl TxnLog {
22    pub fn empty() -> *const TxnLog {
23        static EMPTY_LOG: OnceLock<SyncTxnLog> = OnceLock::new();
24
25        let wrapper = EMPTY_LOG.get_or_init(|| {
26            let mut log = Box::new(TxnLog {
27                type_var_changes: DenseHashMap::new(core::ptr::null()),
28                type_pack_changes: DenseHashMap::new(core::ptr::null()),
29                parent: core::ptr::null_mut(),
30                owned_seen: Vec::new(),
31                shared_seen: core::ptr::null_mut(),
32                // Uses the inline `owned_seen` directly (see below), not a box.
33                owned_seen_box: None,
34                radioactive: false,
35            });
36
37            // Self-referential: `shared_seen` points at `owned_seen` inside the
38            // box. Boxing pins the pointee's address, and moving the `Box` into
39            // the `OnceLock` moves only the pointer, so this stays valid.
40            log.shared_seen = &mut log.owned_seen;
41            SyncTxnLog(log)
42        });
43        wrapper.0.as_ref() as *const TxnLog
44    }
45}