Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 29 additions & 28 deletions crates/types/src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use alloc::borrow::Cow;
use alloc::string::String as StdString;
use core::str::{self, Utf8Error};
use core::{ffi, fmt, ptr, slice};
use core::{ffi, fmt, slice};
use std::num::NonZeroUsize;
use std::path::{Path, PathBuf};

Expand All @@ -20,6 +20,9 @@ use crate::NonOwning;
#[derive(Eq, Ord, PartialOrd)]
#[repr(C)]
pub struct String {
// NOTE: strings built by nvim-oxi can never contain a null pointer as they are intended as
// lua strings in neovim. however a string returned by neovim can have a null pointer so a null
// check should performed on access
pub(super) data: *mut ffi::c_char,
pub(super) len: usize,
}
Expand Down Expand Up @@ -110,7 +113,7 @@ impl String {
/// Creates a new, empty `String`.
#[inline]
pub fn new() -> Self {
Self { data: ptr::null_mut(), len: 0 }
Self::from_bytes(&[])
}

/// Makes a non-owning version of this `String`.
Expand Down Expand Up @@ -139,7 +142,7 @@ impl StringBuilder {
/// Create a new empty `StringBuilder`.
#[inline]
pub fn new() -> Self {
Self { inner: String::new(), cap: 0 }
Self { inner: String { data: core::ptr::null_mut(), len: 0 }, cap: 0 }
}

/// Push new bytes to the builder.
Expand Down Expand Up @@ -187,14 +190,14 @@ impl StringBuilder {
// return Self::new();
// }
let real_cap = cap + 1;
let ptr = unsafe { libc::malloc(real_cap) };
let ptr = unsafe { libc::malloc(real_cap) as *mut libc::c_char };
if ptr.is_null() {
unable_to_alloc_memory();
}
Self {
inner: String { len: 0, data: ptr as *mut ffi::c_char },
cap: real_cap,
}

unsafe { ptr.write(0) };

Self { inner: String { len: 0, data: ptr }, cap: real_cap }
}

/// Reserve space for `additional` more bytes.
Expand Down Expand Up @@ -240,32 +243,30 @@ impl StringBuilder {

/// Finish building the [`String`]
#[inline]
pub fn finish(self) -> String {
let mut s = String { data: self.inner.data, len: self.inner.len() };

if s.data.is_null() {
debug_assert!(s.is_empty());
debug_assert_eq!(self.cap, 0);
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"
);
Comment on lines +246 to +264
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.


let s = String { data: self.inner.data, len: self.inner.len() };
// Prevent self's destructor from being called.
std::mem::forget(self);

s
}

Expand Down