From 22d6df245a6dc12b608a1527dc90267a4a5f5035 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 27 Jul 2024 15:45:16 +0200 Subject: [PATCH 1/3] Allow ZEND_ACC_VIRTUAL to be used to not have property backing storage without resorting to hooks This is useful to reduce the memory usage of objects that don't actually use the backing storage. Examples are XMLReader and DOM. When the properties were added to the stubs, these objects became much much bigger, which is a waste of memory. Closes GH-11644. Work towards GH-13988. --- build/gen_stub.php | 11 +++++++++++ ext/reflection/php_reflection.c | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 1d92ddd46b9ee..bbdd8a85f145f 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -2916,6 +2916,7 @@ class PropertyInfo extends VariableLike public ?Expr $defaultValue; public ?string $defaultValueString; public bool $isDocReadonly; + public bool $isVirtual; /** * @var AttributeInfo[] $attributes @@ -2929,6 +2930,7 @@ public function __construct( ?Expr $defaultValue, ?string $defaultValueString, bool $isDocReadonly, + bool $isVirtual, ?string $link, ?int $phpVersionIdMinimumCompatibility, array $attributes, @@ -2939,6 +2941,7 @@ public function __construct( $this->defaultValue = $defaultValue; $this->defaultValueString = $defaultValueString; $this->isDocReadonly = $isDocReadonly; + $this->isVirtual = $isVirtual; parent::__construct($flags, $type, $phpDocType, $link, $phpVersionIdMinimumCompatibility, $attributes, $exposedDocComment); } @@ -3054,6 +3057,10 @@ protected function getFlagsByPhpVersion(): array $flags = $this->addFlagForVersionsAbove($flags, "ZEND_ACC_READONLY", PHP_82_VERSION_ID); } + if ($this->isVirtual) { + $flags = $this->addFlagForVersionsAbove($flags, "ZEND_ACC_VIRTUAL", PHP_84_VERSION_ID); + } + return $flags; } @@ -4433,6 +4440,7 @@ function parseProperty( ): PropertyInfo { $phpDocType = null; $isDocReadonly = false; + $isVirtual = false; $link = null; if ($comments) { @@ -4444,6 +4452,8 @@ function parseProperty( $isDocReadonly = true; } elseif ($tag->name === 'link') { $link = $tag->value; + } elseif ($tag->name === 'virtual') { + $isVirtual = true; } } } @@ -4472,6 +4482,7 @@ function parseProperty( $property->default, $property->default ? $prettyPrinter->prettyPrintExpr($property->default) : null, $isDocReadonly, + $isVirtual, $link, $phpVersionIdMinimumCompatibility, $attributes, diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 0956bbc1acf70..1d26c80f222bf 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -6041,7 +6041,7 @@ ZEND_METHOD(ReflectionProperty, getSettableType) } /* Get-only virtual property can never be written to. */ - if ((prop->flags & ZEND_ACC_VIRTUAL) && !prop->hooks[ZEND_PROPERTY_HOOK_SET]) { + if (prop->hooks && (prop->flags & ZEND_ACC_VIRTUAL) && !prop->hooks[ZEND_PROPERTY_HOOK_SET]) { zend_type never_type = ZEND_TYPE_INIT_CODE(IS_NEVER, 0, 0); reflection_type_factory(never_type, return_value, 0); return; From 6e61bc4d6ea5be6c46909f7d518e3da8b6adc0c8 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 27 Jul 2024 15:50:00 +0200 Subject: [PATCH 2/3] Use virtual annotation in XMLReader All properties of XMLReader are virtual and therefore don't need backing storage. --- ext/xmlreader/php_xmlreader.stub.php | 14 +++++++++ ext/xmlreader/php_xmlreader_arginfo.h | 30 +++++++++--------- ext/xmlreader/tests/virtual_properties.phpt | 34 +++++++++++++++++++++ 3 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 ext/xmlreader/tests/virtual_properties.phpt diff --git a/ext/xmlreader/php_xmlreader.stub.php b/ext/xmlreader/php_xmlreader.stub.php index c58ee2460ff02..d319037066046 100644 --- a/ext/xmlreader/php_xmlreader.stub.php +++ b/ext/xmlreader/php_xmlreader.stub.php @@ -99,32 +99,46 @@ class XMLReader public const int SUBST_ENTITIES = UNKNOWN; + /** @virtual */ public int $attributeCount; + /** @virtual */ public string $baseURI; + /** @virtual */ public int $depth; + /** @virtual */ public bool $hasAttributes; + /** @virtual */ public bool $hasValue; + /** @virtual */ public bool $isDefault; + /** @virtual */ public bool $isEmptyElement; + /** @virtual */ public string $localName; + /** @virtual */ public string $name; + /** @virtual */ public string $namespaceURI; + /** @virtual */ public int $nodeType; + /** @virtual */ public string $prefix; + /** @virtual */ public string $value; + /** @virtual */ public string $xmlLang; /** @tentative-return-type */ diff --git a/ext/xmlreader/php_xmlreader_arginfo.h b/ext/xmlreader/php_xmlreader_arginfo.h index a5a0c12cd2593..d4ee1c9d8f1c9 100644 --- a/ext/xmlreader/php_xmlreader_arginfo.h +++ b/ext/xmlreader/php_xmlreader_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 551324d130f9755c4c61cebb5084953fb6f539c4 */ + * Stub hash: 80288a0f40eabc7802a928963386616ea31e448d */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_XMLReader_close, 0, 0, IS_TRUE, 0) ZEND_END_ARG_INFO() @@ -313,85 +313,85 @@ static zend_class_entry *register_class_XMLReader(void) zval property_attributeCount_default_value; ZVAL_UNDEF(&property_attributeCount_default_value); zend_string *property_attributeCount_name = zend_string_init("attributeCount", sizeof("attributeCount") - 1, 1); - zend_declare_typed_property(class_entry, property_attributeCount_name, &property_attributeCount_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_declare_typed_property(class_entry, property_attributeCount_name, &property_attributeCount_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release(property_attributeCount_name); zval property_baseURI_default_value; ZVAL_UNDEF(&property_baseURI_default_value); zend_string *property_baseURI_name = zend_string_init("baseURI", sizeof("baseURI") - 1, 1); - zend_declare_typed_property(class_entry, property_baseURI_name, &property_baseURI_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_baseURI_name, &property_baseURI_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_baseURI_name); zval property_depth_default_value; ZVAL_UNDEF(&property_depth_default_value); zend_string *property_depth_name = zend_string_init("depth", sizeof("depth") - 1, 1); - zend_declare_typed_property(class_entry, property_depth_name, &property_depth_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_declare_typed_property(class_entry, property_depth_name, &property_depth_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release(property_depth_name); zval property_hasAttributes_default_value; ZVAL_UNDEF(&property_hasAttributes_default_value); zend_string *property_hasAttributes_name = zend_string_init("hasAttributes", sizeof("hasAttributes") - 1, 1); - zend_declare_typed_property(class_entry, property_hasAttributes_name, &property_hasAttributes_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); + zend_declare_typed_property(class_entry, property_hasAttributes_name, &property_hasAttributes_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); zend_string_release(property_hasAttributes_name); zval property_hasValue_default_value; ZVAL_UNDEF(&property_hasValue_default_value); zend_string *property_hasValue_name = zend_string_init("hasValue", sizeof("hasValue") - 1, 1); - zend_declare_typed_property(class_entry, property_hasValue_name, &property_hasValue_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); + zend_declare_typed_property(class_entry, property_hasValue_name, &property_hasValue_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); zend_string_release(property_hasValue_name); zval property_isDefault_default_value; ZVAL_UNDEF(&property_isDefault_default_value); zend_string *property_isDefault_name = zend_string_init("isDefault", sizeof("isDefault") - 1, 1); - zend_declare_typed_property(class_entry, property_isDefault_name, &property_isDefault_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); + zend_declare_typed_property(class_entry, property_isDefault_name, &property_isDefault_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); zend_string_release(property_isDefault_name); zval property_isEmptyElement_default_value; ZVAL_UNDEF(&property_isEmptyElement_default_value); zend_string *property_isEmptyElement_name = zend_string_init("isEmptyElement", sizeof("isEmptyElement") - 1, 1); - zend_declare_typed_property(class_entry, property_isEmptyElement_name, &property_isEmptyElement_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); + zend_declare_typed_property(class_entry, property_isEmptyElement_name, &property_isEmptyElement_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL)); zend_string_release(property_isEmptyElement_name); zval property_localName_default_value; ZVAL_UNDEF(&property_localName_default_value); zend_string *property_localName_name = zend_string_init("localName", sizeof("localName") - 1, 1); - zend_declare_typed_property(class_entry, property_localName_name, &property_localName_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_localName_name, &property_localName_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_localName_name); zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); - zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_name_name); zval property_namespaceURI_default_value; ZVAL_UNDEF(&property_namespaceURI_default_value); zend_string *property_namespaceURI_name = zend_string_init("namespaceURI", sizeof("namespaceURI") - 1, 1); - zend_declare_typed_property(class_entry, property_namespaceURI_name, &property_namespaceURI_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_namespaceURI_name, &property_namespaceURI_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_namespaceURI_name); zval property_nodeType_default_value; ZVAL_UNDEF(&property_nodeType_default_value); zend_string *property_nodeType_name = zend_string_init("nodeType", sizeof("nodeType") - 1, 1); - zend_declare_typed_property(class_entry, property_nodeType_name, &property_nodeType_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_declare_typed_property(class_entry, property_nodeType_name, &property_nodeType_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release(property_nodeType_name); zval property_prefix_default_value; ZVAL_UNDEF(&property_prefix_default_value); zend_string *property_prefix_name = zend_string_init("prefix", sizeof("prefix") - 1, 1); - zend_declare_typed_property(class_entry, property_prefix_name, &property_prefix_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_prefix_name, &property_prefix_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_prefix_name); zval property_value_default_value; ZVAL_UNDEF(&property_value_default_value); zend_string *property_value_name = zend_string_init("value", sizeof("value") - 1, 1); - zend_declare_typed_property(class_entry, property_value_name, &property_value_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_value_name, &property_value_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_value_name); zval property_xmlLang_default_value; ZVAL_UNDEF(&property_xmlLang_default_value); zend_string *property_xmlLang_name = zend_string_init("xmlLang", sizeof("xmlLang") - 1, 1); - zend_declare_typed_property(class_entry, property_xmlLang_name, &property_xmlLang_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); + zend_declare_typed_property(class_entry, property_xmlLang_name, &property_xmlLang_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_VIRTUAL, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); zend_string_release(property_xmlLang_name); return class_entry; diff --git a/ext/xmlreader/tests/virtual_properties.phpt b/ext/xmlreader/tests/virtual_properties.phpt new file mode 100644 index 0000000000000..57499292b8ce0 --- /dev/null +++ b/ext/xmlreader/tests/virtual_properties.phpt @@ -0,0 +1,34 @@ +--TEST-- +Virtual property tests +--EXTENSIONS-- +xmlreader +--FILE-- +getProperty("nodeType"); +var_dump($prop->isVirtual()); +var_dump($prop->getSettableType()); +var_dump($prop->getHooks()); +var_dump($prop->getRawValue(new XMLReader)); +var_dump($prop->getValue(new XMLReader)); + +$reader = XMLReader::XML("hi"); +var_dump(json_encode($reader)); +var_export($reader); echo "\n"; +var_dump(get_object_vars($reader)); + +?> +--EXPECTF-- +bool(true) +object(ReflectionNamedType)#%d (0) { +} +array(0) { +} +int(0) +int(0) +string(2) "{}" +\XMLReader::__set_state(array( +)) +array(0) { +} From fd0d738cf4669c2a87fae206c8bf9fb4707685d7 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:53:23 +0200 Subject: [PATCH 3/3] Improve virtual property error message Co-authored-by: Ilija Tovilo --- Zend/tests/property_hooks/unserialize.phpt | 2 +- ext/standard/var_unserializer.re | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/tests/property_hooks/unserialize.phpt b/Zend/tests/property_hooks/unserialize.phpt index 2461817a08814..b8834e1d39ff4 100644 --- a/Zend/tests/property_hooks/unserialize.phpt +++ b/Zend/tests/property_hooks/unserialize.phpt @@ -54,7 +54,7 @@ object(Test)#2 (1) { Test::$prop3::get Test::$prop3::set -Warning: unserialize(): Cannot unserialize value for hooked property Test::$prop3 in %s on line %d +Warning: unserialize(): Cannot unserialize value for virtual property Test::$prop3 in %s on line %d Warning: unserialize(): Error at offset 26 of 32 bytes in %s on line %d bool(false) diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index 78d102eefda6c..cbd457e16fdb1 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -561,7 +561,7 @@ static int is_property_visibility_changed(zend_class_entry *ce, zval *key) return 1; } else { php_error_docref(NULL, E_WARNING, - "Cannot unserialize value for hooked property %s::$%s", + "Cannot unserialize value for virtual property %s::$%s", ZSTR_VAL(existing_propinfo->ce->name), Z_STRVAL_P(key)); zval_ptr_dtor_str(key); return -1;