1use std::{
2 ffi::CString,
3 mem::{ManuallyDrop, MaybeUninit},
4 ptr,
5 rc::Rc,
6};
7
8use crate::{
9 args::ArgInfoTables,
10 builders::{FunctionBuilder, function::free_registered_entries},
11 class::{ClassEntryInfo, ConstructorMeta, ConstructorResult, RegisteredClass},
12 convert::{IntoZval, IntoZvalDyn},
13 describe::DocComments,
14 error::{Error, Result},
15 exception::PhpException,
16 ffi::{
17 zend_declare_class_constant_ex, zend_declare_property, zend_do_implement_interface,
18 zend_hash_str_find_ptr_lc, zend_register_internal_class_ex,
19 zend_register_internal_interface,
20 },
21 flags::{ClassFlags, ConstantFlags, DataType, MethodFlags, PropertyFlags},
22 types::{ZendClassObject, ZendObject, ZendStr, Zval},
23 zend::{ClassEntry, CompilerGlobals, ExecuteData, ExecutorGlobals, FunctionEntry},
24 zend_fastcall,
25};
26
27type ConstantEntry = (
29 String,
30 Box<dyn FnOnce() -> Result<Zval>>,
31 DocComments,
32 String,
33 ConstantFlags,
34);
35type PropertyDefault = Option<Box<dyn FnOnce() -> Result<Zval>>>;
36
37pub struct ClassProperty {
39 pub name: String,
41 pub flags: PropertyFlags,
43 pub default: PropertyDefault,
45 pub docs: DocComments,
47 pub ty: Option<DataType>,
49 pub nullable: bool,
51 pub readonly: bool,
53 pub default_stub: Option<String>,
55}
56
57pub(crate) fn first_duplicate<'a>(
61 names: impl IntoIterator<Item = &'a str>,
62 fold_ascii_case: bool,
63) -> Option<&'a str> {
64 let names: Vec<&str> = names.into_iter().collect();
65 names.iter().enumerate().find_map(|(i, name)| {
66 names[..i]
67 .iter()
68 .any(|earlier| {
69 if fold_ascii_case {
70 earlier.eq_ignore_ascii_case(name)
71 } else {
72 earlier == name
73 }
74 })
75 .then_some(*name)
76 })
77}
78
79pub(crate) fn ensure_class_name_free(name: &str) -> Result<()> {
86 let class_table = CompilerGlobals::get().class_table;
87 if class_table.is_null() {
88 return Ok(());
89 }
90 let found = unsafe { zend_hash_str_find_ptr_lc(class_table, name.as_ptr().cast(), name.len()) };
93 if found.is_null() {
94 Ok(())
95 } else {
96 Err(Error::DuplicateClass {
97 class: name.to_owned(),
98 })
99 }
100}
101
102#[must_use]
104pub struct ClassBuilder {
105 pub(crate) name: String,
106 ce: ClassEntry,
107 pub(crate) extends: Option<ClassEntryInfo>,
108 pub(crate) interfaces: Vec<ClassEntryInfo>,
109 pub(crate) methods: Vec<(FunctionBuilder<'static>, MethodFlags)>,
110 object_override: Option<unsafe extern "C" fn(class_type: *mut ClassEntry) -> *mut ZendObject>,
111 pub(crate) properties: Vec<ClassProperty>,
112 pub(crate) constants: Vec<ConstantEntry>,
113 register: Option<fn(&'static mut ClassEntry, ArgInfoTables)>,
114 pub(crate) docs: DocComments,
115}
116
117impl ClassBuilder {
118 pub fn new<T: Into<String>>(name: T) -> Self {
125 Self {
126 name: name.into(),
127 ce: unsafe { MaybeUninit::zeroed().assume_init() },
130 extends: None,
131 interfaces: vec![],
132 methods: vec![],
133 object_override: None,
134 properties: vec![],
135 constants: vec![],
136 register: None,
137 docs: &[],
138 }
139 }
140
141 #[must_use]
143 pub fn get_flags(&self) -> u32 {
144 self.ce.ce_flags
145 }
146
147 pub fn extends(mut self, parent: ClassEntryInfo) -> Self {
153 self.extends = Some(parent);
154 self
155 }
156
157 pub fn implements(mut self, interface: ClassEntryInfo) -> Self {
167 self.interfaces.push(interface);
168 self
169 }
170
171 pub fn method(mut self, func: FunctionBuilder<'static>, flags: MethodFlags) -> Self {
178 self.methods.push((func, flags));
179 self
180 }
181
182 pub fn property(mut self, prop: ClassProperty) -> Self {
188 self.properties.push(prop);
189 self
190 }
191
192 pub fn constant<T: Into<String>>(
209 mut self,
210 name: T,
211 value: impl IntoZval + 'static,
212 docs: DocComments,
213 flags: ConstantFlags,
214 ) -> Result<Self> {
215 let zval = value.into_zval(true)?;
216 let stub = crate::convert::zval_to_stub(&zval);
217 self.constants
218 .push((name.into(), Box::new(|| Ok(zval)), docs, stub, flags));
219 Ok(self)
220 }
221
222 pub fn dyn_constant<T: Into<String>>(
239 mut self,
240 name: T,
241 value: &'static dyn IntoZvalDyn,
242 docs: DocComments,
243 flags: ConstantFlags,
244 ) -> Result<Self> {
245 let stub = value.stub_value();
246 let value = Rc::new(value);
247 self.constants.push((
248 name.into(),
249 Box::new(move || value.as_zval(true)),
250 docs,
251 stub,
252 flags,
253 ));
254 Ok(self)
255 }
256
257 pub fn flags(mut self, flags: ClassFlags) -> Self {
263 self.ce.ce_flags = flags.bits();
264 self
265 }
266
267 pub fn object_override<T: RegisteredClass>(mut self) -> Self {
281 extern "C" fn create_object<T: RegisteredClass>(ce: *mut ClassEntry) -> *mut ZendObject {
282 if let Some(instance) = T::default_init() {
289 let obj = ZendClassObject::<T>::new(instance).into_raw();
290 return unsafe { (*obj).get_mut_zend_obj() };
293 }
294
295 let obj = unsafe { ZendClassObject::<T>::new_uninit(ce.as_ref()) }.into_raw();
298 unsafe { (*obj).get_mut_zend_obj() }
300 }
301
302 zend_fastcall! {
303 extern fn constructor<T: RegisteredClass>(ex: &mut ExecuteData, _: &mut Zval) {
304 crate::zend::run_handler(std::panic::AssertUnwindSafe(|| {
305 let Some(ConstructorMeta { constructor, .. }) = T::constructor() else {
306 PhpException::from_message("You cannot instantiate this class from PHP.".into())
307 .throw();
308 return;
309 };
310
311 let this = match constructor(ex) {
312 ConstructorResult::Ok(this) => this,
313 ConstructorResult::Exception(e) => {
314 e.throw();
315 return;
316 }
317 ConstructorResult::ArgError => return,
318 };
319
320 let Some(this_obj) = ex.get_object_uninit::<T>() else {
323 PhpException::from_message("Failed to retrieve reference to `this` object.".into())
324 .throw();
325 return;
326 };
327
328 this_obj.initialize(this);
329 }));
330 }
331 }
332
333 debug_assert_eq!(
334 self.name.as_str(),
335 T::CLASS_NAME,
336 "Class name in builder does not match class name in `impl RegisteredClass`."
337 );
338 self.object_override = Some(create_object::<T>);
339 let is_interface = T::FLAGS.contains(ClassFlags::Interface);
340
341 if let Some(ConstructorMeta {
344 build_fn, flags, ..
345 }) = T::constructor()
346 {
347 let func = if is_interface {
348 FunctionBuilder::new_abstract("__construct")
349 } else {
350 FunctionBuilder::new("__construct", constructor::<T>)
351 };
352 let visibility = flags.unwrap_or(MethodFlags::Public);
353 self.method(build_fn(func), visibility)
354 } else if is_interface {
355 self
357 } else {
358 let func = FunctionBuilder::new("__construct", constructor::<T>);
360 self.method(func, MethodFlags::Public)
361 }
362 }
363
364 pub fn registration(mut self, register: fn(&'static mut ClassEntry, ArgInfoTables)) -> Self {
383 self.register = Some(register);
384 self
385 }
386
387 pub fn docs(mut self, docs: DocComments) -> Self {
393 self.docs = docs;
394 self
395 }
396
397 fn validate(&self) -> Result<()> {
400 if let Some(name) = first_duplicate(self.properties.iter().map(|p| p.name.as_str()), false)
401 {
402 return Err(Error::DuplicateProperty {
403 property: format!("{}::${name}", self.name),
404 });
405 }
406 if let Some(name) = first_duplicate(self.methods.iter().map(|(m, _)| m.name.as_str()), true)
407 {
408 return Err(Error::DuplicateMethod {
409 method: format!("{}::{name}", self.name),
410 });
411 }
412 if let Some(name) = first_duplicate(self.constants.iter().map(|c| c.0.as_str()), false) {
413 return Err(Error::DuplicateConstant {
414 constant: format!("{}::{name}", self.name),
415 });
416 }
417 Ok(())
418 }
419
420 pub fn register(mut self) -> Result<()> {
438 assert!(
439 !ExecutorGlobals::get().current_module.is_null(),
440 "Classes can only be registered from a module startup (MINIT) function: \
441 `do_register_internal_class` dereferences `EG(current_module)`."
442 );
443 self.validate()?;
444 ensure_class_name_free(&self.name)?;
445
446 self.ce.name = ZendStr::new_interned(&self.name, true).into_raw();
447
448 let mut arg_info = Vec::with_capacity(self.methods.len());
449 let mut methods = Vec::with_capacity(self.methods.len() + 1);
450 for (method, flags) in self.methods {
451 let (mut entry, args) = method.build()?;
452 entry.flags |= flags.bits();
453 methods.push(entry);
454 arg_info.push(args);
455 }
456
457 let arg_info = ManuallyDrop::new(arg_info.into_boxed_slice());
464
465 methods.push(FunctionEntry::end());
466 let entries = Box::into_raw(methods.into_boxed_slice());
467 self.ce.info.internal.builtin_functions = entries.cast::<FunctionEntry>().cast_const();
468
469 let class = if self.ce.flags().contains(ClassFlags::Interface) {
470 unsafe { zend_register_internal_interface(&raw mut self.ce) }
471 } else {
472 let parent = match self.extends {
476 Some((ptr, _)) => ptr::from_ref(ptr()).cast_mut(),
477 None => std::ptr::null_mut(),
478 };
479 unsafe { zend_register_internal_class_ex(&raw mut self.ce, parent) }
480 };
481
482 unsafe { free_registered_entries(entries) };
485
486 let class = unsafe { class.as_mut() }.ok_or(Error::InvalidPointer)?;
487 class.info.internal.builtin_functions = ptr::null();
488
489 if self.object_override.is_some() {
491 cfg_if::cfg_if! {
492 if #[cfg(php81)] {
493 class.ce_flags |= ClassFlags::NotSerializable.bits();
494 } else {
495 class.serialize = Some(crate::ffi::zend_class_serialize_deny);
496 class.unserialize = Some(crate::ffi::zend_class_unserialize_deny);
497 }
498 }
499 }
500
501 for (iface, _) in self.interfaces {
502 let interface = iface();
503 assert!(
504 interface.is_interface(),
505 "Given class entry was not an interface."
506 );
507
508 unsafe { zend_do_implement_interface(class, ptr::from_ref(interface).cast_mut()) };
509 }
510
511 for prop in self.properties {
512 let mut default_zval = match prop.default {
513 Some(f) => f()?,
514 None => Zval::new(),
515 };
516 unsafe {
517 zend_declare_property(
518 class,
519 CString::new(prop.name.as_str())?.as_ptr(),
520 prop.name.len() as _,
521 &raw mut default_zval,
522 prop.flags.bits().try_into()?,
523 );
524 }
525 }
526
527 for (name, value, _, _, flags) in self.constants {
528 let name = ZendStr::new_interned(&name, true);
531 let mut value = ManuallyDrop::new(value()?);
536 unsafe {
537 zend_declare_class_constant_ex(
538 class,
539 name.as_ptr().cast_mut(),
540 &raw mut *value,
541 flags.bits().try_into()?,
542 ptr::null_mut(),
543 );
544 };
545 }
546
547 if let Some(object_override) = self.object_override {
548 class.__bindgen_anon_2.create_object = Some(object_override);
549 }
550
551 if let Some(register) = self.register {
552 register(class, ManuallyDrop::into_inner(arg_info));
553 } else {
554 panic!("Class {} was not registered.", self.name);
555 }
556
557 Ok(())
558 }
559}
560
561#[cfg(test)]
562mod tests {
563 use crate::test::test_function;
564
565 use super::*;
566
567 #[test]
568 #[allow(unpredictable_function_pointer_comparisons)]
569 fn test_new() {
570 let class = ClassBuilder::new("Foo");
571 assert_eq!(class.name, "Foo");
572 assert_eq!(class.extends, None);
573 assert_eq!(class.interfaces, vec![]);
574 assert_eq!(class.methods.len(), 0);
575 assert_eq!(class.object_override, None);
576 assert!(class.properties.is_empty());
577 assert_eq!(class.constants.len(), 0);
578 assert_eq!(class.register, None);
579 assert_eq!(class.docs, &[] as DocComments);
580 }
581
582 #[test]
583 fn test_extends() {
584 let extends: ClassEntryInfo = (|| todo!(), "Bar");
585 let class = ClassBuilder::new("Foo").extends(extends);
586 assert_eq!(class.extends, Some(extends));
587 }
588
589 #[test]
590 fn test_implements() {
591 let implements: ClassEntryInfo = (|| todo!(), "Bar");
592 let class = ClassBuilder::new("Foo").implements(implements);
593 assert_eq!(class.interfaces, vec![implements]);
594 }
595
596 #[test]
597 fn test_method() {
598 let method = FunctionBuilder::new("foo", test_function);
599 let class = ClassBuilder::new("Foo").method(method, MethodFlags::Public);
600 assert_eq!(class.methods.len(), 1);
601 }
602
603 #[test]
604 fn test_property() {
605 let class = ClassBuilder::new("Foo").property(ClassProperty {
606 name: "bar".into(),
607 flags: PropertyFlags::Public,
608 default: None,
609 docs: &["Doc 1"],
610 ty: Some(DataType::String),
611 nullable: false,
612 readonly: false,
613 default_stub: None,
614 });
615 assert_eq!(class.properties.len(), 1);
616 assert_eq!(class.properties[0].name, "bar");
617 assert_eq!(class.properties[0].flags, PropertyFlags::Public);
618 assert!(class.properties[0].default.is_none());
619 assert_eq!(class.properties[0].docs, &["Doc 1"] as DocComments);
620 assert_eq!(class.properties[0].ty, Some(DataType::String));
621 }
622
623 fn named_property(name: &str) -> ClassProperty {
624 ClassProperty {
625 name: name.into(),
626 flags: PropertyFlags::Public,
627 default: None,
628 docs: &[],
629 ty: None,
630 nullable: false,
631 readonly: false,
632 default_stub: None,
633 }
634 }
635
636 fn with_constant(mut class: ClassBuilder, name: &str) -> ClassBuilder {
637 class.constants.push((
638 name.into(),
639 Box::new(|| Err(Error::InvalidPointer)),
640 &[],
641 String::new(),
642 ConstantFlags::Public,
643 ));
644 class
645 }
646
647 #[test]
648 fn first_duplicate_folds_ascii_case_only_when_asked() {
649 assert_eq!(first_duplicate(["a", "b", "a"], false), Some("a"));
650 assert_eq!(first_duplicate(["a", "B", "b"], false), None);
651 assert_eq!(first_duplicate(["a", "B", "b"], true), Some("b"));
652 assert_eq!(first_duplicate(["ä", "Ä"], true), None);
653 assert_eq!(first_duplicate([], true), None);
654 }
655
656 #[test]
657 fn validate_rejects_a_repeated_property() {
658 let mut class = ClassBuilder::new("Foo");
659 class.properties = vec![named_property("a"), named_property("a")];
660 assert!(matches!(
661 class.validate(),
662 Err(Error::DuplicateProperty { property }) if property == "Foo::$a"
663 ));
664 }
665
666 #[test]
667 fn validate_rejects_methods_that_differ_only_by_case() {
668 let class = ClassBuilder::new("Foo")
669 .method(
670 FunctionBuilder::new("run", test_function),
671 MethodFlags::Public,
672 )
673 .method(
674 FunctionBuilder::new("RUN", test_function),
675 MethodFlags::Static,
676 );
677 assert!(matches!(
678 class.validate(),
679 Err(Error::DuplicateMethod { method }) if method == "Foo::RUN"
680 ));
681 }
682
683 #[test]
684 fn validate_compares_constants_exactly() {
685 let class = with_constant(with_constant(ClassBuilder::new("Foo"), "A"), "a");
686 assert!(class.validate().is_ok());
687 let class = with_constant(class, "A");
688 assert!(matches!(
689 class.validate(),
690 Err(Error::DuplicateConstant { constant }) if constant == "Foo::A"
691 ));
692 }
693
694 #[test]
695 #[cfg(feature = "embed")]
696 fn class_names_already_in_the_class_table_are_taken() {
697 let ran = crate::embed::Embed::run(|| {
698 assert!(matches!(
699 ensure_class_name_free("STDCLASS"),
700 Err(Error::DuplicateClass { class }) if class == "STDCLASS"
701 ));
702 assert!(ensure_class_name_free("ExtPhpRsNoSuchClass").is_ok());
703 true
704 });
705 assert!(ran);
706 }
707
708 #[test]
709 fn validate_accepts_distinct_members() {
710 let mut class = ClassBuilder::new("Foo")
711 .method(
712 FunctionBuilder::new("run", test_function),
713 MethodFlags::Public,
714 )
715 .method(
716 FunctionBuilder::new("stop", test_function),
717 MethodFlags::Public,
718 );
719 class.properties = vec![named_property("a"), named_property("b")];
720 assert!(with_constant(class, "A").validate().is_ok());
721 }
722
723 #[test]
724 #[cfg(feature = "embed")]
725 fn test_constant() {
726 let class = ClassBuilder::new("Foo")
727 .constant("bar", 42, &["Doc 1"], ConstantFlags::Public)
728 .expect("Failed to create constant");
729 assert_eq!(class.constants.len(), 1);
730 assert_eq!(class.constants[0].0, "bar");
731 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
732 }
733
734 #[test]
735 #[cfg(feature = "embed")]
736 fn test_dyn_constant() {
737 let class = ClassBuilder::new("Foo")
738 .dyn_constant("bar", &42, &["Doc 1"], ConstantFlags::Public)
739 .expect("Failed to create constant");
740 assert_eq!(class.constants.len(), 1);
741 assert_eq!(class.constants[0].0, "bar");
742 assert_eq!(class.constants[0].2, &["Doc 1"] as DocComments);
743 }
744
745 #[test]
746 fn test_flags() {
747 let class = ClassBuilder::new("Foo").flags(ClassFlags::Abstract);
748 assert_eq!(class.ce.ce_flags, ClassFlags::Abstract.bits());
749 }
750
751 #[test]
752 fn test_registration() {
753 let class = ClassBuilder::new("Foo").registration(|_, _| {});
754 assert!(class.register.is_some());
755 }
756
757 #[test]
758 fn test_registration_interface() {
759 let class = ClassBuilder::new("Foo")
760 .flags(ClassFlags::Interface)
761 .registration(|_, _| {});
762 assert!(class.register.is_some());
763 }
764
765 #[test]
766 fn test_docs() {
767 let class = ClassBuilder::new("Foo").docs(&["Doc 1"]);
768 assert_eq!(class.docs, &["Doc 1"] as DocComments);
769 }
770
771 }