Skip to content

Conversation

airblast-dev
Copy link
Contributor

There were a few methods where a null pointer was used or the string was not null terminated.

This fixes possible undefined behaviors. To avoid these issues in the future a few assertions were added into StringBuilder::finish to ensure that a String is never initialized with a null pointer and is always terminated with a null byte.

@airblast-dev airblast-dev marked this pull request as ready for review February 23, 2025 13:17
@airblast-dev
Copy link
Contributor Author

@MarcusGrass in case you want to check it out. :)

Copy link
Contributor

@MarcusGrass MarcusGrass left a comment

Choose a reason for hiding this comment

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

LGTM, nice solution to start by writing 0. Also good doc-comment add, tricky that you can't send, but can get nulls there!

E: would have made an approving commit but slipped up on the phone

Comment on lines +246 to +264
pub fn finish(mut self) -> String {
// when constructing the final string, the pointer it contains must always be non null and
// terminated with a null byte
if self.inner.data.is_null() {
// ensure we are in a valid state
assert!(self.inner.is_empty());
assert_eq!(self.cap, 0);

// The pointer of `String` should never be null, and it must be
// terminated by a null byte.
if s.data.is_null() {
unsafe {
let ptr = libc::malloc(1) as *mut i8;
if ptr.is_null() {
unable_to_alloc_memory();
}
ptr.write(0);

s.data = ptr;
}
}
// Create a null terminated empty string
self.inner = String::from_bytes(&[]);
} else {
debug_assert!(self.cap > self.inner.len());
assert!(self.cap > self.inner.len());
}

assert_eq!(
unsafe { *self.inner.data.add(self.inner.len()) },
0,
"StringBuilder should always return a null terminated string"
);
Copy link
Owner

Choose a reason for hiding this comment

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

These are all just sanity checks, right? If so, debug_assertions should be enough.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The assertions in the null check is just a sanity check so a debug_assert is fine there and can change it if its preferred.

The other assertions involve soundness so the assertion failing can result in UB.
For example assert!(self.cap > self.inner.len()); failing can result in UB as we might be passing bytes that are not initialized.

As for the other assertion, not being null terminated can cause UB depending on how the string is cloned inside neovim.

noib3

This comment was marked as off-topic.

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