[GIT-PULLS] [php-src] PR #22822: ext/date: guard property-table reads in php_date_interval_initialize_from_hash()
[email protected] (brzuchal) Mon, 20 Jul 2026 09:16:28 +0000
| Newsgroups | php.git-pulls |
|---|---|
| Message-ID | <[email protected]> |
Pull Request: https://github.com/php/php-src/pull/22822
Author: brzuchal
### Problem
`php_date_interval_initialize_from_hash()` reads the interval fields out of a `HashTable`
that, for an uninitialized `DateInterval`, is the object's **raw property table**:
`date_object_get_properties_interval()` returns `zend_std_get_properties()` unchanged when
`!intervalobj->initialized`.
A raw property table stores declared properties as `IS_INDIRECT` slots. Two of the fields are
read open-coded without a type guard and hand that zval straight to `zval_get_double()` /
`zval_get_long()`, which have no `case IS_INDIRECT:` and fall into `ZEND_UNREACHABLE()`.
### Reproducer
```php
<?php
class B extends DateInterval { public float $f = 1.5; }
$o = (new ReflectionClass('B'))->newInstanceWithoutConstructor();
$o->__wakeup();
var_dump($o->f);
```
Debug build:
```
Assertion failed: (0), function zval_get_double_func, file zend_operators.c, line 1056.
```
Release build: no diagnostic at all — the declared value is silently discarded.
`newInstanceWithoutConstructor()` is what leaves `initialized` false. `unserialize()` is not a
route here, because `DateInterval` defines `__unserialize()` and that takes precedence over
`__wakeup()`.
### Fix
The `PHP_DATE_INTERVAL_READ_PROPERTY()` family already accounts for this — all three variants
(plain, `_I64`, `_DAYS`) guard with `Z_TYPE_P(z_arg) <= IS_STRING`, which excludes
`IS_INDIRECT` (12). Eighteen fields are read through them and are safe. Only `"f"` and
`"civil_or_wall"` are read open-coded, and they lack the guard; this applies the same guard to
both.
`ext/zlib` solves the same class of problem at the call site with `ZVAL_DEINDIRECT()`; the
comment there notes that the `|H` ZPP specifier may leave `HashTable` entries wrapped in
`IS_INDIRECT`.
### Behaviour
On an uninitialized instance, a `DateInterval` subclass declaring `$f` or `$civil_or_wall` now
has those fields ignored deterministically — exactly as the eighteen sibling fields already
are — instead of hitting undefined behaviour. Nothing else changes: a plain `DateInterval`,
dynamic properties, and the normal serialize/unserialize round-trip are unaffected.
### Tests
New `ext/date/tests/date_interval_wakeup_declared_property.phpt` covers the
declared-initialized, declared-uninitialized and `civil_or_wall` cases, plus a
dynamic-property control (still read correctly) and a serialize/unserialize round-trip.
`ext/date/tests`: 680/680 pass, debug build.