|
php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login |
Patch v2.1 for Scripting Engine problem Bug #49526Patch version 2011-12-12 00:17 UTC Return to Bug #49526 | Download this patchThis patch is obsolete Obsoleted by patches: This patch renders other patches obsolete Obsolete patches: Patch Revisions:Developer: php-dev@zerocue.com
Index: tests/classes/accessor_implicit.phpt
===================================================================
--- tests/classes/accessor_implicit.phpt (revision 0)
+++ tests/classes/accessor_implicit.phpt (revision 0)
@@ -0,0 +1,23 @@
+--TEST--
+ZE2 Tests that a getter/setter defined as private is overridable in a looser manner and that a getter is inherited
+--FILE--
+<?php
+
+class TimePeriod {
+ public $Seconds {
+ get;
+ set;
+ }
+}
+
+$o = new TimePeriod();
+
+echo "get Seconds :".$o->Seconds."\n";
+echo "Setting Seconds to 3600\n";
+$o->Seconds = 3600;
+echo "get Seconds :".$o->Seconds."\n";
+?>
+--EXPECTF--
+get Seconds :
+Setting Seconds to 3600
+get Seconds :3600
Property changes on: tests\classes\accessor_implicit.phpt
___________________________________________________________________
Added: svn:eol-style
+ native
Index: tests/classes/accessor_interface_error.phpt
===================================================================
--- tests/classes/accessor_interface_error.phpt (revision 0)
+++ tests/classes/accessor_interface_error.phpt (revision 0)
@@ -0,0 +1,19 @@
+--TEST--
+ZE2 Tests that an interface which declares a getter/setter errors if an implementor does not implement them
+--FILE--
+<?php
+
+interface Hours {
+ public $Hours { get; set; }
+}
+
+class TimePeriod implements Hours {
+ public $Seconds = 3600;
+
+ public $Hours {
+ }
+}
+
+?>
+--EXPECTF--
+Fatal error: Class TimePeriod contains 2 abstract accessors and must be declared abstract or implement the remaining accessors (get Hours::$Hours, set Hours::$Hours) in %s on line %d
Property changes on: tests\classes\accessor_interface_error.phpt
___________________________________________________________________
Added: svn:eol-style
+ native
Index: tests/classes/accessor_std_inherit_looser_basic.phpt
===================================================================
--- tests/classes/accessor_std_inherit_looser_basic.phpt (revision 0)
+++ tests/classes/accessor_std_inherit_looser_basic.phpt (revision 0)
@@ -0,0 +1,29 @@
+--TEST--
+ZE2 Tests that a getter/setter defined as private is overridable in a looser manner and that a getter is inherited
+--FILE--
+<?php
+
+class TimePeriod {
+ public $Seconds = 3600;
+
+ public $Hours {
+ get { return $this->Seconds / 3600; }
+ private set { $this->Seconds = $value * 3600; }
+ }
+}
+
+class TimePeriod2 extends TimePeriod {
+ public $Hours {
+ public set { $this->Seconds = ($value+1) * 3600; }
+ }
+}
+
+$o = new TimePeriod2();
+
+echo $o->Hours."\n";
+$o->Hours = 2;
+echo $o->Hours."\n";
+?>
+--EXPECTF--
+1
+3
Property changes on: tests\classes\accessor_std_inherit_looser_basic.phpt
___________________________________________________________________
Added: svn:eol-style
+ native
Index: tests/classes/accessor_std_lazy_load_with_getter_basic.phpt
===================================================================
--- tests/classes/accessor_std_lazy_load_with_getter_basic.phpt (revision 0)
+++ tests/classes/accessor_std_lazy_load_with_getter_basic.phpt (revision 0)
@@ -0,0 +1,27 @@
+--TEST--
+ZE2 Tests that a getter may define its own property for use with lazy-loading via a getter
+ This perpetuates a side-effect of the way __get() works, which allows you to define the gotten property
+ which circumvents calling __get() again.
+--FILE--
+<?php
+
+class TimePeriod {
+ public $Seconds = 3600;
+
+ public $Hours {
+ get {
+ echo "Get Hours Called\n";
+ return $this->Hours = 1;
+ }
+ }
+}
+
+$o = new TimePeriod();
+
+echo $o->Hours."\n";
+echo $o->Hours."\n";
+?>
+--EXPECTF--
+Get Hours Called
+1
+1
\ No newline at end of file
Property changes on: tests\classes\accessor_std_lazy_load_with_getter_basic.phpt
___________________________________________________________________
Added: svn:eol-style
+ native
Index: tests/classes/accessor_std_private_get_error.phpt
===================================================================
--- tests/classes/accessor_std_private_get_error.phpt (revision 0)
+++ tests/classes/accessor_std_private_get_error.phpt (revision 0)
@@ -0,0 +1,20 @@
+--TEST--
+ZE2 Tests that a getter/setter defined as private is not accessible externally
+--FILE--
+<?php
+
+class TimePeriod {
+ public $Seconds = 3600;
+
+ public $Hours {
+ private get { return $this->Seconds / 3600; }
+ private set { $this->Seconds = $value * 3600; }
+ }
+}
+
+$o = new TimePeriod();
+
+echo $o->Hours."\n";
+?>
+--EXPECTF--
+Fatal error: Cannot get private property TimePeriod::$Hours from context '' in %s on line %d
Property changes on: tests\classes\accessor_std_private_get_error.phpt
___________________________________________________________________
Added: svn:eol-style
+ native
Index: tests/classes/accessor_std_private_set_error.phpt
===================================================================
--- tests/classes/accessor_std_private_set_error.phpt (revision 0)
+++ tests/classes/accessor_std_private_set_error.phpt (revision 0)
@@ -0,0 +1,23 @@
+--TEST--
+ZE2 Tests that a getter/setter defined as private is not accessible externally
+--FILE--
+<?php
+
+class TimePeriod {
+ public $Seconds = 3600;
+
+ public $Hours {
+ get { return $this->Seconds / 3600; }
+ private set { $this->Seconds = $value * 3600; }
+ }
+}
+
+$o = new TimePeriod();
+
+echo $o->Hours."\n";
+$o->Hours = 2;
+?>
+--EXPECTF--
+1
+
+Fatal error: Cannot set private property TimePeriod::$Hours from context '' in %s on line %d
Property changes on: tests\classes\accessor_std_private_set_error.phpt
___________________________________________________________________
Added: svn:eol-style
+ native
Index: tests/classes/accessor_std_public_basic.phpt
===================================================================
--- tests/classes/accessor_std_public_basic.phpt (revision 0)
+++ tests/classes/accessor_std_public_basic.phpt (revision 0)
@@ -0,0 +1,26 @@
+--TEST--
+ZE2 Tests that a getter/setter defined as public applies to both getter/setter
+--FILE--
+<?php
+
+class TimePeriod {
+ public $Seconds = 3600;
+
+ public $Hours {
+ get { return $this->Seconds / 3600; }
+ set { $this->Seconds = $value * 3600; }
+ }
+}
+
+$o = new TimePeriod();
+
+echo $o->Hours."\n";
+$o->Hours = 2;
+echo $o->Seconds."\n";
+
+echo "Done\n";
+?>
+--EXPECTF--
+1
+7200
+Done
\ No newline at end of file
Property changes on: tests\classes\accessor_std_public_basic.phpt
___________________________________________________________________
Added: svn:eol-style
+ native
Index: tests/classes/accessor_traits_basic.phpt
===================================================================
--- tests/classes/accessor_traits_basic.phpt (revision 0)
+++ tests/classes/accessor_traits_basic.phpt (revision 0)
@@ -0,0 +1,27 @@
+--TEST--
+ZE2 Tests that a getter/setter defined in a trait is inherited appropriately
+--FILE--
+<?php
+
+trait Hours {
+ public $Hours {
+ get { return $this->Seconds / 3600; }
+ set { $this->Seconds = $value * 3600; }
+ }
+}
+
+class TimePeriod {
+ use Hours;
+ public $Seconds = 3600;
+
+}
+
+$o = new TimePeriod();
+
+echo $o->Hours."\n";
+$o->Hours = 2;
+echo $o->Hours."\n";
+?>
+--EXPECTF--
+1
+2
Property changes on: tests\classes\accessor_traits_basic.phpt
___________________________________________________________________
Added: svn:eol-style
+ native
Index: Zend/zend.c
===================================================================
--- Zend/zend.c (revision 320876)
+++ Zend/zend.c (working copy)
@@ -1343,6 +1343,16 @@
}
/* }}} */
+/* Returns a new string of combined strings */
+char *strcatalloc(const char *a, const char *b) /* {{{ */
+{
+ char *out = estrndup(a, strlen(a) + strlen(b)+1);
+ memcpy(&out[strlen(a)], b, strlen(b)+1);
+ return out;
+}
+/* }}} */
+
+
/*
* Local variables:
* tab-width: 4
Index: Zend/zend.h
===================================================================
--- Zend/zend.h (revision 320876)
+++ Zend/zend.h (working copy)
@@ -469,6 +469,8 @@
HashTable function_table;
HashTable properties_info;
+ HashTable getters, setters;
+
zval **default_properties_table;
zval **default_static_members_table;
zval **static_members_table;
@@ -673,6 +675,8 @@
#define ZEND_PUTS_EX(str) write_func((str), strlen((str)))
#define ZEND_PUTC(c) zend_write(&(c), 1), (c)
+extern char *strcatalloc(const char *a, const char *b);
+
BEGIN_EXTERN_C()
extern ZEND_API int (*zend_printf)(const char *format, ...) ZEND_ATTRIBUTE_PTR_FORMAT(printf, 1, 2);
extern ZEND_API zend_write_func_t zend_write;
Index: Zend/zend_compile.c
===================================================================
--- Zend/zend_compile.c (revision 320876)
+++ Zend/zend_compile.c (working copy)
@@ -1532,6 +1532,129 @@
}
/* }}} */
+void zend_do_begin_accessor_declaration(znode *function_token, znode *var_name, znode *modifiers TSRMLS_DC) /* {{{ */
+{
+ modifiers->u.constant.value.lval &= ~ZEND_ACC_IS_ACCESSOR;
+ if(strcasecmp("get", function_token->u.constant.value.str.val) == 0) {
+ modifiers->u.constant.value.lval |= ZEND_ACC_IS_GETTER;
+ char * prefix = (((zend_uint)Z_LVAL(modifiers->u.constant)) & ZEND_ACC_STATIC) ? "__getStatic" : "__get";
+
+ /* Convert type and variable name to __getHours() */
+ char *tmp = strcatalloc(prefix, Z_STRVAL(var_name->u.constant));
+ efree(Z_STRVAL(function_token->u.constant));
+ Z_STRVAL(function_token->u.constant) = tmp;
+ Z_STRLEN(function_token->u.constant) = strlen(tmp);
+
+ /* Declare Function */
+ zend_do_begin_function_declaration(function_token, function_token, 1, ZEND_RETURN_VAL, modifiers TSRMLS_CC);
+ } else if(strcasecmp("set", function_token->u.constant.value.str.val) == 0) {
+ modifiers->u.constant.value.lval |= ZEND_ACC_IS_SETTER;
+ char * prefix = (modifiers->u.constant.value.lval & ZEND_ACC_STATIC) ? "__setStatic" : "__set";
+ /* Convert type and variable name to __setHours() */
+
+ char *tmp = strcatalloc(prefix, Z_STRVAL(var_name->u.constant));
+ efree(Z_STRVAL(function_token->u.constant));
+ Z_STRVAL(function_token->u.constant) = tmp;
+ Z_STRLEN(function_token->u.constant) = Z_STRLEN(var_name->u.constant)+strlen(prefix);
+
+ /* Declare Function */
+ zend_do_begin_function_declaration(function_token, function_token, 1, ZEND_RETURN_VAL, modifiers TSRMLS_CC);
+
+ /* Add $value parameter to __setHours() */
+ znode *unused_node = emalloc(sizeof(znode));
+ unused_node->op_type = IS_UNUSED;
+ unused_node->u.op.num = 1;
+
+ znode *unused_node2 = emalloc(sizeof(znode));
+ unused_node2->op_type = IS_UNUSED;
+ unused_node2->u.op.num = 1;
+
+ znode *value_node = emalloc(sizeof(znode));
+ Z_STRVAL(value_node->u.constant) = estrndup("value", 5);
+ Z_STRLEN(value_node->u.constant) = 5;
+
+ zend_do_receive_arg(ZEND_RECV, value_node, unused_node, NULL, unused_node2, 0 TSRMLS_CC);
+ efree(unused_node); efree(unused_node2); efree(value_node);
+ } else {
+ zend_error(E_COMPILE_ERROR, "Unknown accessor '%s', expecting get or set for variable $%s", function_token->u.constant.value.str.val, var_name->u.constant.value.str.val);
+ }
+}
+/* }}} */
+
+void zend_do_end_accessor_declaration(znode *function_token, znode *var_name, znode *modifiers, const znode *body TSRMLS_DC) /* {{{ */
+{
+ if(body == NULL && (CG(active_class_entry)->ce_flags & ZEND_ACC_INTERFACE) == 0) {
+ char *int_var_name = strcatalloc("__", Z_STRVAL(var_name->u.constant));
+
+ znode zn_this_rv, zn_this;
+ MAKE_ZNODE(zn_this, "this");
+
+ znode zn_prop_rv, zn_prop;
+ MAKE_ZNODE(zn_prop, int_var_name);
+
+ if(strstr(CG(active_op_array)->function_name, "get") != NULL) {
+ zend_do_declare_property(&zn_prop, NULL, CG(access_type) TSRMLS_CC);
+
+ zend_do_extended_info(TSRMLS_C);
+
+ /* Fetch $this */
+ zend_do_begin_variable_parse(TSRMLS_C);
+ fetch_simple_variable(&zn_this_rv, &zn_this, 1 TSRMLS_CC);
+
+ /* Fetch Internal Variable Name */
+ znode zn_prop_rv;
+ MAKE_ZNODE(zn_prop, int_var_name);
+ zend_do_fetch_property(&zn_prop_rv, &zn_this_rv, &zn_prop TSRMLS_CC);
+
+ /* Return value fetched */
+ zend_do_return(&zn_prop_rv, 1 TSRMLS_CC);
+
+ } else if(strstr(CG(active_op_array)->function_name, "set") != NULL) {
+ zend_do_extended_info(TSRMLS_C);
+ zend_do_begin_variable_parse(TSRMLS_C);
+
+ /* Fetch $this */
+ fetch_simple_variable(&zn_this_rv, &zn_this, 1 TSRMLS_CC);
+
+ /* Fetch Internal Variable Name */
+ zend_do_fetch_property(&zn_prop_rv, &zn_this_rv, &zn_prop TSRMLS_CC);
+
+ znode zn_value_rv, zn_value;
+ MAKE_ZNODE(zn_value, "value");
+
+ /* Fetch $value */
+ zend_do_begin_variable_parse(TSRMLS_C);
+ fetch_simple_variable(&zn_value_rv, &zn_value, 1 TSRMLS_CC);
+ zend_do_end_variable_parse(&zn_value_rv, BP_VAR_R, 0 TSRMLS_CC);
+
+ zend_check_writable_variable(&zn_prop_rv);
+
+ /* Assign $value to $this->[Internal Value] */
+ znode zn_assign_rv;
+ zend_do_assign(&zn_assign_rv, &zn_prop_rv, &zn_value_rv TSRMLS_CC);
+
+ zend_do_free(&zn_assign_rv TSRMLS_CC);
+ }
+ efree(int_var_name);
+ }
+
+ /* Is this safe to do? During debug CG(active_op_array) returns the same memory address as the hash_quick_find on the function table for the name */
+ zend_function *func = (zend_function*)CG(active_op_array);
+
+ zend_do_end_function_declaration(function_token TSRMLS_CC);
+
+ /* Generate Hash Value for Variable */
+ ulong hash_value = zend_hash_func(Z_STRVAL(var_name->u.constant), Z_STRLEN(var_name->u.constant)+1);
+
+ if(func->common.fn_flags & ZEND_ACC_IS_GETTER) {
+ zend_hash_index_update(&CG(active_class_entry)->getters, hash_value, (void**)&func, sizeof(zend_function*), NULL);
+ } else {
+ zend_hash_index_update(&CG(active_class_entry)->setters, hash_value, (void**)&func, sizeof(zend_function*), NULL);
+ }
+}
+/* }}} */
+
+
void zend_do_begin_function_declaration(znode *function_token, znode *function_name, int is_method, int return_reference, znode *fn_flags_znode TSRMLS_DC) /* {{{ */
{
zend_op_array op_array;
@@ -1545,7 +1668,7 @@
if (is_method) {
if (CG(active_class_entry)->ce_flags & ZEND_ACC_INTERFACE) {
- if ((Z_LVAL(fn_flags_znode->u.constant) & ~(ZEND_ACC_STATIC|ZEND_ACC_PUBLIC))) {
+ if ((Z_LVAL(fn_flags_znode->u.constant) & ~(ZEND_ACC_STATIC|ZEND_ACC_PUBLIC|ZEND_ACC_IS_ACCESSOR))) {
zend_error(E_COMPILE_ERROR, "Access type for interface method %s::%s() must be omitted", CG(active_class_entry)->name, function_name->u.constant.value.str.val);
}
Z_LVAL(fn_flags_znode->u.constant) |= ZEND_ACC_ABSTRACT; /* propagates to the rest of the parser */
@@ -2911,6 +3034,17 @@
}
/* }}} */
+char *zend_accessor_type_string(zend_uint fn_flags) /* {{{ */
+{
+ if (fn_flags & ZEND_ACC_IS_GETTER) {
+ return "get";
+ } else if (fn_flags & ZEND_ACC_IS_SETTER) {
+ return "set";
+ }
+ return "access";
+}
+/* }}} */
+
static void do_inherit_method(zend_function *function) /* {{{ */
{
/* The class entry of the derived function intentionally remains the same
@@ -3184,8 +3318,12 @@
}
if (parent_flags & ZEND_ACC_FINAL) {
+ if (parent_flags & ZEND_ACC_IS_ACCESSOR) {
+ zend_error(E_COMPILE_ERROR, "Cannot override final property %ster %s::$%s", zend_accessor_type_string(child->common.fn_flags), ZEND_FN_SCOPE_NAME(parent), &child->common.function_name[5]);
+ } else {
zend_error(E_COMPILE_ERROR, "Cannot override final method %s::%s()", ZEND_FN_SCOPE_NAME(parent), child->common.function_name);
}
+ }
child_flags = child->common.fn_flags;
/* You cannot change from static to non static and vice versa.
@@ -3209,7 +3347,11 @@
/* Prevent derived classes from restricting access that was available in parent classes
*/
if ((child_flags & ZEND_ACC_PPP_MASK) > (parent_flags & ZEND_ACC_PPP_MASK)) {
+ if (child_flags & ZEND_ACC_IS_ACCESSOR) {
+ zend_error(E_COMPILE_ERROR, "Access level to %ster %s::$%s must be %s (as in class %s)%s", zend_accessor_type_string(child->common.fn_flags), ZEND_FN_SCOPE_NAME(child), &child->common.function_name[5], zend_visibility_string(parent_flags), ZEND_FN_SCOPE_NAME(parent), (parent_flags&ZEND_ACC_PUBLIC) ? "" : " or weaker");
+ } else {
zend_error(E_COMPILE_ERROR, "Access level to %s::%s() must be %s (as in class %s)%s", ZEND_FN_SCOPE_NAME(child), child->common.function_name, zend_visibility_string(parent_flags), ZEND_FN_SCOPE_NAME(parent), (parent_flags&ZEND_ACC_PUBLIC) ? "" : " or weaker");
+ }
} else if (((child_flags & ZEND_ACC_PPP_MASK) < (parent_flags & ZEND_ACC_PPP_MASK))
&& ((parent_flags & ZEND_ACC_PPP_MASK) & ZEND_ACC_PRIVATE)) {
child->common.fn_flags |= ZEND_ACC_CHANGED;
@@ -3372,6 +3514,26 @@
((void (*)(void *)) zval_add_ref)
#endif
+/* Updates the index of getters/setters, called after the function_table has been modified en mass */
+static inline void zend_do_update_accessors(zend_class_entry *ce) {
+ zend_function *func;
+
+ for (zend_hash_internal_pointer_reset(&ce->function_table);
+ zend_hash_get_current_data(&ce->function_table, (void *) &func) == SUCCESS;
+ zend_hash_move_forward(&ce->function_table)) {
+ if (func->common.fn_flags & ZEND_ACC_IS_ACCESSOR) {
+ const char *varname = &(func->common.function_name[5]);
+ ulong hash_value = zend_hash_func(varname, strlen(varname)+1);
+
+ if(func->common.fn_flags & ZEND_ACC_IS_GETTER) {
+ zend_hash_index_update(&ce->getters, hash_value, (void**)&func, sizeof(zend_function*), NULL);
+ } else {
+ zend_hash_index_update(&ce->setters, hash_value, (void**)&func, sizeof(zend_function*), NULL);
+ }
+ }
+ }
+}
+
ZEND_API void zend_do_inheritance(zend_class_entry *ce, zend_class_entry *parent_ce TSRMLS_DC) /* {{{ */
{
zend_property_info *property_info;
@@ -3495,6 +3657,8 @@
zend_verify_abstract_class(ce TSRMLS_CC);
}
ce->ce_flags |= parent_ce->ce_flags & ZEND_HAS_STATIC_IN_METHODS;
+
+ zend_do_update_accessors(ce TSRMLS_CC);
}
/* }}} */
@@ -4401,6 +4565,7 @@
if (ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) {
ce->ce_flags -= ZEND_ACC_IMPLICIT_ABSTRACT_CLASS;
}
+ zend_do_update_accessors(ce TSRMLS_CC);
}
/* }}} */
@@ -6760,6 +6925,8 @@
zend_hash_init_ex(&ce->properties_info, 0, NULL, (dtor_func_t) (persistent_hashes ? zend_destroy_property_info_internal : zend_destroy_property_info), persistent_hashes, 0);
zend_hash_init_ex(&ce->constants_table, 0, NULL, zval_ptr_dtor_func, persistent_hashes, 0);
zend_hash_init_ex(&ce->function_table, 0, NULL, ZEND_FUNCTION_DTOR, persistent_hashes, 0);
+ zend_hash_init_ex(&ce->getters, 0, NULL, NULL, persistent_hashes, 0);
+ zend_hash_init_ex(&ce->setters, 0, NULL, NULL, persistent_hashes, 0);
if (ce->type == ZEND_INTERNAL_CLASS) {
#ifdef ZTS
Index: Zend/zend_compile.h
===================================================================
--- Zend/zend_compile.h (revision 320876)
+++ Zend/zend_compile.h (working copy)
@@ -34,6 +34,14 @@
#define FREE_PNODE(znode) zval_dtor(&znode->u.constant);
+#define MAKE_ZNODE(zn, str) \
+ { \
+ zn.op_type = IS_CONST; \
+ INIT_PZVAL(&zn.u.constant); \
+ ZVAL_STRINGL(&zn.u.constant, str, strlen(str), 1); \
+ zn.EA = 0; \
+ }
+
#define SET_UNUSED(op) op ## _type = IS_UNUSED
#define INC_BPC(op_array) if (op_array->fn_flags & ZEND_ACC_INTERACTIVE) { (CG(context).backpatch_count++); }
@@ -207,7 +215,12 @@
#define ZEND_ACC_RETURN_REFERENCE 0x4000000
#define ZEND_ACC_DONE_PASS_TWO 0x8000000
+#define ZEND_ACC_IS_GETTER 0x10000000
+#define ZEND_ACC_IS_SETTER 0x20000000
+#define ZEND_ACC_IS_ACCESSOR 0x30000000 /* Mask */
+
char *zend_visibility_string(zend_uint fn_flags);
+char *zend_accessor_type_string(zend_uint fn_flags);
typedef struct _zend_property_info {
@@ -491,6 +504,10 @@
void zend_do_return(znode *expr, int do_end_vparse TSRMLS_DC);
void zend_do_handle_exception(TSRMLS_D);
+
+void zend_do_begin_accessor_declaration(znode *function_token, znode *var_name, znode *modifiers TSRMLS_DC);
+void zend_do_end_accessor_declaration(znode *function_token, znode *var_name, znode *modifiers, const znode *body TSRMLS_DC);
+
void zend_do_begin_lambda_function_declaration(znode *result, znode *function_token, int return_reference, int is_static TSRMLS_DC);
void zend_do_fetch_lexical_variable(znode *varname, zend_bool is_ref TSRMLS_DC);
Index: Zend/zend_execute_API.c
===================================================================
--- Zend/zend_execute_API.c (revision 320876)
+++ Zend/zend_execute_API.c (working copy)
@@ -1620,33 +1620,53 @@
#define MAX_ABSTRACT_INFO_CNT 3
#define MAX_ABSTRACT_INFO_FMT "%s%s%s%s"
+#define MAX_ABS_ACCESSOR_INFO_FMT "%s%s%s%s%s%s"
+
#define DISPLAY_ABSTRACT_FN(idx) \
ai.afn[idx] ? ZEND_FN_SCOPE_NAME(ai.afn[idx]) : "", \
ai.afn[idx] ? "::" : "", \
ai.afn[idx] ? ai.afn[idx]->common.function_name : "", \
- ai.afn[idx] && ai.afn[idx + 1] ? ", " : (ai.afn[idx] && ai.cnt > MAX_ABSTRACT_INFO_CNT ? ", ..." : "")
+ ai.afn[idx] && ai.afn[idx + 1] ? ", " : (ai.afn[idx] && ai.afn_cnt > MAX_ABSTRACT_INFO_CNT ? ", ..." : "")
+
+#define DISPLAY_ABS_ACCESSOR_FN(idx) \
+ ai.abs_acc[idx] ? zend_accessor_type_string(ai.abs_acc[idx]->common.fn_flags) : "", \
+ ai.abs_acc[idx] ? " " : "", \
+ ai.abs_acc[idx] ? ZEND_FN_SCOPE_NAME(ai.abs_acc[idx]) : "", \
+ ai.abs_acc[idx] ? "::$" : "", \
+ ai.abs_acc[idx] ? &(ai.abs_acc[idx]->common.function_name[5]) : "", \
+ ai.abs_acc[idx] && ai.abs_acc[idx + 1] ? ", " : (ai.abs_acc[idx] && ai.abs_acc_count > MAX_ABSTRACT_INFO_CNT ? ", ..." : "")
typedef struct _zend_abstract_info {
zend_function *afn[MAX_ABSTRACT_INFO_CNT + 1];
- int cnt;
+ int afn_cnt;
+
+ zend_function *abs_acc[MAX_ABSTRACT_INFO_CNT + 1];
+ int abs_acc_count;
+
int ctor;
} zend_abstract_info;
static int zend_verify_abstract_class_function(zend_function *fn, zend_abstract_info *ai TSRMLS_DC) /* {{{ */
{
if (fn->common.fn_flags & ZEND_ACC_ABSTRACT) {
- if (ai->cnt < MAX_ABSTRACT_INFO_CNT) {
- ai->afn[ai->cnt] = fn;
+ if(fn->common.fn_flags & ZEND_ACC_IS_ACCESSOR) {
+ if (ai->abs_acc_count < MAX_ABSTRACT_INFO_CNT)
+ ai->abs_acc[ai->abs_acc_count] = fn;
+ ai->abs_acc_count++;
+ } else {
+ if (ai->afn_cnt < MAX_ABSTRACT_INFO_CNT) {
+ ai->afn[ai->afn_cnt] = fn;
}
if (fn->common.fn_flags & ZEND_ACC_CTOR) {
if (!ai->ctor) {
- ai->cnt++;
+ ai->afn_cnt++;
ai->ctor = 1;
} else {
- ai->afn[ai->cnt] = NULL;
+ ai->afn[ai->afn_cnt] = NULL;
}
} else {
- ai->cnt++;
+ ai->afn_cnt++;
+ }
}
}
return 0;
@@ -1662,15 +1682,24 @@
zend_hash_apply_with_argument(&ce->function_table, (apply_func_arg_t) zend_verify_abstract_class_function, &ai TSRMLS_CC);
- if (ai.cnt) {
+ if (ai.afn_cnt) {
zend_error(E_ERROR, "Class %s contains %d abstract method%s and must therefore be declared abstract or implement the remaining methods (" MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT MAX_ABSTRACT_INFO_FMT ")",
- ce->name, ai.cnt,
- ai.cnt > 1 ? "s" : "",
+ ce->name, ai.afn_cnt,
+ ai.afn_cnt > 1 ? "s" : "",
DISPLAY_ABSTRACT_FN(0),
DISPLAY_ABSTRACT_FN(1),
DISPLAY_ABSTRACT_FN(2)
);
}
+ if (ai.abs_acc_count) {
+ zend_error(E_ERROR, "Class %s contains %d abstract accessor%s and must be declared abstract or implement the remaining accessors (" MAX_ABS_ACCESSOR_INFO_FMT MAX_ABS_ACCESSOR_INFO_FMT MAX_ABS_ACCESSOR_INFO_FMT ")",
+ ce->name, ai.abs_acc_count,
+ ai.abs_acc_count > 1 ? "s" : "",
+ DISPLAY_ABS_ACCESSOR_FN(0),
+ DISPLAY_ABS_ACCESSOR_FN(1),
+ DISPLAY_ABS_ACCESSOR_FN(2)
+ );
+ }
}
}
/* }}} */
Index: Zend/zend_globals.h
===================================================================
--- Zend/zend_globals.h (revision 320876)
+++ Zend/zend_globals.h (working copy)
@@ -124,6 +124,7 @@
zend_bool increment_lineno;
znode implementing_class;
+ znode *accessor_node;
zend_uint access_type;
Index: Zend/zend_language_parser.y
===================================================================
--- Zend/zend_language_parser.y (revision 320876)
+++ Zend/zend_language_parser.y (working copy)
@@ -573,7 +573,7 @@
class_statement:
- variable_modifiers { CG(access_type) = Z_LVAL($1.u.constant); } class_variable_declaration ';'
+ variable_modifiers { CG(access_type) = Z_LVAL($1.u.constant); } class_variable_accessor_declarations
| class_constant_declaration ';'
| trait_use_statement
| method_modifiers function is_reference T_STRING { zend_do_begin_function_declaration(&$2, &$4, 1, $3.op_type, &$1 TSRMLS_CC); } '('
@@ -666,6 +666,49 @@
| T_FINAL { Z_LVAL($$.u.constant) = ZEND_ACC_FINAL; }
;
+accessors:
+ accessor_function
+ | accessor_function
+ accessor_function
+ | /* Empty */
+;
+
+accessor_modifiers:
+ /* empty */ { Z_LVAL($$.u.constant) = CG(access_type); }
+ | non_empty_accessor_modifiers { /* zend_do_verify_access_types(), Line 658 from non_empty_member_modifiers */ }
+;
+
+non_empty_accessor_modifiers:
+ T_PUBLIC { Z_LVAL($$.u.constant) = ZEND_ACC_PUBLIC; }
+ | T_PROTECTED { Z_LVAL($$.u.constant) = ZEND_ACC_PROTECTED; }
+ | T_PRIVATE { Z_LVAL($$.u.constant) = ZEND_ACC_PRIVATE; }
+ | T_STATIC { Z_LVAL($$.u.constant) = ZEND_ACC_STATIC; }
+ | T_FINAL { Z_LVAL($$.u.constant) = ZEND_ACC_FINAL; }
+;
+
+accessor_function:
+ accessor_modifiers T_STRING
+ { zend_do_begin_accessor_declaration(&$2, CG(accessor_node), &$1 TSRMLS_CC); }
+ '{' inner_statement_list '}'
+ { zend_do_end_accessor_declaration(&$2, CG(accessor_node), &$1, &$4 TSRMLS_CC); }
+ | accessor_modifiers T_STRING
+ {
+ zend_do_begin_accessor_declaration(&$2, CG(accessor_node), &$1 TSRMLS_CC);
+ zend_do_end_accessor_declaration(&$2, CG(accessor_node), &$1, NULL TSRMLS_CC);
+ /* efree(Z_STRVAL($2.u.constant)); */
+ }
+ ';'
+;
+
+class_variable_accessor_declarations:
+ T_VARIABLE '{'
+ { CG(accessor_node) = &$1; }
+ accessors
+ { efree($1.u.constant.value.str.val); }
+ '}'
+ | class_variable_declaration ';'
+;
+
class_variable_declaration:
class_variable_declaration ',' T_VARIABLE { zend_do_declare_property(&$3, NULL, CG(access_type) TSRMLS_CC); }
| class_variable_declaration ',' T_VARIABLE '=' static_scalar { zend_do_declare_property(&$3, &$5, CG(access_type) TSRMLS_CC); }
Index: Zend/zend_object_handlers.c
===================================================================
--- Zend/zend_object_handlers.c (revision 320876)
+++ Zend/zend_object_handlers.c (working copy)
@@ -51,6 +51,7 @@
if we have __call and method which is not part of the class function table is
called, we cal __call handler.
*/
+static union _zend_function *zend_std_get_method(zval **object_ptr, char *method_name, int method_len, const struct _zend_literal *key TSRMLS_DC);
ZEND_API void rebuild_object_properties(zend_object *zobj) /* {{{ */
{
@@ -131,7 +132,7 @@
}
/* }}} */
-static zval *zend_std_call_getter(zval *object, zval *member TSRMLS_DC) /* {{{ */
+static zval *zend_std_call_getter(zval *object, zval *member, zend_function *func TSRMLS_DC) /* {{{ */
{
zval *retval = NULL;
zend_class_entry *ce = Z_OBJCE_P(object);
@@ -144,7 +145,11 @@
SEPARATE_ARG_IF_REF(member);
- zend_call_method_with_1_params(&object, ce, &ce->__get, ZEND_GET_FUNC_NAME, &retval, member);
+ if (func->common.num_args == 1) {
+ zend_call_method_with_1_params(&object, ce, &func, func->common.function_name, &retval, member);
+ } else {
+ zend_call_method_with_0_params(&object, ce, &func, func->common.function_name, &retval);
+ }
zval_ptr_dtor(&member);
@@ -156,7 +161,7 @@
}
/* }}} */
-static int zend_std_call_setter(zval *object, zval *member, zval *value TSRMLS_DC) /* {{{ */
+static int zend_std_call_setter(zval *object, zval *member, zval *value, zend_function *func TSRMLS_DC) /* {{{ */
{
zval *retval = NULL;
int result;
@@ -171,7 +176,11 @@
it should return whether the call was successfull or not
*/
- zend_call_method_with_2_params(&object, ce, &ce->__set, ZEND_SET_FUNC_NAME, &retval, member, value);
+ if (func->common.num_args == 2) {
+ zend_call_method_with_2_params(&object, ce, &func, func->common.function_name, &retval, member, value);
+ } else {
+ zend_call_method_with_1_params(&object, ce, &func, func->common.function_name, &retval, value);
+ }
zval_ptr_dtor(&member);
zval_ptr_dtor(&value);
@@ -397,6 +406,40 @@
}
/* }}} */
+zend_function inline *zend_locate_getter(zval *object, zval *member, const zend_literal *key) /* {{{ */
+{
+ zend_object *zobj = Z_OBJ_P(object);
+ zend_function **func;
+ ulong hash_value = key ? key->hash_value : zend_get_hash_value(Z_STRVAL_P(member), Z_STRLEN_P(member) + 1);
+
+ if(zend_hash_index_find(&zobj->ce->getters, hash_value, (void**) &func) == SUCCESS && func) {
+ /* If public getter, no access check required */
+ if((*func)->common.fn_flags & ZEND_ACC_PUBLIC) {
+ return *func;
+ }
+ return zend_std_get_method(&object, (char *)(*func)->common.function_name, strlen((*func)->common.function_name), NULL TSRMLS_CC);
+ }
+ return zobj->ce->__get;
+}
+/* }}} */
+
+zend_function inline *zend_locate_setter(zval *object, zval *member, const zend_literal *key) /* {{{ */
+{
+ zend_object *zobj = Z_OBJ_P(object);
+ zend_function **func;
+ ulong hash_value = key ? key->hash_value : zend_get_hash_value(Z_STRVAL_P(member), Z_STRLEN_P(member) + 1);
+
+ if(zend_hash_index_find(&zobj->ce->setters, hash_value, (void**) &func) == SUCCESS && func) {
+ /* If public getter, no access check required */
+ if((*func)->common.fn_flags & ZEND_ACC_PUBLIC) {
+ return *func;
+ }
+ return zend_std_get_method(&object, (char *)(*func)->common.function_name, strlen((*func)->common.function_name), NULL TSRMLS_CC);
+ }
+ return zobj->ce->__set;
+}
+/* }}} */
+
zval *zend_std_read_property(zval *object, zval *member, int type, const zend_literal *key TSRMLS_DC) /* {{{ */
{
zend_object *zobj;
@@ -434,18 +477,19 @@
(*(retval = &zobj->properties_table[property_info->offset]) == NULL)) :
(UNEXPECTED(!zobj->properties) ||
UNEXPECTED(zend_hash_quick_find(zobj->properties, property_info->name, property_info->name_length+1, property_info->h, (void **) &retval) == FAILURE)))) {
+
zend_guard *guard = NULL;
+ zend_function *getter = zend_locate_getter(object, member, key TSRMLS_CC);
- if (zobj->ce->__get &&
- zend_get_property_guard(zobj, property_info, member, &guard) == SUCCESS &&
- !guard->in_get) {
+ if ( getter != NULL && zend_get_property_guard(zobj, property_info, member, &guard) == SUCCESS && !guard->in_get) {
/* have getter - try with it! */
+
Z_ADDREF_P(object);
if (PZVAL_IS_REF(object)) {
SEPARATE_ZVAL(&object);
}
guard->in_get = 1; /* prevent circular getting */
- rv = zend_std_call_getter(object, member TSRMLS_CC);
+ rv = zend_std_call_getter(object, member, getter TSRMLS_CC);
guard->in_get = 0;
if (rv) {
@@ -555,16 +599,15 @@
}
} else {
zend_guard *guard = NULL;
+ zend_function *setter = zend_locate_setter(object, member, key TSRMLS_CC);
- if (zobj->ce->__set &&
- zend_get_property_guard(zobj, property_info, member, &guard) == SUCCESS &&
- !guard->in_set) {
+ if (setter && zend_get_property_guard(zobj, property_info, member, &guard) == SUCCESS && !guard->in_set) {
Z_ADDREF_P(object);
if (PZVAL_IS_REF(object)) {
SEPARATE_ZVAL(&object);
}
guard->in_set = 1; /* prevent circular setting */
- if (zend_std_call_setter(object, member, value TSRMLS_CC) != SUCCESS) {
+ if (zend_std_call_setter(object, member, value, setter TSRMLS_CC) != SUCCESS) {
/* for now, just ignore it - __set should take care of warnings, etc. */
}
guard->in_set = 0;
@@ -1026,9 +1069,13 @@
if (zobj->ce->__call) {
fbc = zend_get_user_call_function(zobj->ce, method_name, method_len);
} else {
+ if(fbc->op_array.fn_flags & ZEND_ACC_IS_ACCESSOR) {
+ zend_error_noreturn(E_ERROR, "Cannot %s %s property %s::$%s from context '%s'", zend_accessor_type_string(fbc->op_array.fn_flags), zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), &(method_name[5]), EG(scope) ? EG(scope)->name : "");
+ } else {
zend_error_noreturn(E_ERROR, "Call to %s method %s::%s() from context '%s'", zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), method_name, EG(scope) ? EG(scope)->name : "");
}
}
+ }
} else {
/* Ensure that we haven't overridden a private function and end up calling
* the overriding public function...
@@ -1052,6 +1099,9 @@
if (zobj->ce->__call) {
fbc = zend_get_user_call_function(zobj->ce, method_name, method_len);
} else {
+ if(fbc->op_array.fn_flags & ZEND_ACC_IS_ACCESSOR) {
+ zend_error_noreturn(E_ERROR, "Cannot %s %s property %s::$%s from context '%s'", zend_accessor_type_string(fbc->common.fn_flags), zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), &(method_name[5]), EG(scope) ? EG(scope)->name : "");
+ } else
zend_error_noreturn(E_ERROR, "Call to %s method %s::%s() from context '%s'", zend_visibility_string(fbc->common.fn_flags), ZEND_FN_SCOPE_NAME(fbc), method_name, EG(scope) ? EG(scope)->name : "");
}
}
@@ -1412,9 +1462,12 @@
result = zend_is_true(rv);
zval_ptr_dtor(&rv);
if (has_set_exists && result) {
- if (EXPECTED(!EG(exception)) && zobj->ce->__get && !guard->in_get) {
+
+ zend_function *getter = zend_locate_getter(object, member, key TSRMLS_CC);
+
+ if (EXPECTED(!EG(exception)) && getter && !guard->in_get) {
guard->in_get = 1;
- rv = zend_std_call_getter(object, member TSRMLS_CC);
+ rv = zend_std_call_getter(object, member, getter TSRMLS_CC);
guard->in_get = 0;
if (rv) {
Z_ADDREF_P(rv);
Index: Zend/zend_opcode.c
===================================================================
--- Zend/zend_opcode.c (revision 320876)
+++ Zend/zend_opcode.c (working copy)
@@ -291,6 +291,8 @@
efree(ce->default_static_members_table);
}
zend_hash_destroy(&ce->properties_info);
+ zend_hash_destroy(&ce->getters);
+ zend_hash_destroy(&ce->setters);
str_efree(ce->name);
zend_hash_destroy(&ce->function_table);
zend_hash_destroy(&ce->constants_table);
|
Copyright © 2001-2025 The PHP GroupAll rights reserved. |
Last updated: Fri Oct 24 12:00:01 2025 UTC |