diff --git a/fish-rust/build.rs b/fish-rust/build.rs index 76b14d351..fd2383b80 100644 --- a/fish-rust/build.rs +++ b/fish-rust/build.rs @@ -36,6 +36,7 @@ fn main() -> miette::Result<()> { "src/future_feature_flags.rs", "src/highlight.rs", "src/job_group.rs", + "src/null_terminated_array.rs", "src/parse_constants.rs", "src/parse_tree.rs", "src/parse_util.rs", diff --git a/fish-rust/src/null_terminated_array.rs b/fish-rust/src/null_terminated_array.rs index e44d58c0f..ee5a7d336 100644 --- a/fish-rust/src/null_terminated_array.rs +++ b/fish-rust/src/null_terminated_array.rs @@ -2,6 +2,7 @@ use std::ffi::{c_char, CStr, CString}; use std::marker::PhantomData; use std::pin::Pin; use std::ptr; +use std::sync::Arc; pub trait NulTerminatedString { type CharType: Copy; @@ -21,9 +22,6 @@ impl NulTerminatedString for CStr { /// This supports the null-terminated array of NUL-terminated strings consumed by exec. /// Given a list of strings, construct a vector of pointers to those strings contents. /// This is used for building null-terminated arrays of null-terminated strings. -/// *Important*: the vector stores pointers into the interior of the input strings, which may be -/// subject to the small-string optimization. This means that pointers will be left dangling if any -/// input string is deallocated *or moved*. This class should only be used in transient calls. pub struct NullTerminatedArray<'p, T: NulTerminatedString + ?Sized> { pointers: Vec<*const T::CharType>, _phantom: PhantomData<&'p T>, @@ -100,6 +98,53 @@ pub fn null_terminated_array_length(mut arr: *const *const T) -> usize { len } +/// FFI bits. +/// We often work in Arc. +/// Expose this to C++. +pub struct OwningNullTerminatedArrayRefFFI(pub Arc); +impl OwningNullTerminatedArrayRefFFI { + fn get(&self) -> *mut *const c_char { + self.0.get() + } +} + +unsafe impl cxx::ExternType for OwningNullTerminatedArrayRefFFI { + type Id = cxx::type_id!("OwningNullTerminatedArrayRefFFI"); + type Kind = cxx::kind::Opaque; +} + +/// Convert a CxxString to a CString, truncating at the first NUL. +use cxx::{CxxString, CxxVector}; +fn cxxstring_to_cstring(s: &CxxString) -> CString { + let bytes: &[u8] = s.as_bytes(); + let nul_pos = bytes.iter().position(|&b| b == 0); + let slice = &bytes[..nul_pos.unwrap_or(bytes.len())]; + CString::new(slice).unwrap() +} + +fn new_owning_null_terminated_array_ffi( + strs: &CxxVector, +) -> Box { + let cstrs = strs.iter().map(cxxstring_to_cstring).collect(); + Box::new(OwningNullTerminatedArrayRefFFI(Arc::new( + OwningNullTerminatedArray::new(cstrs), + ))) +} + +#[cxx::bridge] +mod null_terminated_array_ffi { + extern "Rust" { + type OwningNullTerminatedArrayRefFFI; + + fn get(&self) -> *mut *const c_char; + + #[cxx_name = "new_owning_null_terminated_array"] + fn new_owning_null_terminated_array_ffi( + strs: &CxxVector, + ) -> Box; + } +} + #[test] fn test_null_terminated_array_length() { let arr = [&1, &2, &3, std::ptr::null()]; diff --git a/src/env.h b/src/env.h index e1b1d5334..afb53e2e7 100644 --- a/src/env.h +++ b/src/env.h @@ -16,7 +16,7 @@ #include "cxx.h" #include "maybe.h" -class owning_null_terminated_array_t; +struct owning_null_terminated_array_t; extern size_t read_byte_limit; extern bool curses_initialized; diff --git a/src/null_terminated_array.cpp b/src/null_terminated_array.cpp index d119e7e4e..2107a6aa8 100644 --- a/src/null_terminated_array.cpp +++ b/src/null_terminated_array.cpp @@ -8,3 +8,12 @@ std::vector wide_string_list_to_narrow(const std::vector } return res; } + +const char **owning_null_terminated_array_t::get() { return impl_->get(); } + +owning_null_terminated_array_t::owning_null_terminated_array_t(std::vector &&strings) + : impl_(new_owning_null_terminated_array(strings)) {} + +owning_null_terminated_array_t::owning_null_terminated_array_t( + rust::Box impl) + : impl_(std::move(impl)) {} diff --git a/src/null_terminated_array.h b/src/null_terminated_array.h index 0d094bc93..71a17af24 100644 --- a/src/null_terminated_array.h +++ b/src/null_terminated_array.h @@ -10,6 +10,12 @@ #include #include "common.h" +#include "cxx.h" + +struct OwningNullTerminatedArrayRefFFI; +#if INCLUDE_RUST_HEADERS +#include "null_terminated_array.rs.h" +#endif /// This supports the null-terminated array of NUL-terminated strings consumed by exec. /// Given a list of strings, construct a vector of pointers to those strings contents. @@ -46,18 +52,19 @@ class null_terminated_array_t : noncopyable_t, nonmovable_t { /// This is useful for persisted null-terminated arrays, e.g. the exported environment variable /// list. This assumes char, since we don't need this for wchar_t. /// Note this class is not movable or copyable as it embeds a null_terminated_array_t. -class owning_null_terminated_array_t { +struct owning_null_terminated_array_t { public: // Access the null-terminated array of nul-terminated strings, appropriate for execv(). - const char **get() { return pointers_.get(); } + const char **get(); // Construct, taking ownership of a list of strings. - explicit owning_null_terminated_array_t(std::vector &&strings) - : strings_(std::move(strings)), pointers_(strings_) {} + explicit owning_null_terminated_array_t(std::vector &&strings); + + // Construct from the FFI side. + explicit owning_null_terminated_array_t(rust::Box impl); private: - const std::vector strings_; - null_terminated_array_t pointers_; + const rust::Box impl_; }; /// Helper to convert a list of wcstring to a list of std::string.