Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 209 additions & 0 deletions ext/reflection/php_reflection.c
Original file line number Diff line number Diff line change
Expand Up @@ -6633,6 +6633,215 @@ ZEND_METHOD(ReflectionProperty, isFinal)
_property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_FINAL);
}

static zend_result get_ce_from_scope_name(zend_class_entry **scope, zend_string *scope_name, zend_execute_data *execute_data)
{
if (!scope_name) {
*scope = NULL;
return SUCCESS;
}

*scope = zend_lookup_class(scope_name);
if (!*scope) {
zend_throw_error(NULL, "Class \"%s\" not found", ZSTR_VAL(scope_name));
return FAILURE;
}
return SUCCESS;
}

static zend_always_inline uint32_t set_visibility_to_visibility(uint32_t set_visibility)
{
switch (set_visibility) {
case ZEND_ACC_PUBLIC_SET:
return ZEND_ACC_PUBLIC;
case ZEND_ACC_PROTECTED_SET:
return ZEND_ACC_PROTECTED;
case ZEND_ACC_PRIVATE_SET:
return ZEND_ACC_PRIVATE;
EMPTY_SWITCH_DEFAULT_CASE();
}
}

static bool check_visibility(uint32_t visibility, zend_class_entry *ce, zend_class_entry *scope)
{
if (!(visibility & ZEND_ACC_PUBLIC) && (scope != ce)) {
if (!scope) {
return false;
}
if (visibility & ZEND_ACC_PRIVATE) {
return false;
}
ZEND_ASSERT(visibility & ZEND_ACC_PROTECTED);
if (!instanceof_function(scope, ce) && !instanceof_function(ce, scope)) {
return false;
}
}
return true;
}

ZEND_METHOD(ReflectionProperty, isReadable)
{
reflection_object *intern;
property_reference *ref;
zend_string *scope_name;
zend_object *obj = NULL;

ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_STR_OR_NULL(scope_name)
Z_PARAM_OPTIONAL
Z_PARAM_OBJ_OR_NULL(obj)
ZEND_PARSE_PARAMETERS_END();

GET_REFLECTION_OBJECT_PTR(ref);

zend_property_info *prop = ref->prop;
if (prop && obj) {
if (prop->flags & ZEND_ACC_STATIC) {
_DO_THROW("null is expected as object argument for static properties");
RETURN_THROWS();
}
if (!instanceof_function(obj->ce, prop->ce)) {
_DO_THROW("Given object is not an instance of the class this property was declared in");
RETURN_THROWS();
}
prop = reflection_property_get_effective_prop(ref, intern->ce, obj);
}

zend_class_entry *ce = obj ? obj->ce : intern->ce;
if (!prop) {
if (obj && obj->properties && zend_hash_find_ptr(obj->properties, ref->unmangled_name)) {
RETURN_TRUE;
}
handle_magic_get:
if (ce->__get) {
if (obj && ce->__isset) {
uint32_t *guard = zend_get_property_guard(obj, ref->unmangled_name);
if (!((*guard) & ZEND_GUARD_PROPERTY_ISSET)) {
GC_ADDREF(obj);
*guard |= ZEND_GUARD_PROPERTY_ISSET;
zval member;
ZVAL_STR(&member, ref->unmangled_name);
zend_call_known_instance_method_with_1_params(ce->__isset, obj, return_value, &member);
*guard &= ~ZEND_GUARD_PROPERTY_ISSET;
OBJ_RELEASE(obj);
return;
}
}
RETURN_TRUE;
}
RETURN_FALSE;
}

zend_class_entry *scope;
if (get_ce_from_scope_name(&scope, scope_name, execute_data) == FAILURE) {
RETURN_THROWS();
}

if (!check_visibility(prop->flags & ZEND_ACC_PPP_MASK, prop->ce, scope)) {
if (!(prop->flags & ZEND_ACC_STATIC)) {
goto handle_magic_get;
}
RETURN_FALSE;
}

if (prop->flags & ZEND_ACC_VIRTUAL) {
ZEND_ASSERT(prop->hooks);
if (!prop->hooks[ZEND_PROPERTY_HOOK_GET]) {
RETURN_FALSE;
}
} else if (obj && (!prop->hooks || !prop->hooks[ZEND_PROPERTY_HOOK_GET])) {
zval *prop_val = OBJ_PROP(obj, prop->offset);
if (Z_TYPE_P(prop_val) == IS_UNDEF) {
if (!(Z_PROP_FLAG_P(prop_val) & IS_PROP_UNINIT)) {
goto handle_magic_get;
}
RETURN_FALSE;
}
} else if (prop->flags & ZEND_ACC_STATIC) {
if (ce->default_static_members_count && !CE_STATIC_MEMBERS(ce)) {
zend_class_init_statics(ce);
}
zval *prop_val = CE_STATIC_MEMBERS(ce) + prop->offset;
RETURN_BOOL(!Z_ISUNDEF_P(prop_val));
}

RETURN_TRUE;
}

