Skip to content

bug(reflection): _IS_BOOL/_IS_NUMBER constants predate PHP 8.1's IS_NEVER, so boolean casts dispatch on the wrong type id #152

Description

@lisachenko

Summary

ReflectionValue on the 8.4 branch still declares the pre-8.1 fake-type ids:

// src/Reflection/ReflectionValue.php:135-136
public const _IS_BOOL   = 17;
public const _IS_NUMBER = 18;

PHP 8.1 inserted IS_NEVER = 17 into the type table (Zend/zend_types.h), shifting the cast-only ids to:

#define IS_NEVER    17
#define _IS_BOOL    18
#define _IS_NUMBER  19

So on PHP 8.4 the engine passes 18 to a cast_object handler for a (bool) cast, which a userland __cast implementation written against ReflectionValue::_IS_BOOL (17) never matches — and worse, ReflectionValue::_IS_NUMBER (18) does match it, so boolean casts get routed into the numeric branch. IS_NEVER is also missing from the constant table entirely.

Reproduction (PHP 8.4.19 NTS, 8.4 branch, ffi.enable=1, opcache.jit=off)

final class Probe implements ObjectCreateInterface, ObjectCastInterface {
    use ObjectCreateTrait;
    public static function __cast(CastObjectHook $hook): mixed {
        error_log('cast type = ' . $hook->getCastType());
        return true;
    }
}
(new ReflectionClass(Probe::class))->installExtensionHandlers();
$b = (bool) new Probe();   // logs "cast type = 18", not ReflectionValue::_IS_BOOL (17)

ReflectionValue::name(18) consequently reports _IS_NUMBER for what is actually a boolean cast.

Expected

_IS_BOOL = 18, _IS_NUMBER = 19, and IS_NEVER = 17 added, matching the PHP 8.1+ zend_types.h layout that this branch's FFI definitions otherwise target. The tests/Stub/NativeNumber.php stub and ReflectionClassTest::testInstallCastObjectHandler() dispatch on these constants too, so they currently exercise the wrong branches ((int)/(float) casts appear to pass because IS_LONG/IS_DOUBLE were unaffected by the shift, but the _IS_BOOL case in the test handler is unreachable).

Context

Found while implementing casts in lisachenko/native-php-matrix (lisachenko/native-php-matrix#17); that library currently works around it with a local ENGINE_IS_BOOL = 18 constant.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions