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

Fix lifetimes and mutability of get_buf and get_buf_mut #649

Merged
merged 1 commit into from
Jul 19, 2023

Conversation

adrianparvino
Copy link
Contributor

Lifetimes should have been '_ and not static.
get_buf_mut created a mutable reference from an immutable reference.

SAFETY annotation has also been added.

@jannic
Copy link
Member

jannic commented Jul 16, 2023

This sounds reasonable, but I don't know enough about this code to evaluate the implications. @ithinuel, what do you think?

@nilclass
Copy link
Contributor

I'm curious, why did you remove get_buf_parts and duplicate it's code instead? Does it have any benefits?

Copy link
Member

@ithinuel ithinuel left a comment

Choose a reason for hiding this comment

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

I'd rather avoid code duplication and keep the unsafe fn get_buf_parts as this is orthogonal to the issue mentionned about lifetimes. I'm not against using a match statement vs an if statement. In this situation I don't think it will have a significant impact on the generated code.

Lifetimes should have been '_ and not static.

Based on my reading of lifetime elision, '_ is expands to fn get_buf_mut<'self>(&'self mut self) -> &'self mut [u8].
The reference targets somewhere in the DPRAM block which is a hardware element that lives for 'static.
So arguably, reusing the lifetime of 'self isn't more correct than using 'static.
A benefit of this would be that it'd prevent one from keeping that reference and continue using it after the Endpoint has be destroyed but it is not possible to do such a thing at present anyway.

get_buf_mut created a mutable reference from an immutable reference.

It is not. It is built from a *mut u8 pointer to a dedicated ram region and a length.
As far as I know, *const T as *mut T is UB but not *mut T as *const T.
If the concern comes from the use of offset, it is implemented for both *const T and *mut T (the latter being our usecase in get_buf_parts) so there's no hidden conversion there.

@jannic
Copy link
Member

jannic commented Jul 16, 2023

With the old signature, fn get_buf_mut(&self) -> &'static mut [u8], one could call the function multiple times and acquire multiple &mut references to the same memory location at the same time. That's instant UB as far as I know.

@ithinuel
Copy link
Member

Fair enough :)
Ohh, that's the non-mut reference OP's refering to!

@adrianparvino
Copy link
Contributor Author

adrianparvino commented Jul 17, 2023

Thank you for the replies.

I'm curious, why did you remove get_buf_parts and duplicate it's code instead? Does it have any benefits?

I'm currently unsure about the safety rules regarding temporarily mutable references and pointers, and unsafe fn get_buf_parts(&self) -> (*mut u8, usize) makes a *mut u8 from an &self, hence why I removed it. In hindsight, as previously mentioned, this is probably okay, as the "provenance" is from DPRAM_BASE and not self.

The reference targets somewhere in the DPRAM block which is a hardware element that lives for 'static.

In my perspective, ep_allocate acts just like a normal allocator. It takes a 'static memory(the heap in a hosted environment, USB_DPRAM in this example) and lends it to a value(Endpoint). That is, the Endpoint logically owns that region of memory until it gets dropped, hence why I consider their lifetimes intrinsically linked.

More pragmatically:

With the old signature, fn get_buf_mut(&self) -> &'static mut [u8], one could call the function multiple times and acquire multiple &mut references to the same memory location at the same time. That's instant UB as far as I know.

changing the type to be fn get_buf_mut(&mut self) -> &'static mut [u8] is not enough to establish the logical ownership, and it's still possible to call get_buf_mut twice. fn get_buf_mut(&mut self) -> &mut [u8] is the required signature to actually "lock" the memory.

@ithinuel ithinuel self-requested a review July 17, 2023 07:10
@ithinuel
Copy link
Member

Indeed. If you don't mind keeping the get_buf_parts function (to avoid code duplication) and simply change the get_buf and get_buf_mut signatures, that should be enough to fix the issue and I'd gladly approve the PR :)

@jannic
Copy link
Member

jannic commented Jul 17, 2023

Just as a side note: *mut don't have the same rules as &mut, you can have multiple of them as long as you don't deference them. That's one of the reasons pointer dereferencing is unsafe.

Lifetimes should have been `'_` and not static.
`get_buf_mut` created a mutable reference from an immutable reference.
@ithinuel ithinuel merged commit c8935b0 into rp-rs:main Jul 19, 2023
8 checks passed
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.

None yet

4 participants