Skip to content

Commit

Permalink
refactor(traverse): improve safety via type system
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Aug 27, 2024
1 parent 085ce20 commit d03628e
Show file tree
Hide file tree
Showing 4 changed files with 472 additions and 523 deletions.
4 changes: 2 additions & 2 deletions crates/oxc_traverse/scripts/lib/walk.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ function generateWalkForStruct(type, types) {
let tagCode = '', retagCode = '';
if (index === 0) {
tagCode = `
ctx.push_stack(
let pop_token = ctx.push_stack(
Ancestor::${type.name}${fieldCamelName}(
ancestor::${type.name}Without${fieldCamelName}(node, PhantomData)
)
Expand Down Expand Up @@ -182,7 +182,7 @@ function generateWalkForStruct(type, types) {
`;
});

if (visitedFields.length > 0) fieldsCodes.push('ctx.pop_stack();');
if (visitedFields.length > 0) fieldsCodes.push('ctx.pop_stack(pop_token);');

const typeSnakeName = camelToSnake(type.name);
return `
Expand Down
23 changes: 17 additions & 6 deletions crates/oxc_traverse/src/context/ancestry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,21 +105,25 @@ impl<'a> TraverseAncestry<'a> {
/// # SAFETY
/// This method must not be public outside this crate, or consumer could break safety invariants.
#[inline]
pub(crate) fn push_stack(&mut self, ancestor: Ancestor<'a, 'static>) {
pub(crate) fn push_stack(&mut self, ancestor: Ancestor<'a, 'static>) -> PopToken {
self.stack.push(ancestor);
PopToken(())
}

/// Pop last item off ancestry stack.
///
/// # SAFETY
/// * Stack must not be empty.
/// * Each `pop_stack` call must correspond to a `push_stack` call for same type.
///
/// This method must not be public outside this crate, or consumer could break safety invariants.
#[inline]
pub(crate) unsafe fn pop_stack(&mut self) {
#[allow(unused_variables, clippy::needless_pass_by_value)]
pub(crate) fn pop_stack(&mut self, token: PopToken) {
debug_assert!(!self.stack.is_empty());
self.stack.pop().unwrap_unchecked();
// SAFETY: `PopToken`s are only created in `push_stack`, so the fact that caller provides one
// guarantees that a push has happened. This method consumes the token which guarantees another
// pop hasn't occurred already corresponding to that push.
// Therefore the stack cannot by empty.
// The stack starts with 1 entry, so also it cannot be left empty after this pop.
unsafe { self.stack.pop().unwrap_unchecked() };
}

/// Retag last item on ancestry stack.
Expand Down Expand Up @@ -149,3 +153,10 @@ impl<'a> TraverseAncestry<'a> {
*(self.stack.last_mut().unwrap_unchecked() as *mut _ as *mut AncestorType) = ty;
}
}

/// Zero sized token which allows popping from stack. Used to ensure push and pop always correspond.
/// Inner field is private to this module so can only be created by methods in this file.
/// It is not `Clone` or `Copy`, so no way to obtain one except in this file.
/// Only method which generates a `PopToken` is `push_stack`, and `pop_stack` consumes one,
/// which guarantees you can't have more pops than pushes.
pub(crate) struct PopToken(());
9 changes: 5 additions & 4 deletions crates/oxc_traverse/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use oxc_syntax::{
use crate::ancestor::{Ancestor, AncestorType};
mod ancestry;
mod ast_operations;
use ancestry::PopToken;
pub use ancestry::TraverseAncestry;
mod scoping;
pub use scoping::TraverseScoping;
Expand Down Expand Up @@ -440,8 +441,8 @@ impl<'a> TraverseCtx<'a> {
/// # SAFETY
/// This method must not be public outside this crate, or consumer could break safety invariants.
#[inline]
pub(crate) fn push_stack(&mut self, ancestor: Ancestor<'a, 'static>) {
self.ancestry.push_stack(ancestor);
pub(crate) fn push_stack(&mut self, ancestor: Ancestor<'a, 'static>) -> PopToken {
self.ancestry.push_stack(ancestor)
}

/// Shortcut for `self.ancestry.pop_stack`, to make `walk_*` methods less verbose.
Expand All @@ -450,8 +451,8 @@ impl<'a> TraverseCtx<'a> {
/// See safety constraints of `TraverseAncestry.pop_stack`.
/// This method must not be public outside this crate, or consumer could break safety invariants.
#[inline]
pub(crate) unsafe fn pop_stack(&mut self) {
self.ancestry.pop_stack();
pub(crate) unsafe fn pop_stack(&mut self, token: PopToken) {
self.ancestry.pop_stack(token);
}

/// Shortcut for `self.ancestry.retag_stack`, to make `walk_*` methods less verbose.
Expand Down
Loading

0 comments on commit d03628e

Please sign in to comment.