-
Notifications
You must be signed in to change notification settings - Fork 8k
Implement ReflectionProperty::is{Readable,Writable}() #16209
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6601,6 +6601,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) { | ||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The object may need to be initialized if the prop is undef:
Suggested change
Test case: class A {
public int $prop;
public function __construct() {
$this->prop = 1;
}
}
$rc = new ReflectionClass(A::class);
$obj = $rc->newLazyProxy(fn() => new A());
$rp = new ReflectionProperty(A::class, 'prop');
var_dump($rp->isReadable(null, $obj)); // should be true |
||||||||||||||||
| 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)) { | ||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ditto |
||||||||||||||||
| RETURN_FALSE; | ||||||||||||||||
| } | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| RETURN_TRUE; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| /* {{{ Constructor. Throws an Exception in case the given extension does not exist */ | ||||||||||||||||
| ZEND_METHOD(ReflectionExtension, __construct) | ||||||||||||||||
| { | ||||||||||||||||
|
|
||||||||||||||||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| 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) |
| 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point, if the object is lazy it may need to be initialized, and
obj->propertiesmust be checked again:The initialized object will have neither
__getor__issetsince the uninitialized one didn't, so we don't need to check these again.Test case: