Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core, value): add as_bytes_mut() to array buffer and typed array #329

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

stephencroberts
Copy link
Contributor

Overview

There's currently not a way to modify the bytes of an ArrayBuffer or a typed array like Uint8Array, so this PR adds new as_bytes_mut() methods that follows the typical convention in Rust.

Comment on lines +115 to 118
pub fn as_bytes_mut(&self) -> Option<&mut [u8]> {
let raw = Self::get_raw(self.as_value())?;
Some(unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), raw.len) })
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unsound. It allows you to call as_bytes_mut twice on the same object resulting in aliasing mutable references which is undefined behavior. This also cannot be avoided by making the function take a mutable reference to self as you can have two separate ArrayBuffer values point to the same slice.

In order to make this sound, there needs to be some kind of RefCell/Mutex like lock to prevent aliasing mutable references.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @DelSkayn that's a great point. I'll take some time to rework it, unless there's already a way to manipulate the bytes directly without copying that I'm missing?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to chime in, this is a valid API even if unsafe.
I use it in llrt_modules to access the buffer.
The undefined behaviour is what is expected from some APIs like filehandle.write in node, see the discussion I had with node maintainers.
So I would still allow it but I would mark the function function as unsafe.

Copy link
Owner

@DelSkayn DelSkayn Jul 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is unsafe and also very prone to unsoundness, especially with a scripting language which might call functions in any order this function could quickly be used wrong. Even just creating an aliasing mutable reference to the same object is unsound, even if the mutable reference is never used.

I am fine with allowing having some unsafe way to access the bytes internally but it should then be via a raw pointer, not a mutable reference. Raw pointers force the user of the API to think about how such a buffer is accessed and makes it clear that mutable access, not properly coordinated, is unsound.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that sense the raw interface that is already present is good enough since I was able to use it for my usecase.

I am just not sure what the worst case scenario is really is if we do pub unsafe fn as_bytes_mut(&self) -> Option<&mut [u8]>. We know the slice will be be valid since the GC wont run until self is dropped at minimum.
Yes it can be unsound if you have two concurrent access to it (even though that seems to be the expectation for Node), that is why it is marked unsafe and we should add a proper documentation.

This is not a problem unique to rquickjs, deno is in the same boat here. They allow DerefMut from it, which is even worst.

Their safety comment is "interesting" to say the least.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if I update to mark as unsafe, do you think that would be acceptable @DelSkayn given @Sytten 's feedback? Or should I just close the MR? We can continue to maintain this code in our own fork of rquickjs if we need to.

@stephencroberts stephencroberts marked this pull request as draft July 15, 2024 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants