oxc_allocator/
address.rs

1// All methods are 1 instruction or less
2#![expect(clippy::inline_always)]
3
4use std::ptr::NonNull;
5
6use crate::Box;
7
8/// Memory address of an AST node in arena.
9//
10// At present, this is a `usize`, but it could be a `NonZeroUsize` instead, so that `Address` gains a niche,
11// which would reduce the size of `Option<Address>` from 16 bytes to 8 bytes.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[repr(transparent)]
14pub struct Address(usize);
15
16impl Address {
17    /// Dummy address.
18    ///
19    /// Never equal to any real `Address`, but is equal to itself.
20    pub const DUMMY: Self = Self(0);
21
22    /// Get the memory address of a pointer to an AST node in arena.
23    ///
24    /// **This method is an escape hatch only.**
25    /// Prefer using [`GetAddress::address`] or [`UnstableAddress::unstable_address`] instead,
26    /// because they are more likely to produce a stable [`Address`].
27    /// (Yes even `unstable_address` is more likely to produce a stable `Address` than this function!)
28    ///
29    /// If the AST node is in a [`Box`], the address is guaranteed to be a unique identifier
30    /// for the duration of the arena's existence.
31    ///
32    /// But if the node is in a [`Vec`], then the `Address` may not remain accurate if the `Vec`
33    /// is resized or has elements added or removed before this node.
34    ///
35    /// The pointer must point to an AST node in the arena (not on the stack),
36    /// or the returned `Address` will be meaningless.
37    ///
38    /// If called with a reference, the reference must point to an AST node in the arena (not on the stack),
39    /// or the returned `Address` will be meaningless. Be careful not to pass a double-reference to `from_ptr`,
40    /// or the resulting `Address` will point to the reference itself, instead of the thing being referenced.
41    ///
42    /// ```ignore
43    /// impl<'a> Visit<'a> for MyVisitor {
44    ///     fn visit_identifier_reference(&mut self, ident: &IdentifierReference<'a>) {
45    ///         // Correct - `address` is address of the `IdentifierReference`
46    ///         let address = Address::from_ptr(ident);
47    ///         // WRONG - `address` is address of `&IdentifierReference` reference itself, which is on the stack
48    ///         let address = Address::from_ptr(&ident);
49    ///     }
50    /// }
51    /// ```
52    ///
53    /// # SAFETY
54    ///
55    /// Pointer must be non-null.
56    ///
57    /// # Example
58    ///
59    /// Demonstration of the difference between `Address::from_ptr` and `GetAddress::address`:
60    ///
61    /// ```ignore
62    /// use oxc_allocator::{Address, GetAddress, Vec};
63    /// use oxc_span::SPAN;
64    ///
65    /// // Create a `Vec<Statement>` containing a single `BlockStatement`
66    /// let mut stmts = Vec::with_capacity_in(1, &allocator);
67    /// stmts.push(ast_builder.statement_block(SPAN, Vec::new_in(&allocator)));
68    ///
69    /// let block_address = stmts[0].address();
70    /// let stmt_address = Address::from_ptr(&stmts[0]);
71    ///
72    /// // Add another `Statement` to the `Vec`.
73    /// // This causes the `Vec` to grow and reallocate.
74    /// stmts.push(ast_builder.statement_empty(SPAN));
75    ///
76    /// let block_address_after_push = stmts[0].address();
77    /// let stmt_address_after_push = Address::from_ptr(&stmts[0]);
78    ///
79    /// // Address of the `BlockStatement` is unchanged
80    /// // (because the `Box`'s pointer still points to same memory location)
81    /// assert!(block_address_after_push == block_address);
82    /// // Address of the `Statement` has changed
83    /// // (because the `Vec` reallocated, so its contents have moved in memory)
84    /// assert!(stmt_address_after_push != stmt_address);
85    ///
86    /// // Insert a new `Statement` at start of the `Vec`.
87    /// // The `BlockStatement` is now at index 1.
88    /// stmts.insert(0, ast_builder.statement_empty(SPAN));
89    ///
90    /// let block_address_after_insert = stmts[1].address();
91    /// let stmt_address_after_insert = Address::from_ptr(&stmts[1]);
92    ///
93    /// // Address of the `BlockStatement` is still unchanged
94    /// assert!(block_address_after_insert == block_address_after_push);
95    /// // Address of the `Statement` has changed again
96    /// assert!(stmt_address_after_insert != stmt_address_after_push);
97    /// ```
98    ///
99    /// [`Box`]: crate::Box
100    /// [`Vec`]: crate::Vec
101    #[inline(always)] // Because it's a no-op
102    pub unsafe fn from_ptr<T>(p: *const T) -> Self {
103        Self(p as usize)
104    }
105}
106
107/// Trait for getting the memory address of an AST node.
108pub trait GetAddress {
109    /// Get the memory address of a value allocated in the arena.
110    fn address(&self) -> Address;
111}
112
113impl<T> GetAddress for Box<'_, T> {
114    /// Get the memory address of a value allocated in the arena.
115    ///
116    /// AST nodes in a `Box` in an arena are guaranteed to never move in memory,
117    /// so this address acts as a unique identifier for the duration of the arena's existence.
118    #[inline(always)] // Because it's only 1 instruction
119    fn address(&self) -> Address {
120        let ptr = Box::as_non_null(self);
121        Address(ptr.addr().get())
122    }
123}
124
125impl GetAddress for Address {
126    /// Address of an `Address` is itself.
127    #[inline(always)] // Because it's a no-op
128    fn address(&self) -> Address {
129        *self
130    }
131}
132
133/// Trait for getting the memory address of an AST node which is not necessarily stable.
134///
135/// See [`UnstableAddress::unstable_address`] for more details.
136///
137/// This trait is implemented for all AST struct types.
138///
139/// *DO NOT* implement this trait on any other type.
140pub trait UnstableAddress {
141    /// Get the memory [`Address`] of a reference to an AST node in arena, which is not necessarily stable.
142    ///
143    /// # Stable addresses
144    ///
145    /// It's ideal to obtain an `Address` for an AST node which is guaranteed to be stable for the life of the AST.
146    ///
147    /// Then you can reliably compare two `Address`es to determine if they refer to the same node,
148    /// without any risk of the result being wrong because one or other of the nodes has moved in memory.
149    ///
150    /// Types which have a stable address:
151    /// * [`Box<T>`]
152    /// * AST enums where all variants are `Box`es (e.g. `Statement`, `Expression`)
153    ///
154    /// Some other types guarantee stability for a shorter period of time:
155    /// * `oxc_ast::AstKind` - guaranteed stable address while `Semantic` is alive.
156    /// * `oxc_traverse::Ancestor` - guaranteed stable address while traversing descendents of the ancestor.
157    ///
158    /// The above types all implement [`GetAddress::address`]. If you have access to one of these types,
159    /// it's better to use `GetAddress::address` instead of this method.
160    ///
161    /// # Why this method exists
162    ///
163    /// Sometimes you only have access to a reference to an AST node, but you know from context that the node
164    /// will not move in memory during the time you need the `Address` to remain stable.
165    ///
166    /// You can use this method in such cases, but you need to be careful. For correct behavior, you must ensure
167    /// yourself that the node will not move in memory during the time you need the `Address` to remain accurate.
168    ///
169    /// When using this method, it is recommended to make a comment at the call site, explaining how you can prove
170    /// that the `Address` will remain accurate (the AST node will not move) for the time period that you need it to be.
171    ///
172    /// # When a type does not move in memory
173    ///
174    /// If the AST is immutable (e.g. in `Visit` trait), then any node in the AST is statically positioned in memory.
175    /// Therefore, in the linter, any reference to an AST node is guaranteed to have a stable `Address`.
176    ///
177    /// If an AST node is in `Vec`, then it'll remain at same memory address, but only as long as the `Vec` does
178    /// not reallocate (e.g. by `Vec::push`, `Vec::extend`).
179    ///
180    /// This method will return a stable `Address` for any AST node in such circumstances.
181    ///
182    /// # Common pitfalls
183    ///
184    /// ## References to AST nodes on the stack
185    ///
186    /// Ensure that the reference passed to this method is to a node which is in the arena, *not* on the stack.
187    ///
188    /// ```ignore
189    /// let binary_expr: BinaryExpression<'a> = get_owned_binary_expression_somehow();
190    /// // WRONG: `binary_expr` is on the stack, so the `Address` will be meaningless
191    /// let address = binary_expr.unstable_address();
192    /// // More correct: `binary_expr` is in the arena.
193    /// // Will have a stable `Address` as long as `vec` does not reallocate.
194    /// let mut vec = Vec::new_in(&allocator);
195    /// vec.push(binary_expr);
196    /// let address = vec[0].unstable_address();
197    /// ```
198    ///
199    /// ## AST nodes in `Vec`s
200    ///
201    /// ```ignore
202    /// let mut vec: &mut Vec<BinaryExpression<'a>> = get_vec_somehow();
203    ///
204    /// let address = vec[0].unstable_address();
205    /// vec.push(get_owned_binary_expression_somehow());
206    /// let address_after_push = vec[0].unstable_address();
207    ///
208    /// // This assertion may or may not pass, depending on whether `push` caused the `Vec` to reallocate.
209    /// // This depends on whether `vec` had spare capacity or not, prior to the `push` call.
210    /// assert!(address_after_push == address);
211    /// ```
212    ///
213    /// # Guardrails
214    ///
215    /// This method is less error-prone than [`Address::from_ptr`], because it provides a few guardrails:
216    ///
217    /// * [`UnstableAddress`] is only implemented on AST struct types, so you can't call it on a type which
218    ///   it doesn't make sense to get the `Address` of.
219    ///
220    /// * You don't need to worry about passing it a double-reference (`&&T`), because this method will automatically
221    ///   dereference as required.
222    ///
223    /// Even with these guardrails, usage of this method still requires care, for the reasons discussed above.
224    ///
225    /// [`Box<T>`]: crate::Box
226    #[inline(always)] // Because it's a no-op
227    fn unstable_address(&self) -> Address {
228        let p = NonNull::from_ref(self);
229        Address(p.addr().get())
230    }
231}