Skip to content

Commit 79e29ee

Browse files
authored
Refactor clone_obj, remove Object clone methods. (#121)
1 parent 4dd852f commit 79e29ee

File tree

5 files changed

+185
-115
lines changed

5 files changed

+185
-115
lines changed

phper-doc/doc/_05_internal_types/_03_z_obj/index.md

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,9 @@ e.set_property("message", "oh no");
1717
let _message = e.call("getMessage", []).unwrap();
1818
```
1919

20-
`ZObj` implements `ToOwned` and `ToRefOwned` to upgrade to `ZObject`.
20+
`ZObj` implements `ToRefOwned` to upgrade to `ZObject`, duplicate the object via increment refcount.
2121

22-
- `ToOwned`: Duplicate the object via PHP keyword `clone`, like `$cloned_object = clone $some_object();`;
23-
- `ToRefOwned`: Duplicate the object via increment refcount.
24-
25-
`ZObject` implements `Clone`, same as `ZObj::to_owned`.
22+
`ZObject` implements `RefClone`, same as `ZObj::to_owned`.
2623

2724
```rust,no_run
2825
use phper::sys;
@@ -35,9 +32,6 @@ extern "C" {
3532
3633
let o = unsafe { ZObj::from_mut_ptr(something()) };
3734
38-
// By PHP `clone`.
39-
let _o = o.to_owned();
40-
4135
// By refcount increment.
4236
let _o = o.to_ref_owned();
4337
```

phper/src/classes.rs

Lines changed: 133 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ use crate::{
2121
utils::ensure_end_with_zero,
2222
values::ZVal,
2323
};
24-
use once_cell::sync::OnceCell;
2524
use std::{
2625
any::Any,
2726
borrow::ToOwned,
@@ -33,6 +32,7 @@ use std::{
3332
os::raw::c_int,
3433
ptr::null_mut,
3534
rc::Rc,
35+
slice,
3636
sync::atomic::{AtomicPtr, Ordering},
3737
};
3838

@@ -282,6 +282,8 @@ impl<T> StateClass<T> {
282282

283283
pub(crate) type StateConstructor = dyn Fn() -> *mut dyn Any;
284284

285+
pub(crate) type StateCloner = dyn Fn(*const dyn Any) -> *mut dyn Any;
286+
285287
/// Builder for registering class.
286288
///
287289
/// `<T>` means the type of holding state.
@@ -296,6 +298,7 @@ pub struct ClassEntity<T: 'static> {
296298
parent: Option<Box<dyn Fn() -> &'static ClassEntry>>,
297299
interfaces: Vec<Box<dyn Fn() -> &'static ClassEntry>>,
298300
bind_class: Option<&'static StateClass<T>>,
301+
state_cloner: Option<Rc<StateCloner>>,
299302
_p: PhantomData<(*mut (), T)>,
300303
}
301304

@@ -332,6 +335,7 @@ impl<T: 'static> ClassEntity<T> {
332335
parent: None,
333336
interfaces: Vec::new(),
334337
bind_class: None,
338+
state_cloner: None,
335339
_p: PhantomData,
336340
}
337341
}
@@ -426,6 +430,47 @@ impl<T: 'static> ClassEntity<T> {
426430
self.bind_class = Some(cls);
427431
}
428432

433+
/// Add the state clone function, called when cloning PHP object.
434+
///
435+
/// By default, the object registered by `phper` is uncloneable, if you
436+
/// clone the object in PHP like this:
437+
///
438+
/// ```php
439+
/// $foo = new Foo();
440+
/// $foo2 = clone $foo;
441+
/// ```
442+
///
443+
/// Will throw the Error: `Uncaught Error: Trying to clone an uncloneable
444+
/// object of class Foo`.
445+
///
446+
/// And then, if you want the object to be cloneable, you should register
447+
/// the state clone method for the class.
448+
///
449+
/// # Examples
450+
///
451+
/// ```
452+
/// use phper::classes::ClassEntity;
453+
///
454+
/// fn make_foo_class() -> ClassEntity<i64> {
455+
/// let mut class = ClassEntity::new_with_state_constructor("Foo", || 123456);
456+
/// class.state_cloner(Clone::clone);
457+
/// class
458+
/// }
459+
/// ```
460+
pub fn state_cloner(&mut self, clone_fn: impl Fn(&T) -> T + 'static) {
461+
self.state_cloner = Some(Rc::new(move |src| {
462+
let src = unsafe {
463+
src.as_ref()
464+
.unwrap()
465+
.downcast_ref::<T>()
466+
.expect("cast Any to T failed")
467+
};
468+
let dest = clone_fn(src);
469+
let boxed = Box::new(dest) as Box<dyn Any>;
470+
Box::into_raw(boxed)
471+
}));
472+
}
473+
429474
#[allow(clippy::useless_conversion)]
430475
pub(crate) unsafe fn init(&self) -> *mut zend_class_entry {
431476
let parent: *mut zend_class_entry = self
@@ -464,7 +509,6 @@ impl<T: 'static> ClassEntity<T> {
464509
}
465510

466511
unsafe fn function_entries(&self) -> *const zend_function_entry {
467-
let last_entry = self.take_state_constructor_into_function_entry();
468512
let mut methods = self
469513
.method_entities
470514
.iter()
@@ -473,8 +517,11 @@ impl<T: 'static> ClassEntity<T> {
473517

474518
methods.push(zeroed::<zend_function_entry>());
475519

476-
// Store the state constructor pointer to zend_class_entry
477-
methods.push(last_entry);
520+
// Store the state constructor pointer to zend_class_entry.
521+
methods.push(self.take_state_constructor_into_function_entry());
522+
523+
// Store the state cloner pointer to zend_class_entry.
524+
methods.push(self.take_state_cloner_into_function_entry());
478525

479526
Box::into_raw(methods.into_boxed_slice()).cast()
480527
}
@@ -486,6 +533,16 @@ impl<T: 'static> ClassEntity<T> {
486533
ptr.write(state_constructor);
487534
entry
488535
}
536+
537+
unsafe fn take_state_cloner_into_function_entry(&self) -> zend_function_entry {
538+
let mut entry = zeroed::<zend_function_entry>();
539+
let ptr = &mut entry as *mut _ as *mut *const StateCloner;
540+
if let Some(state_cloner) = &self.state_cloner {
541+
let state_constructor = Rc::into_raw(state_cloner.clone());
542+
ptr.write(state_constructor);
543+
}
544+
entry
545+
}
489546
}
490547

