diff --git a/Zend/tests/attributes/no_serialize/001_base.phpt b/Zend/tests/attributes/no_serialize/001_base.phpt new file mode 100644 index 0000000000000..66c19edef24df --- /dev/null +++ b/Zend/tests/attributes/no_serialize/001_base.phpt @@ -0,0 +1,53 @@ +--TEST-- +#[\NoSerialize]: Basic test. +--FILE-- +b1 = 'b'; +var_dump(unserialize(serialize($base))); +var_dump(unserialize('O:14:"Unserializable":2:{s:1:"a";s:1:"a";s:1:"b";s:1:"b";}')); + +?> +--EXPECTF-- +O:4:"Base":2:{s:5:"%0*%0b2";s:1:"b";s:8:"%0Base%0b3";s:1:"c";} +O:5:"Child":4:{s:2:"b1";s:1:"b";s:5:"%0*%0b2";s:1:"b";s:8:"%0Base%0b3";s:1:"c";s:1:"d";s:1:"d";} +object(Base)#%d (3) { + ["b1"]=> + string(1) "a" + ["b2":protected]=> + string(1) "b" + ["b3":"Base":private]=> + string(1) "c" +} +object(Unserializable)#2 (2) { + ["a"]=> + string(1) "a" + ["b"]=> + string(1) "b" +} diff --git a/Zend/tests/attributes/no_serialize/002-warnings.phpt b/Zend/tests/attributes/no_serialize/002-warnings.phpt new file mode 100644 index 0000000000000..e8ca14c488a95 --- /dev/null +++ b/Zend/tests/attributes/no_serialize/002-warnings.phpt @@ -0,0 +1,19 @@ +--TEST-- +#[\NoSerialize]: Warnings test. +--FILE-- + 1; + } +} +?> +--EXPECTF-- +Warning: Static property Base::$b1 is not serializable in %s on line %d + +Warning: Virtual property Base::$b2 is not serializable in %s on line %d diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index b69e192701e48..4ac060a99e3b5 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_no_serialize; static zend_object_handlers attributes_object_handlers_sensitive_parameter_value; @@ -606,6 +607,9 @@ 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_no_serialize = register_class_NoSerialize(); + zend_mark_internal_attribute(zend_ce_no_serialize); } void zend_attributes_shutdown(void) diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h index 10227c2d1e8ef..1450d8adc0236 100644 --- a/Zend/zend_attributes.h +++ b/Zend/zend_attributes.h @@ -51,6 +51,7 @@ extern ZEND_API zend_class_entry *zend_ce_override; extern ZEND_API zend_class_entry *zend_ce_deprecated; extern ZEND_API zend_class_entry *zend_ce_nodiscard; extern ZEND_API zend_class_entry *zend_ce_delayed_target_validation; +extern ZEND_API zend_class_entry *zend_ce_no_serialize; typedef struct { zend_string *name; diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php index ded9c89593a36..d74e52990d953 100644 --- a/Zend/zend_attributes.stub.php +++ b/Zend/zend_attributes.stub.php @@ -103,3 +103,11 @@ public function __construct(?string $message = null) {} */ #[Attribute(Attribute::TARGET_ALL)] final class DelayedTargetValidation {} + +/** + * @strict-properties + */ +#[Attribute(Attribute::TARGET_PROPERTY)] +final class NoSerialize +{ +} diff --git a/Zend/zend_attributes_arginfo.h b/Zend/zend_attributes_arginfo.h index ec8d8de4ee508..ce3c749253867 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: a24033fd1376cbe4914c046344c859deb582d9d0 */ 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_NoSerialize(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "NoSerialize", 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_NoSerialize_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, true); + zend_attribute *attribute_Attribute_class_NoSerialize_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_NoSerialize_0, 1); + zend_string_release_ex(attribute_name_Attribute_class_NoSerialize_0, true); + ZVAL_LONG(&attribute_Attribute_class_NoSerialize_0->args[0].value, ZEND_ATTRIBUTE_TARGET_PROPERTY); + + return class_entry; +} diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index a0215611b610d..b4f46b3299dd1 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8000,6 +8000,16 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32 if (override_attribute) { prop->flags |= ZEND_ACC_OVERRIDE; } + + zend_attribute *no_serialize_attribute = zend_get_attribute_str(prop->attributes, "noserialize", sizeof("noserialize")-1); + if (no_serialize_attribute) { + if (prop->flags & ZEND_ACC_VIRTUAL) { + zend_error(E_COMPILE_WARNING, + "Virtual property %s::$%s is not serializable", + ZSTR_VAL(scope->name), ZSTR_VAL(name)); + } + prop->flags |= ZEND_ACC_NO_SERIALIZE; + } } } } @@ -8989,6 +8999,21 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f if (override_attribute) { info->flags |= ZEND_ACC_OVERRIDE; } + + zend_attribute *no_serialize_attribute = zend_get_attribute_str(info->attributes, "noserialize", sizeof("noserialize")-1); + if (no_serialize_attribute) { + if (info->flags & ZEND_ACC_STATIC) { + zend_error(E_COMPILE_WARNING, + "Static property %s::$%s is not serializable", + ZSTR_VAL(ce->name), ZSTR_VAL(name)); + } + if (info->flags & ZEND_ACC_VIRTUAL) { + zend_error(E_COMPILE_WARNING, + "Virtual property %s::$%s is not serializable", + ZSTR_VAL(ce->name), ZSTR_VAL(name)); + } + info->flags |= ZEND_ACC_NO_SERIALIZE; + } } CG(context).active_property_info_name = old_active_property_info_name; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 8321e4a03c1e3..97aea6c3d3aa3 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -261,7 +261,7 @@ typedef struct _zend_oparray_context { /* has #[\Override] attribute | | | */ #define ZEND_ACC_OVERRIDE (1 << 28) /* | X | X | */ /* | | | */ -/* Property Flags (unused: 13-27,29...) | | | */ +/* Property Flags (unused: 13-27,30...) | | | */ /* =========== | | | */ /* | | | */ /* Promoted property / parameter | | | */ @@ -275,6 +275,8 @@ typedef struct _zend_oparray_context { #define ZEND_ACC_PROTECTED_SET (1 << 11) /* | | X | */ #define ZEND_ACC_PRIVATE_SET (1 << 12) /* | | X | */ /* | | | */ +#define ZEND_ACC_NO_SERIALIZE (1 << 29) /* | | X | */ +/* | | | */ /* Class Flags (unused: 31) | | | */ /* =========== | | | */ /* | | | */ diff --git a/ext/standard/var.c b/ext/standard/var.c index 4df86f49434a0..f04d0e7449bc8 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -1248,7 +1248,7 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_ count = ce->default_properties_count; for (i = 0; i < ce->default_properties_count; i++) { prop_info = ce->properties_info_table[i]; - if (!prop_info) { + if (!prop_info || prop_info->flags & ZEND_ACC_NO_SERIALIZE) { count--; continue; } @@ -1263,7 +1263,7 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_ smart_str_appendl(buf, ":{", 2); for (i = 0; i < ce->default_properties_count; i++) { prop_info = ce->properties_info_table[i]; - if (!prop_info) { + if (!prop_info || prop_info->flags & ZEND_ACC_NO_SERIALIZE) { continue; } prop = OBJ_PROP(obj, prop_info->offset);