ZEND_METHOD(ReflectionProperty, isWritable)
{
reflection_object *intern;
property_reference *ref;
zend_string *scope_name;
zend_object *obj = NULL;

ZEND_PARSE_PARAMETERS_START(1, 2)
Z_PARAM_STR_OR_NULL(scope_name)
Z_PARAM_OPTIONAL
Z_PARAM_OBJ_OR_NULL(obj)
ZEND_PARSE_PARAMETERS_END();

GET_REFLECTION_OBJECT_PTR(ref);

zend_property_info *prop = ref->prop;
if (prop && obj) {
if (prop->flags & ZEND_ACC_STATIC) {
_DO_THROW("null is expected as object argument for static properties");
RETURN_THROWS();
}
if (!instanceof_function(obj->ce, prop->ce)) {
_DO_THROW("Given object is not an instance of the class this property was declared in");
RETURN_THROWS();
}
prop = reflection_property_get_effective_prop(ref, intern->ce, obj);
}

zend_class_entry *ce = obj ? obj->ce : intern->ce;
if (!prop) {
if (!(ce->ce_flags & ZEND_ACC_NO_DYNAMIC_PROPERTIES)) {
RETURN_TRUE;
}
/* This path is effectively unreachable, but theoretically possible for
* two internal classes where ZEND_ACC_NO_DYNAMIC_PROPERTIES is only
* added to the subclass, in which case a ReflectionProperty can be
* constructed on the parent class, and then tested on the subclass. */
handle_magic_set:
RETURN_BOOL(ce->__set);
}

zend_class_entry *scope;
if (get_ce_from_scope_name(&scope, scope_name, execute_data) == FAILURE) {
RETURN_THROWS();
}

if (!check_visibility(prop->flags & ZEND_ACC_PPP_MASK, prop->ce, scope)) {
if (!(prop->flags & ZEND_ACC_STATIC)) {
goto handle_magic_set;
}
RETURN_FALSE;
}
uint32_t set_visibility = prop->flags & ZEND_ACC_PPP_SET_MASK;
if (!set_visibility) {
set_visibility = zend_visibility_to_set_visibility(prop->flags & ZEND_ACC_PPP_MASK);
}
if (!check_visibility(set_visibility_to_visibility(set_visibility), prop->ce, scope)) {
RETURN_FALSE;
}

if (prop->flags & ZEND_ACC_VIRTUAL) {
ZEND_ASSERT(prop->hooks);
if (!prop->hooks[ZEND_PROPERTY_HOOK_SET]) {
RETURN_FALSE;
}
} else if (obj && (prop->flags & ZEND_ACC_READONLY)) {
zval *prop_val = OBJ_PROP(obj, prop->offset);
if (Z_TYPE_P(prop_val) != IS_UNDEF && !(Z_PROP_FLAG_P(prop_val) & IS_PROP_REINITABLE)) {
RETURN_FALSE;
}
}

RETURN_TRUE;
}

/* {{{ Constructor. Throws an Exception in case the given extension does not exist */
ZEND_METHOD(ReflectionExtension, __construct)
{
Expand Down
4 changes: 4 additions & 0 deletions ext/reflection/php_reflection.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,10 @@ public function hasHook(PropertyHookType $type): bool {}
public function getHook(PropertyHookType $type): ?ReflectionMethod {}

public function isFinal(): bool {}

public function isReadable(?string $scope, ?object $object = null): bool {}

public function isWritable(?string $scope, ?object $object = null): bool {}
}

/** @not-serializable */
Expand Down
13 changes: 12 additions & 1 deletion ext/reflection/php_reflection_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions ext/reflection/tests/ReflectionProperty_isReadable_dynamic.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
Test ReflectionProperty::isReadable() dynamic
--FILE--
<?php

#[AllowDynamicProperties]
class A {}

$a = new A;

$a->a = 'a';
$r = new ReflectionProperty($a, 'a');

var_dump($r->isReadable(null, $a));
unset($a->a);
var_dump($r->isReadable(null, $a));

$a = new A;
var_dump($r->isReadable(null, $a));

var_dump($r->isReadable(null, null));

?>
--EXPECT--
bool(true)
bool(false)
bool(false)
bool(false)
39 changes: 39 additions & 0 deletions ext/reflection/tests/ReflectionProperty_isReadable_hooks.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
--TEST--
Test ReflectionProperty::isReadable() hooks
--FILE--
<?php

class A {
public $a { get => $this->a; }
public $b { get => 42; }
public $c { set => $value; }
public $d { set {} }
public $e { get => $this->e; set => $value; }
public $f { get {} set {} }
}

function test($scope) {
$rc = new ReflectionClass(A::class);
foreach ($rc->getProperties() as $rp) {
echo $rp->getName() . ' from ' . ($scope ?? 'global') . ': ';
var_dump($rp->isReadable($scope, null));
}
}

test('A');
test(null);

?>
--EXPECT--
a from A: bool(true)
b from A: bool(true)
c from A: bool(true)
d from A: bool(false)
e from A: bool(true)
f from A: bool(true)
a from global: bool(true)
b from global: bool(true)
c from global: bool(true)
d from global: bool(false)
e from global: bool(true)
f from global: bool(true)
Loading