491548
unsafe extern "C" fn class_init_handler(
@@ -577,45 +634,97 @@ pub enum Visibility {
577634
Private = ZEND_ACC_PRIVATE,
578635
}
579636

580-
fn get_object_handlers() -> &'static zend_object_handlers {
581-
static HANDLERS: OnceCell<zend_object_handlers> = OnceCell::new();
582-
HANDLERS.get_or_init(|| unsafe {
583-
let mut handlers = std_object_handlers;
584-
handlers.offset = StateObj::<()>::offset() as c_int;
585-
handlers.free_obj = Some(free_object);
586-
handlers
587-
})
588-
}
589-
590637
#[allow(clippy::useless_conversion)]
591638
unsafe extern "C" fn create_object(ce: *mut zend_class_entry) -> *mut zend_object {
592639
// Alloc more memory size to store state data.
593640
let state_object = phper_zend_object_alloc(size_of::<StateObj<()>>().try_into().unwrap(), ce);
594641
let state_object = StateObj::<()>::from_mut_ptr(state_object);
595642

596-
// Common initialize process.
597-
let object = state_object.as_mut_object().as_mut_ptr();
598-
zend_object_std_init(object, ce);
599-
object_properties_init(object, ce);
600-
rebuild_object_properties(object);
601-
(*object).handlers = get_object_handlers();
602-
603-
// Get state constructor.
643+
// Find the hack elements hidden behind null builtin_function.
604644
let mut func_ptr = (*ce).info.internal.builtin_functions;
605645
while !(*func_ptr).fname.is_null() {
606646
func_ptr = func_ptr.offset(1);
607647
}
648+
649+
// Get state constructor.
608650
func_ptr = func_ptr.offset(1);
609651
let state_constructor = func_ptr as *mut *const StateConstructor;
610-
let state_constructor = state_constructor.read();
652+
let state_constructor = state_constructor.read().as_ref().unwrap();
653+
654+
// Get state cloner.
655+
func_ptr = func_ptr.offset(1);
656+
let has_state_cloner =
657+
slice::from_raw_parts(func_ptr as *const u8, size_of::<*const StateCloner>())
658+
!= [0u8; size_of::<*const StateCloner>()];
659+
660+
// Common initialize process.
661+
let object = state_object.as_mut_object().as_mut_ptr();
662+
zend_object_std_init(object, ce);
663+
object_properties_init(object, ce);
664+
rebuild_object_properties(object);
665+
666+
// Set handlers
667+
let mut handlers = Box::new(std_object_handlers);
668+
handlers.offset = StateObj::<()>::offset() as c_int;
669+
handlers.free_obj = Some(free_object);
670+
handlers.clone_obj = has_state_cloner.then_some(clone_object);
671+
(*object).handlers = Box::into_raw(handlers);
611672

612673
// Call the state constructor and store the state.
613-
let data = (state_constructor.as_ref().unwrap())();
674+
let data = (state_constructor)();
614675
*state_object.as_mut_any_state() = data;
615676

616677
object
617678
}
618679

