From a3ea91918d350194f8f2773ec169941d02790fff Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Mon, 10 Aug 2026 17:30:06 +0800 Subject: [PATCH] feat(parser): add readonly property unset protection - Check for readonly property when calling unset on object properties - Throw fatal error when attempting to unset readonly properties - Add proper error message with property display name - Include tests for unset operations on readonly properties - Support both regular readonly properties and readonly class properties - Ensure consistent error handling for readonly property access violations --- phpunit/code/unset-readonly-class-property.php | 16 ++++++++++++++++ phpunit/code/unset-readonly-property.php | 17 +++++++++++++++++ phpunit/src/UndefineTest.php | 13 +++++++++++++ src/Parser/PropertyAccessTrait.php | 8 ++++++++ 4 files changed, 54 insertions(+) create mode 100644 phpunit/code/unset-readonly-class-property.php create mode 100644 phpunit/code/unset-readonly-property.php diff --git a/phpunit/code/unset-readonly-class-property.php b/phpunit/code/unset-readonly-class-property.php new file mode 100644 index 00000000..8ddd9b79 --- /dev/null +++ b/phpunit/code/unset-readonly-class-property.php @@ -0,0 +1,16 @@ +value = 1; + } + + public function clear(): void + { + unset($this->value); + } +} diff --git a/phpunit/code/unset-readonly-property.php b/phpunit/code/unset-readonly-property.php new file mode 100644 index 00000000..9ca29ef0 --- /dev/null +++ b/phpunit/code/unset-readonly-property.php @@ -0,0 +1,17 @@ +value = 1; + } +} + +function main(): void +{ + $object = new ReadonlyPropertyUnset(); + unset($object->value); +} diff --git a/phpunit/src/UndefineTest.php b/phpunit/src/UndefineTest.php index 0d53fa76..23e47887 100644 --- a/phpunit/src/UndefineTest.php +++ b/phpunit/src/UndefineTest.php @@ -18,6 +18,19 @@ class UndefineTest extends \BaseTest $this->exec('Attempt to unset static property', 'unset-static-prop.php'); } + public function testUnsetReadonlyPropertyIsRejected(): void + { + $this->exec('Cannot unset readonly property `ReadonlyPropertyUnset::$value`', 'unset-readonly-property.php'); + } + + public function testUnsetReadonlyClassPropertyIsRejected(): void + { + $this->exec( + 'Cannot unset readonly property `ReadonlyClassPropertyUnset::$value`', + 'unset-readonly-class-property.php' + ); + } + public function testPropertyAccessOnUndefinedVar(): void { $this->compile('undefined-prop-access.php'); diff --git a/src/Parser/PropertyAccessTrait.php b/src/Parser/PropertyAccessTrait.php index 4f5aac76..68a0f025 100644 --- a/src/Parser/PropertyAccessTrait.php +++ b/src/Parser/PropertyAccessTrait.php @@ -737,6 +737,14 @@ trait PropertyAccessTrait $propertyId = $this->getPropertyIdentifier($var, $var->var, $var->name); $def = $this->getNativePropertyDef($var); if ($def) { + if ($def->isReadonly()) { + $this->fatalError( + $var, + 'Cannot unset readonly property `' + . $this->getObjectPropertyTypeCheckDisplayName($var) + . '`' + ); + } // Object typed properties are backed by Zend object // properties, so PHP can represent their uninitialized // state after unset(). Keep that behavior instead of