• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

davidcole1340 / ext-php-rs / 15938921447

28 Jun 2025 01:19AM UTC coverage: 20.223% (-0.3%) from 20.545%
15938921447

Pull #456

github

web-flow
Merge 560eb29d8 into 688201af6
Pull Request #456: fix(array): cast numeric keys into zend_ulong when inserting string keys

2 of 84 new or added lines in 3 files covered. (2.38%)

359 existing lines in 6 files now uncovered.

761 of 3763 relevant lines covered (20.22%)

3.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/types/class_object.rs
1
//! Represents an object in PHP. Allows for overriding the internal object used
2
//! by classes, allowing users to store Rust data inside a PHP object.
3

4
use std::{
5
    fmt::Debug,
6
    mem,
7
    ops::{Deref, DerefMut},
8
    os::raw::c_char,
9
    ptr::{self, NonNull},
10
};
11

12
use crate::{
13
    boxed::{ZBox, ZBoxable},
14
    class::RegisteredClass,
15
    convert::{FromZendObject, FromZendObjectMut, FromZval, FromZvalMut, IntoZval},
16
    error::{Error, Result},
17
    ffi::{
18
        ext_php_rs_zend_object_alloc, ext_php_rs_zend_object_release, object_properties_init,
19
        zend_object, zend_object_std_init, zend_objects_clone_members,
20
    },
21
    flags::DataType,
22
    types::{ZendObject, Zval},
23
    zend::ClassEntry,
24
};
25

26
/// Representation of a Zend class object in memory.
27
#[repr(C)]
28
#[derive(Debug)]
29
pub struct ZendClassObject<T> {
30
    /// The object stored inside the class object.
31
    pub obj: Option<T>,
32
    /// The standard zend object.
33
    pub std: ZendObject,
34
}
35

36
impl<T: RegisteredClass> ZendClassObject<T> {
37
    /// Creates a new [`ZendClassObject`] of type `T`, where `T` is a
38
    /// [`RegisteredClass`] in PHP, storing the given value `val` inside the
39
    /// object.
40
    ///
41
    /// # Parameters
42
    ///
43
    /// * `val` - The value to store inside the object.
44
    ///
45
    /// # Panics
46
    ///
47
    /// Panics if memory was unable to be allocated for the new object.
48
    pub fn new(val: T) -> ZBox<Self> {
×
49
        // SAFETY: We are providing a value to initialize the object with.
50
        unsafe { Self::internal_new(Some(val), None) }
×
51
    }
52

53
    /// Creates a new [`ZendClassObject`] of type `T`, with an uninitialized
54
    /// internal object.
55
    ///
56
    /// # Safety
57
    ///
58
    /// As the object is uninitialized, the caller must ensure the following
59
    /// until the internal object is initialized:
60
    ///
61
    /// * The object is never dereferenced to `T`.
62
    /// * The [`Clone`] implementation is never called.
63
    /// * The [`Debug`] implementation is never called.
64
    ///
65
    /// If any of these conditions are not met while not initialized, the
66
    /// corresponding function will panic. Converting the object into its
67
    /// inner pointer with the [`into_raw`] function is valid, however.
68
    ///
69
    /// [`into_raw`]: #method.into_raw
70
    ///
71
    /// # Panics
72
    ///
73
    /// Panics if memory was unable to be allocated for the new object.
74
    pub unsafe fn new_uninit(ce: Option<&'static ClassEntry>) -> ZBox<Self> {
×
75
        Self::internal_new(None, ce)
×
76
    }
77

78
    /// Creates a new [`ZendObject`] of type `T`, storing the given (and
79
    /// potentially uninitialized) `val` inside the object.
80
    ///
81
    /// # Parameters
82
    ///
83
    /// * `val` - Value to store inside the object. See safety section.
84
    /// * `init` - Whether the given `val` was initialized.
85
    ///
86
    /// # Safety
87
    ///
88
    /// Providing an initialized variant of [`MaybeUninit<T>`] is safe.
89
    ///
90
    /// Providing an uninitialized variant of [`MaybeUninit<T>`] is unsafe. As
91
    /// the object is uninitialized, the caller must ensure the following
92
    /// until the internal object is initialized:
93
    ///
94
    /// * The object is never dereferenced to `T`.
95
    /// * The [`Clone`] implementation is never called.
96
    /// * The [`Debug`] implementation is never called.
97
    ///
98
    /// If any of these conditions are not met while not initialized, the
99
    /// corresponding function will panic. Converting the object into its
100
    /// inner with the [`into_raw`] function is valid, however. You can
101
    /// initialize the object with the [`initialize`] function.
102
    ///
103
    /// [`into_raw`]: #method.into_raw
104
    /// [`initialize`]: #method.initialize
105
    ///
106
    /// # Panics
107
    ///
108
    /// Panics if memory was unable to be allocated for the new object.
109
    unsafe fn internal_new(val: Option<T>, ce: Option<&'static ClassEntry>) -> ZBox<Self> {
×
110
        let size = mem::size_of::<ZendClassObject<T>>();
×
111
        let meta = T::get_metadata();
×
112
        let ce = ptr::from_ref(ce.unwrap_or_else(|| meta.ce())).cast_mut();
×
113
        let obj = ext_php_rs_zend_object_alloc(size as _, ce).cast::<ZendClassObject<T>>();
×
114
        let obj = obj
×
115
            .as_mut()
116
            .expect("Failed to allocate for new Zend object");
117

118
        zend_object_std_init(&raw mut obj.std, ce);
×
119
        object_properties_init(&raw mut obj.std, ce);
×
120

121
        // SAFETY: `obj` is non-null and well aligned as it is a reference.
122
        // As the data in `obj.obj` is uninitialized, we don't want to drop
123
        // the data, but directly override it.
124
        ptr::write(&raw mut obj.obj, val);
×
125

126
        obj.std.handlers = meta.handlers();
×
127
        ZBox::from_raw(obj)
×
128
    }
129

130
    /// Initializes the class object with the value `val`.
131
    ///
132
    /// # Parameters
133
    ///
134
    /// * `val` - The value to initialize the object with.
135
    ///
136
    /// # Returns
137
    ///
138
    /// Returns the old value in an [`Option`] if the object had already been
139
    /// initialized, [`None`] otherwise.
140
    pub fn initialize(&mut self, val: T) -> Option<T> {
×
141
        self.obj.replace(val)
×
142
    }
143

144
    /// Returns a mutable reference to the [`ZendClassObject`] of a given zend
145
    /// object `obj`. Returns [`None`] if the given object is not of the
146
    /// type `T`.
147
    ///
148
    /// # Parameters
149
    ///
150
    /// * `obj` - The zend object to get the [`ZendClassObject`] for.
151
    ///
152
    /// # Panics
153
    ///
154
    /// * If the std offset over/underflows `isize`.
155
    #[must_use]
156
    pub fn from_zend_obj(std: &zend_object) -> Option<&Self> {
×
157
        Some(Self::internal_from_zend_obj(std)?)
×
158
    }
159

160
    /// Returns a mutable reference to the [`ZendClassObject`] of a given zend
161
    /// object `obj`. Returns [`None`] if the given object is not of the
162
    /// type `T`.
163
    ///
164
    /// # Parameters
165
    ///
166
    /// * `obj` - The zend object to get the [`ZendClassObject`] for.
167
    ///
168
    /// # Panics
169
    ///
170
    /// * If the std offset over/underflows `isize`.
171
    #[allow(clippy::needless_pass_by_ref_mut)]
172
    pub fn from_zend_obj_mut(std: &mut zend_object) -> Option<&mut Self> {
×
173
        Self::internal_from_zend_obj(std)
×
174
    }
175

176
    #[allow(clippy::mut_from_ref)]
177
    fn internal_from_zend_obj(std: &zend_object) -> Option<&mut Self> {
×
UNCOV
178
        let std = ptr::from_ref(std).cast::<c_char>();
×
179
        let ptr = unsafe {
180
            let offset = isize::try_from(Self::std_offset()).expect("Offset overflow");
×
181
            let ptr = std.offset(0 - offset).cast::<Self>();
×
UNCOV
182
            ptr.cast_mut().as_mut()?
×
183
        };
184

185
        if ptr.std.instance_of(T::get_metadata().ce()) {
×
UNCOV
186
            Some(ptr)
×
187
        } else {
UNCOV
188
            None
×
189
        }
190
    }
191

192
    /// Returns a mutable reference to the underlying Zend object.
193
    pub fn get_mut_zend_obj(&mut self) -> &mut zend_object {
×
UNCOV
194
        &mut self.std
×
195
    }
196

197
    /// Returns the offset of the `std` property in the class object.
UNCOV
198
    pub(crate) fn std_offset() -> usize {
×
199
        unsafe {
200
            let null = NonNull::<Self>::dangling();
×
201
            let base = null.as_ref() as *const Self;
×
UNCOV
202
            let std = &raw const null.as_ref().std;
×
203

UNCOV
204
            (std as usize) - (base as usize)
×
205
        }
206
    }
207
}
208

209
impl<'a, T: RegisteredClass> FromZval<'a> for &'a ZendClassObject<T> {
210
    const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
211

212
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
UNCOV
213
        Self::from_zend_object(zval.object()?).ok()
×
214
    }
215
}
216

217
impl<'a, T: RegisteredClass> FromZendObject<'a> for &'a ZendClassObject<T> {
UNCOV
218
    fn from_zend_object(obj: &'a ZendObject) -> Result<Self> {
×
219
        // TODO(david): replace with better error
UNCOV
220
        ZendClassObject::from_zend_obj(obj).ok_or(Error::InvalidScope)
×
221
    }
222
}
223

224
impl<'a, T: RegisteredClass> FromZvalMut<'a> for &'a mut ZendClassObject<T> {
225
    const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
226

227
    fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
×
UNCOV
228
        Self::from_zend_object_mut(zval.object_mut()?).ok()
×
229
    }
230
}
231

232
impl<'a, T: RegisteredClass> FromZendObjectMut<'a> for &'a mut ZendClassObject<T> {
233
    fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result<Self> {
×
UNCOV
234
        ZendClassObject::from_zend_obj_mut(obj).ok_or(Error::InvalidScope)
×
235
    }
236
}
237

238
unsafe impl<T: RegisteredClass> ZBoxable for ZendClassObject<T> {
UNCOV
239
    fn free(&mut self) {
×
240
        // SAFETY: All constructors guarantee that `self` contains a valid pointer.
241
        // Further, all constructors guarantee that the `std` field of
242
        // `ZendClassObject` will be initialized.
UNCOV
243
        unsafe { ext_php_rs_zend_object_release(&raw mut self.std) }
×
244
    }
245
}
246

247
impl<T> Deref for ZendClassObject<T> {
248
    type Target = T;
249

250
    fn deref(&self) -> &Self::Target {
×
UNCOV
251
        self.obj
×
252
            .as_ref()
253
            .expect("Attempted to access uninitialized class object")
254
    }
255
}
256

257
impl<T> DerefMut for ZendClassObject<T> {
258
    fn deref_mut(&mut self) -> &mut Self::Target {
×
UNCOV
259
        self.obj
×
260
            .as_mut()
261
            .expect("Attempted to access uninitialized class object")
262
    }
263
}
264

265
impl<T: RegisteredClass + Default> Default for ZBox<ZendClassObject<T>> {
266
    #[inline]
267
    fn default() -> Self {
×
UNCOV
268
        ZendClassObject::new(T::default())
×
269
    }
270
}
271

272
impl<T: RegisteredClass + Clone> Clone for ZBox<ZendClassObject<T>> {
UNCOV
273
    fn clone(&self) -> Self {
×
274
        // SAFETY: All constructors of `NewClassObject` guarantee that it will contain a
275
        // valid pointer. The constructor also guarantees that the internal
276
        // `ZendClassObject` pointer will contain a valid, initialized `obj`,
277
        // therefore we can dereference both safely.
278
        unsafe {
279
            let mut new = ZendClassObject::new((***self).clone());
×
280
            zend_objects_clone_members(&raw mut new.std, (&raw const self.std).cast_mut());
×
UNCOV
281
            new
×
282
        }
283
    }
284
}
285

286
impl<T: RegisteredClass> IntoZval for ZBox<ZendClassObject<T>> {
287
    const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
288
    const NULLABLE: bool = false;
289

290
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
291
        let obj = self.into_raw();
×
292
        zv.set_object(&mut obj.std);
×
UNCOV
293
        Ok(())
×
294
    }
295
}
296

297
impl<T: RegisteredClass> IntoZval for &mut ZendClassObject<T> {
298
    const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
299
    const NULLABLE: bool = false;
300

301
    #[inline]
302
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
303
        zv.set_object(&mut self.std);
×
UNCOV
304
        Ok(())
×
305
    }
306
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc