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(buffer): implement some static and prototype methods #869

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

nabetti1720
Copy link
Contributor

@nabetti1720 nabetti1720 commented Mar 5, 2025

Description of changes

This PR implements some methods that I need for my project.

Checklist

  • Created unit tests in tests/unit and/or in Rust for my feature if needed
  • Ran make fix to format JS and apply Clippy auto fixes
  • Made sure my code didn't add any additional warnings: make check
  • Added relevant type info in types/ directory
  • Updated documentation if needed (API.md/README.md/Other)

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@nabetti1720 nabetti1720 marked this pull request as ready for review March 7, 2025 08:36
Copy link
Collaborator

@richarddavison richarddavison left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! Some comments!

Comment on lines +422 to +462
fn get_write_parameters(args: &Rest<Value<'_>>, len: usize) -> Result<(usize, usize, String)> {
let encoding = "utf8".to_owned();

match (args.0.first(), args.0.get(1), args.0.get(2)) {
(Some(v1), _, _) if v1.as_string().is_some() => {
Ok((0, len, v1.as_string().unwrap().to_string()?))
},

(Some(v1), Some(v2), _) if v2.as_string().is_some() => {
let offset = v1.as_int().unwrap_or(0) as usize;
Ok((offset, len - offset, v2.as_string().unwrap().to_string()?))
},

(Some(v1), Some(v2), Some(v3)) => {
let offset = v1.as_int().unwrap_or(0) as usize;
Ok((
offset,
v2.as_int()
.map_or(len - offset, |l| (l as usize).min(len - offset)),
v3.as_string().map_or(encoding, |s| s.to_string().unwrap()),
))
},

(Some(v1), Some(v2), None) => {
let offset = v1.as_int().unwrap_or(0) as usize;
Ok((
offset,
v2.as_int()
.map_or(len - offset, |l| (l as usize).min(len - offset)),
encoding,
))
},

(Some(v1), None, None) => {
let offset = v1.as_int().unwrap_or(0) as usize;
Ok((offset, len - offset, encoding))
},

_ => Ok((0, len, encoding)),
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seams pretty complex. Can we simplify arg parsing? Maybe use Opt instead of Rest and pass each of the opts. This is done in many places, for instance see how we do with child_process arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I looked into the implementation of child_process but didn't find anything useful.

What makes parameter analysis difficult this time is that different types of encodings can appear for the first, second and third arguments.

From top to bottom, I expect the following patterns, which makes branching complicated:

  • encoding
  • offset, encoding
  • offset, length, encoding
  • offset, length
  • offset
  • none

It might be possible to implement this more trickily using if statements, but do you have any good ideas for a cleaner implementation?

Comment on lines +481 to +524
fn write_int32_be<'js>(
this: This<Object<'js>>,
ctx: Ctx<'js>,
value: i32,
offset: Opt<i32>,
) -> Result<usize> {
let source_slice = [
(value >> 24) as u8,
(value >> 16) as u8,
(value >> 8) as u8,
value as u8,
];
let offset = offset.0.unwrap_or_default();
let buf_length = this.0.len().try_into().unwrap_or(i32::MAX);

if offset < 0 || offset + source_slice.len() as i32 > buf_length {
return Err(Exception::throw_range(
&ctx,
"The specified offset is out of range",
));
}

let offset = offset as usize;

let target = ObjectBytes::from(&ctx, this.0.as_inner())?;

let mut writable_length = 0;

if let Some((array_buffer, _, _)) = target.get_array_buffer()? {
let raw = array_buffer
.as_raw()
.ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED)
.or_throw(&ctx)?;

let target_bytes = unsafe { slice::from_raw_parts_mut(raw.ptr.as_ptr(), raw.len) };

writable_length = offset + source_slice.len();

target_bytes[offset..writable_length].copy_from_slice(&source_slice);
}

Ok(writable_length)
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we make this a bit more generic to support all write methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I can do it. However, I won't have much time for a few days from today, so please be patient.

Comment on lines +145 to +153
fn maybeuninit_to_u8(vec: Vec<MaybeUninit<u8>>) -> Vec<u8> {
let len = vec.len();
let capacity = vec.capacity();
let ptr = vec.as_ptr() as *mut u8;

std::mem::forget(vec);

unsafe { Vec::from_raw_parts(ptr, len, capacity) }
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you elaborate what this does. Please add a safety comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would comments like this help you understand?

This conversion is safe because MaybeUninit has the same memory layout as u8, meaning the underlying bytes are identical. Since Vec<MaybeUninit> and Vec share the same memory representation, a simple reinterpretation of the pointer is valid.
Additionally, Vec::from_raw_parts correctly reconstructs the vector using the original length and capacity, ensuring that memory ownership remains consistent. The call to std::mem::forget(vec) prevents the original Vec<MaybeUninit> from being dropped, avoiding double frees or memory corruption.
However, this conversion is only safe if all elements of MaybeUninit are properly initialized. If any uninitialized values exist, reading them as u8 would lead to undefined behavior.

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.

2 participants