Skip to content

AstBuilder::copy is unsound #3483

@overlookmotel

Description

@overlookmotel

pub fn copy<T>(&self, src: &T) -> T {
// SAFETY:
// This should be safe as long as `src` is an reference from the allocator.
// But honestly, I'm not really sure if this is safe.
#[allow(unsafe_code)]
unsafe {
std::mem::transmute_copy(src)
}
}

This method can be used correctly when a node is being removed from AST anyway, but there are multiple ways in which it can lead to unsound situations:

Aliasing violation:

fn do_bad_stuff(stmt: &Statement, ast: &AstBuilder) {
  if let Statement::BlockStatement(block) = stmt {
    let box1 = ast.copy(block);
    let box2 = ast.copy(block);
    // We now have two `Box<BlockStatement>` pointing to same `BlockStatement`
  }
}

Mutation through immutable ref:

fn do_bad_stuff2(stmt: &Statement, ast: &AstBuilder) {
  if let Statement::BlockStatement(block) = stmt {
    let block = ast.copy(block);
    // We can mutate, even though we only got an immutable ref
    block.body.clear();
  }
}

The double-free which #3481 fixes was likely caused by using copy.

We could make the method unsafe and caller will have to ensure it's only used when safe to do so. However, personally I think we should remove the API entirely because (1) it is difficult to reason about when it's safe to use and is therefore a footgun and (2) the performance hit from using safe alternatives is very minimal.

Unfortunately the alternatives are likely take or mem::replace which are not so ergonomic.

Metadata

Metadata

Labels

C-bugCategory - BugP-highPriority - High

Type

No type

Priority

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions