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

davidcole1340 / ext-php-rs / 18881635050

28 Oct 2025 04:18PM UTC coverage: 31.037% (-0.1%) from 31.138%
18881635050

Pull #566

github

web-flow
Merge 610ff3dd0 into a0387a4cd
Pull Request #566: chore(rust): bump Rust edition to 2024

10 of 91 new or added lines in 14 files covered. (10.99%)

1 existing line in 1 file now uncovered.

1356 of 4369 relevant lines covered (31.04%)

8.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> {
×
NEW
75
        unsafe { 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();
×
NEW
113
        let obj =
×
NEW
114
            unsafe { ext_php_rs_zend_object_alloc(size as _, ce).cast::<ZendClassObject<T>>() };
×
115
        let obj = unsafe {
NEW
116
            obj.as_mut()
×
117
                .expect("Failed to allocate for new Zend object")
118
        };
119

NEW
120
        unsafe { zend_object_std_init(&raw mut obj.std, ce) };
×
NEW
121
        unsafe { object_properties_init(&raw mut obj.std, ce) };
×
122

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

128
        obj.std.handlers = meta.handlers();
×
NEW
129
        unsafe { ZBox::from_raw(obj) }
×
130
    }
131

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

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

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

178
    // TODO: Verify if this is safe to use, as it allows mutating the
179
    // hashtable while only having a reference to it. #461
180
    #[allow(clippy::mut_from_ref)]
181
    fn internal_from_zend_obj(std: &zend_object) -> Option<&mut Self> {
×
182
        let std = ptr::from_ref(std).cast::<c_char>();
×
183
        let ptr = unsafe {
184
            let offset = isize::try_from(Self::std_offset()).expect("Offset overflow");
×
185
            let ptr = std.offset(0 - offset).cast::<Self>();
×
186
            ptr.cast_mut().as_mut()?
×
187
        };
188

189
        if ptr.std.instance_of(T::get_metadata().ce()) {
×
190
            Some(ptr)
×
191
        } else {
192
            None
×
193
        }
194
    }
195

196
    /// Returns a mutable reference to the underlying Zend object.
197
    pub fn get_mut_zend_obj(&mut self) -> &mut zend_object {
×
198
        &mut self.std
×
199
    }
200

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

208
            (std as usize) - (base as usize)
×
209
        }
210
    }
211
}
212

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

216
    fn from_zval(zval: &'a Zval) -> Option<Self> {
×
217
        Self::from_zend_object(zval.object()?).ok()
×
218
    }
219
}
220

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

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

231
    fn from_zval_mut(zval: &'a mut Zval) -> Option<Self> {
×
232
        Self::from_zend_object_mut(zval.object_mut()?).ok()
×
233
    }
234
}
235

236
impl<'a, T: RegisteredClass> FromZendObjectMut<'a> for &'a mut ZendClassObject<T> {
237
    fn from_zend_object_mut(obj: &'a mut ZendObject) -> Result<Self> {
×
238
        ZendClassObject::from_zend_obj_mut(obj).ok_or(Error::InvalidScope)
×
239
    }
240
}
241

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

251
impl<T> Deref for ZendClassObject<T> {
252
    type Target = T;
253

254
    fn deref(&self) -> &Self::Target {
×
255
        self.obj
×
256
            .as_ref()
257
            .expect("Attempted to access uninitialized class object")
258
    }
259
}
260

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

269
impl<T: RegisteredClass + Default> Default for ZBox<ZendClassObject<T>> {
270
    #[inline]
271
    fn default() -> Self {
×
272
        ZendClassObject::new(T::default())
×
273
    }
274
}
275

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

290
impl<T: RegisteredClass> IntoZval for ZBox<ZendClassObject<T>> {
291
    const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
292
    const NULLABLE: bool = false;
293

294
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
295
        let obj = self.into_raw();
×
296
        zv.set_object(&mut obj.std);
×
297
        Ok(())
×
298
    }
299
}
300

301
impl<T: RegisteredClass> IntoZval for &mut ZendClassObject<T> {
302
    const TYPE: DataType = DataType::Object(Some(T::CLASS_NAME));
303
    const NULLABLE: bool = false;
304

305
    #[inline]
306
    fn set_zval(self, zv: &mut Zval, _: bool) -> Result<()> {
×
307
        zv.set_object(&mut self.std);
×
308
        Ok(())
×
309
    }
310
}
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