Skip to content

Commit f57b916

Browse files
committed
Add support for empty immutable shared arrays
Why: - Since PHP 7.3, it's possible for extensions to create zvals backed by an immutable shared hashtable via the ZVAL_EMPTY_ARRAY macro. - This helps avoid redundant hashtable allocations when returning empty arrays back to userland PHP code, and could likewise be beneficial for Rust extensions too. What: - Add ZendHashTable::new_empty_immutable() to obtain a ZendHashTable that is actually an empty immutable shared hashtable. - Add ZendHashTable::is_immutable(). Use it to avoid attempting to free the immutable shared hashtable on drop, and to set appropriate type flags when initializing a zval with a ZendHashTable. - Make ZendHashTable's TryFrom implementations from Vec and HashMap return an empty immutable shared hashtable if the input collection was empty. Although this would allow every user to automatically benefit from this optimization, I'm not convinced about this part because this is technically a breaking change for consumers that construct ZendHashTables via these converters in their Rust code. Maybe it'd be better to not change the converters and let projects explicitly use ::new_empty_immutable if they deem the optimization would be useful.
1 parent 464407b commit f57b916

File tree

7 files changed

+205
-64
lines changed

7 files changed

+205
-64
lines changed

allowed_bindings.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ bind! {
8181
zend_declare_class_constant,
8282
zend_declare_property,
8383
zend_do_implement_interface,
84+
zend_empty_array,
8485
zend_execute_data,
8586
zend_function_entry,
8687
zend_hash_clean,
@@ -137,6 +138,9 @@ bind! {
137138
E_RECOVERABLE_ERROR,
138139
E_DEPRECATED,
139140
E_USER_DEPRECATED,
141+
GC_IMMUTABLE,
142+
GC_FLAGS_MASK,
143+
GC_FLAGS_SHIFT,
140144
HT_MIN_SIZE,
141145
IS_ARRAY,
142146
IS_ARRAY_EX,

docsrs_bindings.rs

Lines changed: 64 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ where
8282
}
8383
pub const ZEND_DEBUG: u32 = 1;
8484
pub const _ZEND_TYPE_NAME_BIT: u32 = 16777216;
85+
pub const _ZEND_TYPE_LITERAL_NAME_BIT: u32 = 8388608;
8586
pub const _ZEND_TYPE_NULLABLE_BIT: u32 = 2;
8687
pub const HT_MIN_SIZE: u32 = 8;
8788
pub const IS_UNDEF: u32 = 0;
@@ -104,6 +105,9 @@ pub const IS_INDIRECT: u32 = 12;
104105
pub const IS_PTR: u32 = 13;
105106
pub const _IS_BOOL: u32 = 18;
106107
pub const Z_TYPE_FLAGS_SHIFT: u32 = 8;
108+
pub const GC_FLAGS_MASK: u32 = 1008;
109+
pub const GC_FLAGS_SHIFT: u32 = 0;
110+
pub const GC_IMMUTABLE: u32 = 64;
107111
pub const IS_TYPE_REFCOUNTED: u32 = 1;
108112
pub const IS_TYPE_COLLECTABLE: u32 = 2;
109113
pub const IS_INTERNED_STRING_EX: u32 = 6;
@@ -269,7 +273,7 @@ pub struct _IO_FILE {
269273
pub _wide_data: *mut _IO_wide_data,
270274
pub _freeres_list: *mut _IO_FILE,
271275
pub _freeres_buf: *mut ::std::os::raw::c_void,
272-
pub __pad5: usize,
276+
pub _prevchain: *mut *mut _IO_FILE,
273277
pub _mode: ::std::os::raw::c_int,
274278
pub _unused2: [::std::os::raw::c_char; 20usize],
275279
}
@@ -537,6 +541,9 @@ pub type zend_string_init_interned_func_t = ::std::option::Option<
537541
extern "C" {
538542
pub static mut zend_string_init_interned: zend_string_init_interned_func_t;
539543
}
544+
extern "C" {
545+
pub static zend_empty_array: HashTable;
546+
}
540547
extern "C" {
541548
pub fn zend_hash_clean(ht: *mut HashTable);
542549
}
@@ -1992,7 +1999,7 @@ pub struct _php_stream {
19921999
pub wrapperthis: *mut ::std::os::raw::c_void,
19932000
pub wrapperdata: zval,
19942001
pub _bitfield_align_1: [u8; 0],
1995-
pub _bitfield_1: __BindgenBitfieldUnit<[u8; 1usize]>,
2002+
pub _bitfield_1: __BindgenBitfieldUnit<[u8; 2usize]>,
19962003
pub mode: [::std::os::raw::c_char; 16usize],
19972004
pub flags: u32,
19982005
pub res: *mut zend_resource,
@@ -2011,105 +2018,122 @@ pub struct _php_stream {
20112018
}
20122019
impl _php_stream {
20132020
#[inline]
2014-
pub fn is_persistent(&self) -> u8 {
2015-
unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u8) }
2021+
pub fn is_persistent(&self) -> u16 {
2022+
unsafe { ::std::mem::transmute(self._bitfield_1.get(0usize, 1u8) as u16) }
20162023
}
20172024
#[inline]
2018-
pub fn set_is_persistent(&mut self, val: u8) {
2025+
pub fn set_is_persistent(&mut self, val: u16) {
20192026
unsafe {
2020-
let val: u8 = ::std::mem::transmute(val);
2027+
let val: u16 = ::std::mem::transmute(val);
20212028
self._bitfield_1.set(0usize, 1u8, val as u64)
20222029
}
20232030
}
20242031
#[inline]
2025-
pub fn in_free(&self) -> u8 {
2026-
unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 2u8) as u8) }
2032+
pub fn in_free(&self) -> u16 {
2033+
unsafe { ::std::mem::transmute(self._bitfield_1.get(1usize, 2u8) as u16) }
20272034
}
20282035
#[inline]
2029-
pub fn set_in_free(&mut self, val: u8) {
2036+
pub fn set_in_free(&mut self, val: u16) {
20302037
unsafe {
2031-
let val: u8 = ::std::mem::transmute(val);
2038+
let val: u16 = ::std::mem::transmute(val);
20322039
self._bitfield_1.set(1usize, 2u8, val as u64)
20332040
}
20342041
}
20352042
#[inline]
2036-
pub fn eof(&self) -> u8 {
2037-
unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u8) }
2043+
pub fn eof(&self) -> u16 {
2044+
unsafe { ::std::mem::transmute(self._bitfield_1.get(3usize, 1u8) as u16) }
20382045
}
20392046
#[inline]
2040-
pub fn set_eof(&mut self, val: u8) {
2047+
pub fn set_eof(&mut self, val: u16) {
20412048
unsafe {
2042-
let val: u8 = ::std::mem::transmute(val);
2049+
let val: u16 = ::std::mem::transmute(val);
20432050
self._bitfield_1.set(3usize, 1u8, val as u64)
20442051
}
20452052
}
20462053
#[inline]
2047-
pub fn __exposed(&self) -> u8 {
2048-
unsafe { ::std::mem::transmute(self._bitfield_1.get(4usize, 1u8) as u8) }
2054+
pub fn __exposed(&self) -> u16 {
2055+
unsafe { ::std::mem::transmute(self._bitfield_1.get(4usize, 1u8) as u16) }
20492056
}
20502057
#[inline]
2051-
pub fn set___exposed(&mut self, val: u8) {
2058+
pub fn set___exposed(&mut self, val: u16) {
20522059
unsafe {
2053-
let val: u8 = ::std::mem::transmute(val);
2060+
let val: u16 = ::std::mem::transmute(val);
20542061
self._bitfield_1.set(4usize, 1u8, val as u64)
20552062
}
20562063
}
20572064
#[inline]
2058-
pub fn fclose_stdiocast(&self) -> u8 {
2059-
unsafe { ::std::mem::transmute(self._bitfield_1.get(5usize, 2u8) as u8) }
2065+
pub fn fclose_stdiocast(&self) -> u16 {
2066+
unsafe { ::std::mem::transmute(self._bitfield_1.get(5usize, 2u8) as u16) }
20602067
}
20612068
#[inline]
2062-
pub fn set_fclose_stdiocast(&mut self, val: u8) {
2069+
pub fn set_fclose_stdiocast(&mut self, val: u16) {
20632070
unsafe {
2064-
let val: u8 = ::std::mem::transmute(val);
2071+
let val: u16 = ::std::mem::transmute(val);
20652072
self._bitfield_1.set(5usize, 2u8, val as u64)
20662073
}
20672074
}
20682075
#[inline]
2069-
pub fn has_buffered_data(&self) -> u8 {
2070-
unsafe { ::std::mem::transmute(self._bitfield_1.get(7usize, 1u8) as u8) }
2076+
pub fn has_buffered_data(&self) -> u16 {
2077+
unsafe { ::std::mem::transmute(self._bitfield_1.get(7usize, 1u8) as u16) }
20712078
}
20722079
#[inline]
2073-
pub fn set_has_buffered_data(&mut self, val: u8) {
2080+
pub fn set_has_buffered_data(&mut self, val: u16) {
20742081
unsafe {
2075-
let val: u8 = ::std::mem::transmute(val);
2082+
let val: u16 = ::std::mem::transmute(val);
20762083
self._bitfield_1.set(7usize, 1u8, val as u64)
20772084
}
20782085
}
20792086
#[inline]
2087+
pub fn fclose_stdiocast_flush_in_progress(&self) -> u16 {
2088+
unsafe { ::std::mem::transmute(self._bitfield_1.get(8usize, 1u8) as u16) }
2089+
}
2090+
#[inline]
2091+
pub fn set_fclose_stdiocast_flush_in_progress(&mut self, val: u16) {
2092+
unsafe {
2093+
let val: u16 = ::std::mem::transmute(val);
2094+
self._bitfield_1.set(8usize, 1u8, val as u64)
2095+
}
2096+
}
2097+
#[inline]
20802098
pub fn new_bitfield_1(
2081-
is_persistent: u8,
2082-
in_free: u8,
2083-
eof: u8,
2084-
__exposed: u8,
2085-
fclose_stdiocast: u8,
2086-
has_buffered_data: u8,
2087-
) -> __BindgenBitfieldUnit<[u8; 1usize]> {
2088-
let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 1usize]> = Default::default();
2099+
is_persistent: u16,
2100+
in_free: u16,
2101+
eof: u16,
2102+
__exposed: u16,
2103+
fclose_stdiocast: u16,
2104+
has_buffered_data: u16,
2105+
fclose_stdiocast_flush_in_progress: u16,
2106+
) -> __BindgenBitfieldUnit<[u8; 2usize]> {
2107+
let mut __bindgen_bitfield_unit: __BindgenBitfieldUnit<[u8; 2usize]> = Default::default();
20892108
__bindgen_bitfield_unit.set(0usize, 1u8, {
2090-
let is_persistent: u8 = unsafe { ::std::mem::transmute(is_persistent) };
2109+
let is_persistent: u16 = unsafe { ::std::mem::transmute(is_persistent) };
20912110
is_persistent as u64
20922111
});
20932112
__bindgen_bitfield_unit.set(1usize, 2u8, {
2094-
let in_free: u8 = unsafe { ::std::mem::transmute(in_free) };
2113+
let in_free: u16 = unsafe { ::std::mem::transmute(in_free) };
20952114
in_free as u64
20962115
});
20972116
__bindgen_bitfield_unit.set(3usize, 1u8, {
2098-
let eof: u8 = unsafe { ::std::mem::transmute(eof) };
2117+
let eof: u16 = unsafe { ::std::mem::transmute(eof) };
20992118
eof as u64
21002119
});
21012120
__bindgen_bitfield_unit.set(4usize, 1u8, {
2102-
let __exposed: u8 = unsafe { ::std::mem::transmute(__exposed) };
2121+
let __exposed: u16 = unsafe { ::std::mem::transmute(__exposed) };
21032122
__exposed as u64
21042123
});
21052124
__bindgen_bitfield_unit.set(5usize, 2u8, {
2106-
let fclose_stdiocast: u8 = unsafe { ::std::mem::transmute(fclose_stdiocast) };
2125+
let fclose_stdiocast: u16 = unsafe { ::std::mem::transmute(fclose_stdiocast) };
21072126
fclose_stdiocast as u64
21082127
});
21092128
__bindgen_bitfield_unit.set(7usize, 1u8, {
2110-
let has_buffered_data: u8 = unsafe { ::std::mem::transmute(has_buffered_data) };
2129+
let has_buffered_data: u16 = unsafe { ::std::mem::transmute(has_buffered_data) };
21112130
has_buffered_data as u64
21122131
});
2132+
__bindgen_bitfield_unit.set(8usize, 1u8, {
2133+
let fclose_stdiocast_flush_in_progress: u16 =
2134+
unsafe { ::std::mem::transmute(fclose_stdiocast_flush_in_progress) };
2135+
fclose_stdiocast_flush_in_progress as u64
2136+
});
21132137
__bindgen_bitfield_unit
21142138
}
21152139
}

src/flags.rs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,22 @@ use crate::ffi::{
88
CONST_CS, CONST_DEPRECATED, CONST_NO_FILE_CACHE, CONST_PERSISTENT, E_COMPILE_ERROR,
99
E_COMPILE_WARNING, E_CORE_ERROR, E_CORE_WARNING, E_DEPRECATED, E_ERROR, E_NOTICE, E_PARSE,
1010
E_RECOVERABLE_ERROR, E_STRICT, E_USER_DEPRECATED, E_USER_ERROR, E_USER_NOTICE, E_USER_WARNING,
11-
E_WARNING, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE, IS_INDIRECT,
12-
IS_ITERABLE, IS_LONG, IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE, IS_RESOURCE,
13-
IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID, PHP_INI_ALL,
14-
PHP_INI_PERDIR, PHP_INI_SYSTEM, PHP_INI_USER, ZEND_ACC_ABSTRACT, ZEND_ACC_ANON_CLASS,
15-
ZEND_ACC_CALL_VIA_TRAMPOLINE, ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE, ZEND_ACC_CONSTANTS_UPDATED,
16-
ZEND_ACC_CTOR, ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO, ZEND_ACC_EARLY_BINDING,
17-
ZEND_ACC_FAKE_CLOSURE, ZEND_ACC_FINAL, ZEND_ACC_GENERATOR, ZEND_ACC_HAS_FINALLY_BLOCK,
18-
ZEND_ACC_HAS_RETURN_TYPE, ZEND_ACC_HAS_TYPE_HINTS, ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE,
19-
ZEND_ACC_IMPLICIT_ABSTRACT_CLASS, ZEND_ACC_INTERFACE, ZEND_ACC_LINKED, ZEND_ACC_NEARLY_LINKED,
20-
ZEND_ACC_NEVER_CACHE, ZEND_ACC_NO_DYNAMIC_PROPERTIES, ZEND_ACC_PRELOADED, ZEND_ACC_PRIVATE,
21-
ZEND_ACC_PROMOTED, ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, ZEND_ACC_RESOLVED_INTERFACES,
22-
ZEND_ACC_RESOLVED_PARENT, ZEND_ACC_RETURN_REFERENCE, ZEND_ACC_STATIC, ZEND_ACC_STRICT_TYPES,
23-
ZEND_ACC_TOP_LEVEL, ZEND_ACC_TRAIT, ZEND_ACC_TRAIT_CLONE, ZEND_ACC_UNRESOLVED_VARIANCE,
24-
ZEND_ACC_USES_THIS, ZEND_ACC_USE_GUARDS, ZEND_ACC_VARIADIC, ZEND_EVAL_CODE,
25-
ZEND_HAS_STATIC_IN_METHODS, ZEND_INTERNAL_FUNCTION, ZEND_USER_FUNCTION, Z_TYPE_FLAGS_SHIFT,
26-
_IS_BOOL,
11+
E_WARNING, GC_IMMUTABLE, IS_ARRAY, IS_CALLABLE, IS_CONSTANT_AST, IS_DOUBLE, IS_FALSE,
12+
IS_INDIRECT, IS_ITERABLE, IS_LONG, IS_MIXED, IS_NULL, IS_OBJECT, IS_PTR, IS_REFERENCE,
13+
IS_RESOURCE, IS_STRING, IS_TRUE, IS_TYPE_COLLECTABLE, IS_TYPE_REFCOUNTED, IS_UNDEF, IS_VOID,
14+
PHP_INI_ALL, PHP_INI_PERDIR, PHP_INI_SYSTEM, PHP_INI_USER, ZEND_ACC_ABSTRACT,
15+
ZEND_ACC_ANON_CLASS, ZEND_ACC_CALL_VIA_TRAMPOLINE, ZEND_ACC_CHANGED, ZEND_ACC_CLOSURE,
16+
ZEND_ACC_CONSTANTS_UPDATED, ZEND_ACC_CTOR, ZEND_ACC_DEPRECATED, ZEND_ACC_DONE_PASS_TWO,
17+
ZEND_ACC_EARLY_BINDING, ZEND_ACC_FAKE_CLOSURE, ZEND_ACC_FINAL, ZEND_ACC_GENERATOR,
18+
ZEND_ACC_HAS_FINALLY_BLOCK, ZEND_ACC_HAS_RETURN_TYPE, ZEND_ACC_HAS_TYPE_HINTS,
19+
ZEND_ACC_HEAP_RT_CACHE, ZEND_ACC_IMMUTABLE, ZEND_ACC_IMPLICIT_ABSTRACT_CLASS,
20+
ZEND_ACC_INTERFACE, ZEND_ACC_LINKED, ZEND_ACC_NEARLY_LINKED, ZEND_ACC_NEVER_CACHE,
21+
ZEND_ACC_NO_DYNAMIC_PROPERTIES, ZEND_ACC_PRELOADED, ZEND_ACC_PRIVATE, ZEND_ACC_PROMOTED,
22+
ZEND_ACC_PROTECTED, ZEND_ACC_PUBLIC, ZEND_ACC_RESOLVED_INTERFACES, ZEND_ACC_RESOLVED_PARENT,
23+
ZEND_ACC_RETURN_REFERENCE, ZEND_ACC_STATIC, ZEND_ACC_STRICT_TYPES, ZEND_ACC_TOP_LEVEL,
24+
ZEND_ACC_TRAIT, ZEND_ACC_TRAIT_CLONE, ZEND_ACC_UNRESOLVED_VARIANCE, ZEND_ACC_USES_THIS,
25+
ZEND_ACC_USE_GUARDS, ZEND_ACC_VARIADIC, ZEND_EVAL_CODE, ZEND_HAS_STATIC_IN_METHODS,
26+
ZEND_INTERNAL_FUNCTION, ZEND_USER_FUNCTION, Z_TYPE_FLAGS_SHIFT, _IS_BOOL,
2727
};
2828

2929
use std::{convert::TryFrom, fmt::Display};
@@ -61,6 +61,8 @@ bitflags! {
6161

6262
const RefCounted = (IS_TYPE_REFCOUNTED << Z_TYPE_FLAGS_SHIFT);
6363
const Collectable = (IS_TYPE_COLLECTABLE << Z_TYPE_FLAGS_SHIFT);
64+
65+
const Immutable = GC_IMMUTABLE;
6466
}
6567
}
6668

src/types/array.rs

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ use crate::{
1414
convert::{FromZval, IntoZval},
1515
error::{Error, Result},
1616
ffi::{
17-
_zend_new_array, zend_array_count, zend_array_destroy, zend_array_dup, zend_hash_clean,
18-
zend_hash_get_current_data_ex, zend_hash_get_current_key_type_ex,
17+
_zend_new_array, zend_array_count, zend_array_destroy, zend_array_dup, zend_empty_array,
18+
zend_hash_clean, zend_hash_get_current_data_ex, zend_hash_get_current_key_type_ex,
1919
zend_hash_get_current_key_zval_ex, zend_hash_index_del, zend_hash_index_find,
2020
zend_hash_index_update, zend_hash_move_backwards_ex, zend_hash_move_forward_ex,
2121
zend_hash_next_index_insert, zend_hash_str_del, zend_hash_str_find, zend_hash_str_update,
22-
HashPosition, HT_MIN_SIZE,
22+
HashPosition, GC_FLAGS_MASK, GC_FLAGS_SHIFT, HT_MIN_SIZE,
2323
},
24-
flags::DataType,
24+
flags::{DataType, ZvalTypeFlags},
2525
types::Zval,
2626
};
2727

@@ -71,6 +71,27 @@ impl ZendHashTable {
7171
Self::with_capacity(HT_MIN_SIZE)
7272
}
7373

74+
/// Returns a shared immutable empty hashtable.
75+
/// This is useful to avoid redundant allocations when returning
76+
/// an empty collection from Rust code back to the PHP userland.
77+
/// Do not use this if you intend to modify the hashtable.
78+
///
79+
/// # Example
80+
/// ```no_run
81+
/// use ext_php_rs::types::ZendHashTable;
82+
///
83+
/// let ht = ZendHashTable::new_empty_immutable();
84+
/// ```
85+
pub fn new_empty_immutable() -> ZBox<Self> {
86+
unsafe {
87+
// SAFETY: zend_empty_array is a static global.
88+
let ptr = (&zend_empty_array as *const ZendHashTable) as *mut ZendHashTable;
89+
90+
// SAFETY: `as_mut()` panics if the pointer is null.
91+
ZBox::from_raw(ptr.as_mut().expect("zend_empty_array inconsistent"))
92+
}
93+
}
94+
7495
/// Creates a new, empty, PHP hashtable with an initial size, returned
7596
/// inside a [`ZBox`].
7697
///
@@ -102,6 +123,27 @@ impl ZendHashTable {
102123
}
103124
}
104125

126+
/// Determine whether this hashtable is immutable.
127+
///
128+
/// # Example
129+
///
130+
/// ```no_run
131+
/// use ext_php_rs::types::ZendHashTable;
132+
///
133+
/// let ht = ZendHashTable::new();
134+
/// assert!(!ht.is_immutable());
135+
///
136+
/// let immutable_ht = ZendHashTable::new_empty_immutable();
137+
/// assert!(immutable_ht.is_immutable());
138+
/// ```
139+
pub fn is_immutable(&self) -> bool {
140+
// SAFETY: Type info is initialized by Zend on array init.
141+
let gc_type_info = unsafe { self.gc.u.type_info };
142+
let gc_flags = (gc_type_info >> GC_FLAGS_SHIFT) & (GC_FLAGS_MASK >> GC_FLAGS_SHIFT);
143+
144+
gc_flags & ZvalTypeFlags::Immutable.bits() != 0
145+
}
146+
105147
/// Returns the current number of elements in the array.
106148
///
107149
/// # Example
@@ -539,8 +581,10 @@ impl ZendHashTable {
539581

540582
unsafe impl ZBoxable for ZendHashTable {
541583
fn free(&mut self) {
542-
// SAFETY: ZBox has immutable access to `self`.
543-
unsafe { zend_array_destroy(self) }
584+
if !self.is_immutable() {
585+
// SAFETY: ZBox has immutable access to `self`.
586+
unsafe { zend_array_destroy(self) }
587+
}
544588
}
545589
}
546590

@@ -878,6 +922,10 @@ where
878922
type Error = Error;
879923

880924
fn try_from(value: HashMap<K, V>) -> Result<Self> {
925+
if value.is_empty() {
926+
return Ok(ZendHashTable::new_empty_immutable());
927+
}
928+
881929
let mut ht = ZendHashTable::with_capacity(
882930
value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
883931
);
@@ -943,6 +991,10 @@ where
943991
type Error = Error;
944992

945993
fn try_from(value: Vec<T>) -> Result<Self> {
994+
if value.is_empty() {
995+
return Ok(ZendHashTable::new_empty_immutable());
996+
}
997+
946998
let mut ht = ZendHashTable::with_capacity(
947999
value.len().try_into().map_err(|_| Error::IntegerOverflow)?,
9481000
);

src/types/zval.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,14 @@ impl Zval {
563563
///
564564
/// * `val` - The value to set the zval as.
565565
pub fn set_hashtable(&mut self, val: ZBox<ZendHashTable>) {
566-
self.change_type(ZvalTypeFlags::ArrayEx);
566+
// Handle immutable shared arrays akin to ZVAL_EMPTY_ARRAY.
567+
let type_info = if val.is_immutable() {
568+
ZvalTypeFlags::Array
569+
} else {
570+
ZvalTypeFlags::ArrayEx
571+
};
572+
573+
self.change_type(type_info);
567574
self.value.arr = val.into_raw();
568575
}
569576

0 commit comments

Comments
 (0)