680+
#[cfg(phper_major_version = "8")]
681+
unsafe extern "C" fn clone_object(object: *mut zend_object) -> *mut zend_object {
682+
clone_object_common(object)
683+
}
684+
685+
#[cfg(phper_major_version = "7")]
686+
unsafe extern "C" fn clone_object(object: *mut zval) -> *mut zend_object {
687+
let object = phper_z_obj_p(object);
688+
clone_object_common(object)
689+
}
690+
691+
#[allow(clippy::useless_conversion)]
692+
unsafe fn clone_object_common(object: *mut zend_object) -> *mut zend_object {
693+
let ce = (*object).ce;
694+
695+
// Alloc more memory size to store state data.
696+
let new_state_object =
697+
phper_zend_object_alloc(size_of::<StateObj<()>>().try_into().unwrap(), ce);
698+
let new_state_object = StateObj::<()>::from_mut_ptr(new_state_object);
699+
700+
// Find the hack elements hidden behind null builtin_function.
701+
let mut func_ptr = (*(*object).ce).info.internal.builtin_functions;
702+
while !(*func_ptr).fname.is_null() {
703+
func_ptr = func_ptr.offset(1);
704+
}
705+
706+
// Get state cloner.
707+
func_ptr = func_ptr.offset(2);
708+
let state_cloner = func_ptr as *mut *const StateCloner;
709+
let state_cloner = state_cloner.read().as_ref().unwrap();
710+
711+
// Initialize and clone members
712+
let new_object = new_state_object.as_mut_object().as_mut_ptr();
713+
zend_object_std_init(new_object, ce);
714+
object_properties_init(new_object, ce);
715+
zend_objects_clone_members(new_object, object);
716+
717+
// Set handlers
718+
(*new_object).handlers = (*object).handlers;
719+
720+
// Call the state cloner and store the state.
721+
let state_object = StateObj::<()>::from_mut_object_ptr(object);
722+
let data = (state_cloner)(*state_object.as_mut_any_state());
723+
*new_state_object.as_mut_any_state() = data;
724+
725+
new_object
726+
}
727+
619728
unsafe extern "C" fn free_object(object: *mut zend_object) {
620729
let state_object = StateObj::<()>::from_mut_object_ptr(object);
621730

phper/src/objects.rs

Lines changed: 5 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,15 @@ use crate::{
1717
sys::*,
1818
values::ZVal,
1919
};
20-
use phper_alloc::ToRefOwned;
20+
use phper_alloc::{RefClone, ToRefOwned};
2121
use std::{
2222
any::Any,
2323
borrow::Borrow,
2424
convert::TryInto,
2525
ffi::c_void,
2626
fmt::{self, Debug},
2727
marker::PhantomData,
28-
mem::{replace, size_of, zeroed, ManuallyDrop},
28+
mem::{replace, size_of, ManuallyDrop},
2929
ops::{Deref, DerefMut},
3030
ptr::null_mut,
3131
};
@@ -268,17 +268,6 @@ impl ZObj {
268268
}
269269
}
270270

271-
impl ToOwned for ZObj {
272-
type Owned = ZObject;
273-
274-
/// The `to_owned` will do the copy like in PHP `$cloned_object = clone
275-
/// $some_object();`.
276-
#[inline]
277-
fn to_owned(&self) -> Self::Owned {
278-
clone_obj(self.as_ptr())
279-
}
280-
}
281-
282271
impl ToRefOwned for ZObj {
283272
type Owned = ZObject;
284273

@@ -344,12 +333,10 @@ impl ZObject {
344333
}
345334
}
346335

347-
impl Clone for ZObject {
348-
/// The clone will do the copy like in PHP `$cloned_object = clone
349-
/// $some_object();`.
336+
impl RefClone for ZObject {
350337
#[inline]
351-
fn clone(&self) -> Self {
352-
clone_obj(self.as_ptr())
338+
fn ref_clone(&mut self) -> Self {
339+
self.to_ref_owned()
353340
}
354341
}
355342

@@ -387,32 +374,6 @@ impl Debug for ZObject {
387374
}
388375
}
389376

390-
fn clone_obj(obj: *const zend_object) -> ZObject {
391-
unsafe {
392-
ZObject::from_raw({
393-
let mut zv = zeroed::<zval>();
394-
phper_zval_obj(&mut zv, obj as *mut _);
395-
let handlers = phper_z_obj_ht_p(&zv);
396-
397-
let ptr = {
398-
#[cfg(phper_major_version = "7")]
399-
{
400-
&mut zv as *mut _
401-
}
402-
#[cfg(phper_major_version = "8")]
403-
{
404-
obj as *mut _
405-
}
406-
};
407-
408-
match (*handlers).clone_obj {
409-
Some(clone_obj) => clone_obj(ptr),
410-
None => zend_objects_clone_obj(ptr),
411-
}
412-
})
413-
}
414-
}
415-
416377
pub(crate) type AnyState = *mut dyn Any;
417378

418379
/// The object owned state, usually as the parameter of method handler.

0 commit comments

Comments
 (0)