diff --git a/Zend/tests/attributes/not_serializable/001-base.phpt b/Zend/tests/attributes/not_serializable/001-base.phpt new file mode 100644 index 0000000000000..659d64a089841 --- /dev/null +++ b/Zend/tests/attributes/not_serializable/001-base.phpt @@ -0,0 +1,29 @@ +--TEST-- +#[NotSerializable] basic behavior +--FILE-- +getMessage(), "\n"; +} + +try { + $data = 'O:3:"Foo":1:{s:1:"x";i:42;}'; + unserialize($data); + echo "Should not reach here\n"; +} catch (Throwable $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +Serialization of 'Foo' is not allowed +Unserialization of 'Foo' is not allowed diff --git a/Zend/tests/attributes/not_serializable/002-inheritance.phpt b/Zend/tests/attributes/not_serializable/002-inheritance.phpt new file mode 100644 index 0000000000000..b8e8f709e1a8e --- /dev/null +++ b/Zend/tests/attributes/not_serializable/002-inheritance.phpt @@ -0,0 +1,31 @@ +--TEST-- +#[NotSerializable] inheritance behavior +--FILE-- +getMessage(), "\n"; +} + +try { + $data = 'O:3:"Bar":1:{s:1:"x";i:42;}'; + unserialize($data); + echo "Should not reach here\n"; +} catch (Throwable $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +Serialization of 'Bar' is not allowed +Unserialization of 'Bar' is not allowed diff --git a/Zend/tests/attributes/not_serializable/003-interface.phpt b/Zend/tests/attributes/not_serializable/003-interface.phpt new file mode 100644 index 0000000000000..75a2e5691544d --- /dev/null +++ b/Zend/tests/attributes/not_serializable/003-interface.phpt @@ -0,0 +1,13 @@ +--TEST-- +#[NotSerializable] interface behavior +--FILE-- + +--EXPECTF-- +Fatal error: Cannot apply #[\NotSerializable] to interface Foo in %s on line %d diff --git a/Zend/tests/attributes/not_serializable/004-traits.phpt b/Zend/tests/attributes/not_serializable/004-traits.phpt new file mode 100644 index 0000000000000..6ec39b0eda3be --- /dev/null +++ b/Zend/tests/attributes/not_serializable/004-traits.phpt @@ -0,0 +1,13 @@ +--TEST-- +#[NotSerializable] traits behavior +--FILE-- + +--EXPECTF-- +Fatal error: Cannot apply #[\NotSerializable] to trait Foo in %s on line %d diff --git a/Zend/tests/attributes/not_serializable/005-enum.phpt b/Zend/tests/attributes/not_serializable/005-enum.phpt new file mode 100644 index 0000000000000..df5359c01d289 --- /dev/null +++ b/Zend/tests/attributes/not_serializable/005-enum.phpt @@ -0,0 +1,29 @@ +--TEST-- +#[NotSerializable] enum behavior +--FILE-- +getMessage(), "\n"; +} + +try { + $data = 'E:7:"Foo:BAR";'; + unserialize($data); + echo "Should not reach here\n"; +} catch (Throwable $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +Serialization of 'Foo' is not allowed +Unserialization of 'Foo' is not allowed diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index b69e192701e48..39d7f6f33714d 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -33,6 +33,7 @@ ZEND_API zend_class_entry *zend_ce_override; ZEND_API zend_class_entry *zend_ce_deprecated; ZEND_API zend_class_entry *zend_ce_nodiscard; ZEND_API zend_class_entry *zend_ce_delayed_target_validation; +ZEND_API zend_class_entry *zend_ce_not_serializable; static zend_object_handlers attributes_object_handlers_sensitive_parameter_value; @@ -242,6 +243,18 @@ static zend_string *validate_nodiscard( return NULL; } +static zend_string *validate_not_serializable( + zend_attribute *attr, uint32_t target, zend_class_entry *scope) +{ + if (scope->ce_flags & (ZEND_ACC_TRAIT|ZEND_ACC_INTERFACE)) { + const char *type = zend_get_object_type_case(scope, false); + return zend_strpprintf(0, "Cannot apply #[\\NotSerializable] to %s %s", type, ZSTR_VAL(scope->name)); + } + + scope->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE; + return NULL; +} + ZEND_METHOD(NoDiscard, __construct) { zend_string *message = NULL; @@ -606,6 +619,10 @@ void zend_register_attribute_ce(void) zend_ce_delayed_target_validation = register_class_DelayedTargetValidation(); attr = zend_mark_internal_attribute(zend_ce_delayed_target_validation); + + zend_ce_not_serializable = register_class_NotSerializable(); + attr = zend_mark_internal_attribute(zend_ce_not_serializable); + attr->validator = validate_not_serializable; } void zend_attributes_shutdown(void) diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php index ded9c89593a36..d07ca74fcec09 100644 --- a/Zend/zend_attributes.stub.php +++ b/Zend/zend_attributes.stub.php @@ -103,3 +103,9 @@ public function __construct(?string $message = null) {} */ #[Attribute(Attribute::TARGET_ALL)] final class DelayedTargetValidation {} + +/** + * @strict-properties + */ +#[Attribute(Attribute::TARGET_CLASS)] +final class NotSerializable {} diff --git a/Zend/zend_attributes_arginfo.h b/Zend/zend_attributes_arginfo.h index ec8d8de4ee508..72a8bc3824ebe 100644 --- a/Zend/zend_attributes_arginfo.h +++ b/Zend/zend_attributes_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: b868cb33f41d9442f42d0cec84e33fcc09f5d88c */ + * Stub hash: ef3a21a6e698ac7755676ec43f5ba0daac63bb76 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Attribute___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "Attribute::TARGET_ALL") @@ -291,3 +291,18 @@ static zend_class_entry *register_class_DelayedTargetValidation(void) return class_entry; } + +static zend_class_entry *register_class_NotSerializable(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "NotSerializable", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES); + + zend_string *attribute_name_Attribute_class_NotSerializable_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, true); + zend_attribute *attribute_Attribute_class_NotSerializable_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_NotSerializable_0, 1); + zend_string_release_ex(attribute_name_Attribute_class_NotSerializable_0, true); + ZVAL_LONG(&attribute_Attribute_class_NotSerializable_0->args[0].value, ZEND_ATTRIBUTE_TARGET_CLASS); + + return class_entry; +} diff --git a/ext/standard/tests/serialize/ref_to_failed_serialize.phpt b/ext/standard/tests/serialize/ref_to_failed_serialize.phpt index 294f30b7b6c12..f5ff2c2f51f8e 100644 --- a/ext/standard/tests/serialize/ref_to_failed_serialize.phpt +++ b/ext/standard/tests/serialize/ref_to_failed_serialize.phpt @@ -3,7 +3,7 @@ References to objects for which Serializable::serialize() returned NULL should u --FILE-- ce_flags & ZEND_ACC_NOT_SERIALIZABLE) { + zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed", + ZSTR_VAL(ce->name)); + goto fail; + } + YYCURSOR += 2; *p = YYCURSOR;