php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Return to Bug #49526
Patch v2.2 revision 2011-12-12 03:55 UTC by php-dev at zerocue dot com
Patch v2.1 revision 2011-12-12 00:17 UTC by php-dev at zerocue dot com
Patch accessor_v2.diff revision 2011-12-11 19:20 UTC by php-dev at zerocue dot com

Patch v2.2 for Scripting Engine problem Bug #49526

Patch version 2011-12-12 03:55 UTC

Return to Bug #49526 | Download this patch
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,28 @@
+--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;	/* Calls Setter */
+		}
+	}
+}
+
+$o = new TimePeriod();
+
+echo $o->Hours."\n";
+echo $o->Hours."\n";
+$o->Hours = 4;
+?>
+--EXPECTF--
+Get Hours Called
+1
+1

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_std_readonly_error.phpt
===================================================================
--- tests/classes/accessor_std_readonly_error.phpt	(revision 0)
+++ tests/classes/accessor_std_readonly_error.phpt	(revision 0)
@@ -0,0 +1,19 @@
+--TEST--
+ZE2 Tests that a getter-only define enforces a read-only property
+--FILE--
+<?php
+
+class TimePeriod {
+	public $Seconds = 3600;
+	
+	public $Hours {
+		get { return $this->Seconds / 3600; }
+	}
+}
+
+$o = new TimePeriod();
+
+$o->Hours = 4;
+?>
+--EXPECTF--
+Fatal error: Cannot set read-only property TimePeriod::$Hours, no setter defined. in %s on line %d

Property changes on: tests\classes\accessor_std_readonly_error.phpt
___________________________________________________________________
Added: svn:eol-style
   + native

Index: tests/classes/accessor_std_writeonly_error.phpt
===================================================================
--- tests/classes/accessor_std_writeonly_error.phpt	(revision 0)
+++ tests/classes/accessor_std_writeonly_error.phpt	(revision 0)
@@ -0,0 +1,19 @@
+--TEST--
+ZE2 Tests that a setter-only define enforces a write-only property
+--FILE--
+<?php
+
+class TimePeriod {
+	public $Seconds = 3600;
+	
+	public $Hours {
+		set { $this->Seconds = $value * 3600; }
+	}
+}
+
+$o = new TimePeriod();
+
+echo $o->Hours;
+?>
+--EXPECTF--
+Fatal error: Cannot get write-only property TimePeriod::$Hours, no getter defined. in %s on line %d

Property changes on: tests\classes\accessor_std_writeonly_error.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 TSRMLS_DC) {
+	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,44 @@
 }
 /* }}} */
 
+zend_function inline *zend_locate_getter(zval *object, zval *member, const zend_literal *key TSRMLS_DC) /* {{{ */
+{
+	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) {
+		/* 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);
+	} else if(zend_hash_index_find(&zobj->ce->setters, hash_value, (void**) &func) == SUCCESS) {
+		zend_error_noreturn(E_ERROR, "Cannot get write-only property %s::$%s, no getter defined.", ZEND_FN_SCOPE_NAME(*func), Z_STRVAL_P(member));
+	}
+	return zobj->ce->__get;
+}
+/* }}} */
+
+zend_function inline *zend_locate_setter(zval *object, zval *member, zend_guard *guard, const zend_literal *key TSRMLS_DC) /* {{{ */
+{
+	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);
+	} else if((!guard || guard->in_get == 0) && zend_hash_index_find(&zobj->ce->getters, hash_value, (void**) &func) == SUCCESS) {
+		zend_error_noreturn(E_ERROR, "Cannot set read-only property %s::$%s, no setter defined.", ZEND_FN_SCOPE_NAME(*func), Z_STRVAL_P(member));
+	}
+	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 +481,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) {
@@ -556,15 +604,17 @@
 	} else {
 		zend_guard *guard = NULL;
 
-		if (zobj->ce->__set &&
-		    zend_get_property_guard(zobj, property_info, member, &guard) == SUCCESS &&
-		    !guard->in_set) {
+		zend_get_property_guard(zobj, property_info, member, &guard);
+
+		zend_function 		*setter = zend_locate_setter(object, member, guard, key TSRMLS_CC);
+
+		if (setter && guard && !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 +1076,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 +1106,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 +1469,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);
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri Dec 27 19:01:28 2024 UTC