php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login | |
Patch accessor_v2.diff for Scripting Engine problem Bug #49526Patch version 2011-12-11 19:20 UTC Return to Bug #49526 | Download this patchThis patch is obsolete Obsoleted by patches: Patch Revisions:Developer: php-dev@zerocue.comIndex: 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 320865) +++ 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 320865) +++ 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 320865) +++ 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 320865) +++ 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 320865) +++ 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 320865) +++ 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 320865) +++ 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 320865) +++ Zend/zend_object_handlers.c (working copy) @@ -52,6 +52,8 @@ 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) /* {{{ */ { if (!zobj->properties) { @@ -131,7 +133,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 +146,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 +162,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 +177,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 +407,38 @@ } /* }}} */ +zend_function inline *zend_locate_getter(zval *object, const zend_literal *key) /* {{{ */ +{ + zend_object *zobj = Z_OBJ_P(object); + zend_function **func; + + if(zend_hash_index_find(&zobj->ce->getters, key->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, const zend_literal *key) /* {{{ */ +{ + zend_object *zobj = Z_OBJ_P(object); + zend_function **func; + + if(zend_hash_index_find(&zobj->ce->setters, key->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 +476,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, key); - 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 +598,15 @@ } } else { zend_guard *guard = NULL; + zend_function *setter = zend_locate_setter(object, key); - 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 +1068,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 +1098,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 +1461,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, key); + + 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 320865) +++ 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-2024 The PHP Group All rights reserved. |
Last updated: Fri Dec 27 19:01:28 2024 UTC |