#[cfg(feature = "smartstring")]
mod smartstring_impl;
use std::{
borrow::Cow,
convert::TryFrom,
ffi::{CStr, CString},
fmt::Debug,
ptr, slice,
};
use parking_lot::{Mutex, const_mutex};
use crate::{
boxed::{ZBox, ZBoxable},
convert::{FromZval, IntoZval},
error::{Error, Result},
ffi::{
ext_php_rs_is_known_valid_utf8, ext_php_rs_set_known_valid_utf8,
ext_php_rs_zend_string_init, ext_php_rs_zend_string_release, zend_string,
zend_string_init_interned,
},
flags::DataType,
types::Zval,
};
pub type ZendStr = zend_string;
static INTERNED_LOCK: Mutex<()> = const_mutex(());
#[allow(clippy::len_without_is_empty)]
impl ZendStr {
pub fn new(str: impl AsRef<[u8]>, persistent: bool) -> ZBox<Self> {
let s = str.as_ref();
unsafe {
let ptr = ext_php_rs_zend_string_init(s.as_ptr().cast(), s.len(), persistent)
.as_mut()
.expect("Failed to allocate memory for new Zend string");
ZBox::from_raw(ptr)
}
}
#[must_use]
pub fn from_c_str(str: &CStr, persistent: bool) -> ZBox<Self> {
unsafe {
let ptr =
ext_php_rs_zend_string_init(str.as_ptr(), str.to_bytes().len() as _, persistent);
ZBox::from_raw(
ptr.as_mut()
.expect("Failed to allocate memory for new Zend string"),
)
}
}
pub fn new_interned(str: impl AsRef<[u8]>, persistent: bool) -> ZBox<Self> {
let _lock = INTERNED_LOCK.lock();
let s = str.as_ref();
unsafe {
let init = zend_string_init_interned.expect("`zend_string_init_interned` not ready");
let ptr = init(s.as_ptr().cast(), s.len() as _, persistent)
.as_mut()
.expect("Failed to allocate memory for new Zend string");
ZBox::from_raw(ptr)
}
}
pub fn interned_from_c_str(str: &CStr, persistent: bool) -> ZBox<Self> {
let _lock = INTERNED_LOCK.lock();
unsafe {
let init = zend_string_init_interned.expect("`zend_string_init_interned` not ready");
let ptr = init(str.as_ptr(), str.to_bytes().len() as _, persistent);
ZBox::from_raw(
ptr.as_mut()
.expect("Failed to allocate memory for new Zend string"),
)
}
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn as_c_str(&self) -> Result<&CStr> {
let bytes_with_null =
unsafe { slice::from_raw_parts(self.val.as_ptr().cast(), self.len() + 1) };
CStr::from_bytes_with_nul(bytes_with_null).map_err(|_| Error::InvalidCString)
}
#[inline]
pub fn as_str(&self) -> Result<&str> {
if unsafe { ext_php_rs_is_known_valid_utf8(self.as_ptr()) } {
let str = unsafe { std::str::from_utf8_unchecked(self.as_bytes()) };
return Ok(str);
}
let str = std::str::from_utf8(self.as_bytes()).map_err(|_| Error::InvalidUtf8)?;
unsafe { ext_php_rs_set_known_valid_utf8(self.as_ptr().cast_mut()) };
Ok(str)
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.val.as_ptr().cast(), self.len()) }
}
#[must_use]
pub fn as_ptr(&self) -> *const ZendStr {
ptr::from_ref(self)
}
pub fn as_mut_ptr(&mut self) -> *mut ZendStr {
ptr::from_mut(self)
}
}
unsafe impl ZBoxable for ZendStr {
fn free(&mut self) {
unsafe { ext_php_rs_zend_string_release(self) };
}
}
impl Debug for ZendStr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.as_str().fmt(f)
}
}
impl AsRef<[u8]> for ZendStr {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<T> PartialEq<T> for ZendStr
where
T: AsRef<[u8]>,
{
fn eq(&self, other: &T) -> bool {
self.as_ref() == other.as_ref()
}
}
impl ToOwned for ZendStr {
type Owned = ZBox<ZendStr>;
fn to_owned(&self) -> Self::Owned {
Self::new(self.as_bytes(), false)
}
}
impl<'a> TryFrom<&'a ZendStr> for &'a CStr {
type Error = Error;
fn try_from(value: &'a ZendStr) -> Result<Self> {
value.as_c_str()
}
}
impl<'a> TryFrom<&'a ZendStr> for &'a str {
type Error = Error;
fn try_from(value: &'a ZendStr) -> Result<Self> {
value.as_str()
}
}
impl TryFrom<&ZendStr> for String {
type Error = Error;
fn try_from(value: &ZendStr) -> Result<Self> {
value.as_str().map(ToString::to_string)
}
}
impl<'a> From<&'a ZendStr> for Cow<'a, ZendStr> {
fn from(value: &'a ZendStr) -> Self {
Cow::Borrowed(value)
}
}
impl From<&CStr> for ZBox<ZendStr> {
fn from(value: &CStr) -> Self {
ZendStr::from_c_str(value, false)
}
}
impl From<CString> for ZBox<ZendStr> {
fn from(value: CString) -> Self {
ZendStr::from_c_str(&value, false)
}
}
impl From<&str> for ZBox<ZendStr> {
fn from(value: &str) -> Self {
ZendStr::new(value.as_bytes(), false)
}
}
impl From<String> for ZBox<ZendStr> {
fn from(value: String) -> Self {
ZendStr::new(value.as_str(), false)
}
}
impl From<ZBox<ZendStr>> for Cow<'_, ZendStr> {
fn from(value: ZBox<ZendStr>) -> Self {
Cow::Owned(value)
}
}
impl From<Cow<'_, ZendStr>> for ZBox<ZendStr> {
fn from(value: Cow<'_, ZendStr>) -> Self {
value.into_owned()
}
}
macro_rules! try_into_zval_str {
($type: ty) => {
impl TryFrom<$type> for Zval {
type Error = Error;
fn try_from(value: $type) -> Result<Self> {
let mut zv = Self::new();
zv.set_string(&value, false)?;
Ok(zv)
}
}
impl IntoZval for $type {
const TYPE: DataType = DataType::String;
const NULLABLE: bool = false;
fn set_zval(self, zv: &mut Zval, persistent: bool) -> Result<()> {
zv.set_string(&self, persistent)
}
}
};
}
try_into_zval_str!(String);
try_into_zval_str!(&str);
try_from_zval!(String, string, String);
impl<'a> FromZval<'a> for &'a str {
const TYPE: DataType = DataType::String;
fn from_zval(zval: &'a Zval) -> Option<Self> {
zval.str()
}
}
#[cfg(test)]
#[cfg(feature = "embed")]
mod tests {
use crate::embed::Embed;
#[test]
fn test_string() {
Embed::run(|| {
let result = Embed::eval("'foo';");
assert!(result.is_ok());
let zval = result.as_ref().expect("Unreachable");
assert!(zval.is_string());
assert_eq!(zval.string(), Some("foo".to_string()));
});
}
#[test]
fn test_zend_string_init_fast() {
Embed::run(|| {
let cases: &[(&[u8], usize, bool)] = &[
(b"", 0, true),
(b"a", 1, true),
(b"x", 1, true),
(b"\0", 1, true),
(b"\xff", 1, true),
(b"hello", 5, false),
(b"ab", 2, false),
];
for &(input, expected_len, interned) in cases {
let s = crate::types::ZendStr::new(input, false);
assert_eq!(s.len(), expected_len, "len mismatch for {input:?}");
assert_eq!(s.as_bytes(), input, "content mismatch for {input:?}");
if interned {
let s2 = crate::types::ZendStr::new(input, false);
assert!(
std::ptr::eq(s.as_ptr(), s2.as_ptr()),
"expected interned pointer for {input:?}"
);
}
}
for c in b' '..=b'~' {
let s1 = crate::types::ZendStr::new(&[c][..], false);
let s2 = crate::types::ZendStr::new(&[c][..], false);
assert!(
std::ptr::eq(s1.as_ptr(), s2.as_ptr()),
"expected interned pointer for single char {c:#x}"
);
}
});
}
}