diff --git a/Zend/tests/anon/001.phpt b/Zend/tests/anon/001.phpt new file mode 100644 index 000000000000..8031c7a128f5 --- /dev/null +++ b/Zend/tests/anon/001.phpt @@ -0,0 +1,10 @@ +--TEST-- +declare bare anonymous class +--FILE-- + +--EXPECTF-- +object(%s$$1)#%d (0) { +} + diff --git a/Zend/tests/anon/002.phpt b/Zend/tests/anon/002.phpt new file mode 100644 index 000000000000..149670876ccd --- /dev/null +++ b/Zend/tests/anon/002.phpt @@ -0,0 +1,10 @@ +--TEST-- +declare anonymous class extending another +--FILE-- + +--EXPECTF-- +object(%s$$1)#%d (0) { +} + diff --git a/Zend/tests/anon/003.phpt b/Zend/tests/anon/003.phpt new file mode 100644 index 000000000000..e74c6d604c8d --- /dev/null +++ b/Zend/tests/anon/003.phpt @@ -0,0 +1,54 @@ +--TEST-- +reusing anonymous classes +--FILE-- +i = $i; + } + }($i)); +} +?> +--EXPECTF-- +object(%s$$1)#1 (1) { + ["i"]=> + int(1) +} +object(%s$$1)#1 (1) { + ["i"]=> + int(2) +} +object(%s$$1)#1 (1) { + ["i"]=> + int(3) +} +object(%s$$1)#1 (1) { + ["i"]=> + int(4) +} +object(%s$$1)#1 (1) { + ["i"]=> + int(5) +} +object(%s$$1)#1 (1) { + ["i"]=> + int(6) +} +object(%s$$1)#1 (1) { + ["i"]=> + int(7) +} +object(%s$$1)#1 (1) { + ["i"]=> + int(8) +} +object(%s$$1)#1 (1) { + ["i"]=> + int(9) +} +object(%s$$1)#1 (1) { + ["i"]=> + int(10) +} + diff --git a/Zend/tests/anon/004.phpt b/Zend/tests/anon/004.phpt new file mode 100644 index 000000000000..17203124c5e3 --- /dev/null +++ b/Zend/tests/anon/004.phpt @@ -0,0 +1,34 @@ +--TEST-- +testing anonymous inheritance +--FILE-- +data = $data; + } + + public function getArrayAccess() { + /* create a proxy object implementing array access */ + return new class extends Outer implements ArrayAccess { + public function offsetGet($offset) { return $this->data[$offset]; } + public function offsetSet($offset, $data) { return ($this->data[$offset] = $data); } + public function offsetUnset($offset) { unset($this->data[$offset]); } + public function offsetExists($offset) { return isset($this->data[$offset]); } + }($this->data); + } +} + +$outer = new Outer(array( + rand(1, 100) +)); + +$proxy = $outer->getArrayAccess(); + +var_dump($proxy[0]); +?> +--EXPECTF-- +int(%d) + + diff --git a/Zend/tests/anon/005.phpt b/Zend/tests/anon/005.phpt new file mode 100644 index 000000000000..eb0c52d68f3a --- /dev/null +++ b/Zend/tests/anon/005.phpt @@ -0,0 +1,39 @@ +--TEST-- +testing reusing anons that implement an interface +--FILE-- +data = &$data; + } + + public function getArrayAccess() { + /* create a child object implementing array access */ + /* this grants you access to protected methods and members */ + return new class extends Outer implements ArrayAccess { + public function offsetGet($offset) { return $this->data[$offset]; } + public function offsetSet($offset, $data) { return ($this->data[$offset] = $data); } + public function offsetUnset($offset) { unset($this->data[$offset]); } + public function offsetExists($offset) { return isset($this->data[$offset]); } + }($this->data); + } +} + +$data = array( + rand(1, 100), + rand(2, 200) +); +$outer = new Outer($data); +$proxy = $outer->getArrayAccess(); + +unset($proxy[0]); +var_dump(@$outer->getArrayAccess()[0]); +?> +--EXPECT-- +NULL + + + diff --git a/Zend/tests/anon/006.phpt b/Zend/tests/anon/006.phpt new file mode 100644 index 000000000000..335fcebbb0f9 --- /dev/null +++ b/Zend/tests/anon/006.phpt @@ -0,0 +1,18 @@ +--TEST-- +testing anons inside namespaces +--FILE-- + +--EXPECTF-- +object(%s$$1)#1 (0) { +} + + + diff --git a/Zend/tests/anon/007.phpt b/Zend/tests/anon/007.phpt new file mode 100644 index 000000000000..36a8098048ef --- /dev/null +++ b/Zend/tests/anon/007.phpt @@ -0,0 +1,26 @@ +--TEST-- +testing anons in functions outside of classes in namespaces +--FILE-- + +--EXPECT-- +object(my_factory$$1)#2 (0) { +} + + + diff --git a/Zend/zend.c b/Zend/zend.c index 11baf34c9356..c4d712012662 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -534,6 +534,7 @@ static void compiler_globals_ctor(zend_compiler_globals *compiler_globals TSRMLS compiler_globals->static_members_table = NULL; } compiler_globals->script_encoding_list = NULL; + compiler_globals->anon_class_id = 0L; } /* }}} */ diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 25a604c4405e..00b57132658d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2175,8 +2175,12 @@ void zend_resolve_class_name(znode *class_name TSRMLS_DC) /* {{{ */ zval **ns; znode tmp; int len; - + compound = memchr(Z_STRVAL(class_name->u.constant), '\\', Z_STRLEN(class_name->u.constant)); + + if ((class_name->EA & ZEND_ACC_ANON_CLASS) == ZEND_ACC_ANON_CLASS) + return; + if (compound) { /* This is a compound class name that contains namespace prefix */ if (Z_STRVAL(class_name->u.constant)[0] == '\\') { @@ -2219,7 +2223,8 @@ void zend_resolve_class_name(znode *class_name TSRMLS_DC) /* {{{ */ *class_name = tmp; } } - } else if (CG(current_import) || CG(current_namespace)) { + } else if ((CG(current_import) || CG(current_namespace)) && + !((class_name->EA & ZEND_ACC_ANON_CLASS) == ZEND_ACC_ANON_CLASS)) { /* this is a plain name (without \) */ lcname = zend_str_tolower_dup(Z_STRVAL(class_name->u.constant), Z_STRLEN(class_name->u.constant)); @@ -2237,11 +2242,64 @@ void zend_resolve_class_name(znode *class_name TSRMLS_DC) /* {{{ */ zend_do_build_namespace_name(&tmp, &tmp, class_name TSRMLS_CC); *class_name = tmp; } + efree(lcname); } } /* }}} */ +void zend_do_create_anon_class(znode *result TSRMLS_DC) { /* {{{ */ + char *class_name = NULL; + int class_name_len = 0; + char *prefix_name = NULL; + + /* give each class a unique id */ + CG(anon_class_id)++; + + /* prefix anonymous class with current scope for reference */ + if (CG(active_op_array)) { + if (CG(active_class_entry) || CG(active_op_array)->function_name) { + if (CG(active_class_entry)) { + prefix_name = (char*) CG(active_class_entry)->name; + } else { + prefix_name = (char*) CG(active_op_array)->function_name; + } + + /* remove namespace from entry name */ + if (CG(in_namespace)) { + prefix_name += Z_STRLEN_P( + CG(current_namespace)) + sizeof("\\")-1; + } + } else { + /* use filename as prefix for class */ + prefix_name = (char*) CG(active_op_array)->filename; + } + } + + if (!prefix_name) { + /* this shouldn't happen, possibly emit a warning ? */ + prefix_name = "Class"; + } + + /* populate znode constant with generated anonymous class name */ + Z_TYPE(result->u.constant) = IS_STRING; + class_name_len = snprintf( + NULL, 0, "%s$$%lu", prefix_name, CG(anon_class_id)); + class_name = (char*) emalloc(class_name_len+1); + snprintf( + class_name, class_name_len+1, + "%s$$%lu", prefix_name, CG(anon_class_id) + ); + class_name[class_name_len] = '\0'; + + Z_STRLEN(result->u.constant) = class_name_len; + Z_STRVAL(result->u.constant) = (char*) zend_new_interned_string( + class_name, class_name_len+1, 1 TSRMLS_CC); + + result->op_type = IS_CONST; + result->EA = ZEND_ACC_ANON_CLASS; +} /* }}} */ + void zend_do_fetch_class(znode *result, znode *class_name TSRMLS_DC) /* {{{ */ { long fetch_class_op_number; @@ -3773,11 +3831,16 @@ ZEND_API void zend_do_implement_interface(zend_class_entry *ce, zend_class_entry } else if (ce->interfaces[i] == iface) { if (i < parent_iface_num) { ignore = 1; - } else { + } else { + if ((ce->ce_flags & (ZEND_ACC_ANON_CLASS|ZEND_ACC_FINAL_CLASS)) == (ZEND_ACC_ANON_CLASS|ZEND_ACC_FINAL_CLASS)) { + continue; + } + zend_error(E_COMPILE_ERROR, "Class %s cannot implement previously implemented interface %s", ce->name, iface->name); } } } + if (ignore) { /* Check for attempt to redeclare interface constants */ zend_hash_apply_with_arguments(&ce->constants_table TSRMLS_CC, (apply_func_args_t) do_interface_constant_check, 1, &iface); @@ -4542,13 +4605,22 @@ ZEND_API zend_class_entry *do_bind_class(const zend_op_array* op_array, const ze op1 = opline->op1.zv; op2 = opline->op2.zv; } + if (zend_hash_quick_find(class_table, Z_STRVAL_P(op1), Z_STRLEN_P(op1), Z_HASH_P(op1), (void **) &pce)==FAILURE) { zend_error(E_COMPILE_ERROR, "Internal Zend error - Missing class information for %s", Z_STRVAL_P(op1)); return NULL; } else { ce = *pce; } + ce->refcount++; + + /* return anonymous class */ + if ((ce->ce_flags & (ZEND_ACC_ANON_CLASS|ZEND_ACC_FINAL_CLASS)) == (ZEND_ACC_ANON_CLASS|ZEND_ACC_FINAL_CLASS)) { + ce->refcount--; + return ce; + } + if (zend_hash_quick_add(class_table, Z_STRVAL_P(op2), Z_STRLEN_P(op2)+1, Z_HASH_P(op2), &ce, sizeof(zend_class_entry *), NULL)==FAILURE) { ce->refcount--; if (!compile_time) { @@ -4564,6 +4636,11 @@ ZEND_API zend_class_entry *do_bind_class(const zend_op_array* op_array, const ze if (!(ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_IMPLEMENT_INTERFACES|ZEND_ACC_IMPLEMENT_TRAITS))) { zend_verify_abstract_class(ce TSRMLS_CC); } + + /* set final anonymous class */ + if ((ce->ce_flags & ZEND_ACC_ANON_CLASS) == ZEND_ACC_ANON_CLASS) { + ce->ce_flags |= ZEND_ACC_FINAL_CLASS; + } return ce; } } @@ -4605,6 +4682,11 @@ ZEND_API zend_class_entry *do_bind_inherited_class(const zend_op_array *op_array zend_error(E_COMPILE_ERROR, "Class %s cannot extend from trait %s", ce->name, parent_ce->name); } + /* return anonymous class */ + if ((ce->ce_flags & (ZEND_ACC_ANON_CLASS|ZEND_ACC_FINAL_CLASS)) == (ZEND_ACC_ANON_CLASS|ZEND_ACC_FINAL_CLASS)) { + return ce; + } + zend_do_inheritance(ce, parent_ce TSRMLS_CC); ce->refcount++; @@ -4613,6 +4695,12 @@ ZEND_API zend_class_entry *do_bind_inherited_class(const zend_op_array *op_array if (zend_hash_quick_add(class_table, Z_STRVAL_P(op2), Z_STRLEN_P(op2)+1, Z_HASH_P(op2), pce, sizeof(zend_class_entry *), NULL)==FAILURE) { zend_error(E_COMPILE_ERROR, "Cannot redeclare class %s", ce->name); } + + /* set final anonymous class */ + if ((ce->ce_flags & ZEND_ACC_ANON_CLASS) == (ZEND_ACC_ANON_CLASS)) { + ce->ce_flags |= ZEND_ACC_FINAL_CLASS; + } + return ce; } /* }}} */ @@ -4962,7 +5050,7 @@ void zend_do_default_before_statement(const znode *case_list, znode *default_tok } /* }}} */ -void zend_do_begin_class_declaration(const znode *class_token, znode *class_name, const znode *parent_class_name TSRMLS_DC) /* {{{ */ +void zend_do_begin_class_declaration(znode *class_token, znode *class_name, const znode *parent_class_name TSRMLS_DC) /* {{{ */ { zend_op *opline; int doing_inheritance = 0; @@ -4970,11 +5058,13 @@ void zend_do_begin_class_declaration(const znode *class_token, znode *class_name char *lcname; int error = 0; zval **ns_name, key; + zend_uint opline_num = 0L; + + /* save opline_num before changing node */ + opline_num = class_token->u.op.opline_num; - if (CG(active_class_entry)) { - zend_error(E_COMPILE_ERROR, "Class declarations may not be nested"); - return; - } + /* save active class address */ + class_token->u.op.ptr = CG(active_class_entry) ? CG(active_class_entry) : NULL; lcname = zend_str_tolower_dup(Z_STRVAL(class_name->u.constant), Z_STRLEN(class_name->u.constant)); @@ -4988,8 +5078,13 @@ void zend_do_begin_class_declaration(const znode *class_token, znode *class_name zend_hash_find(CG(current_import), lcname, Z_STRLEN(class_name->u.constant)+1, (void**)&ns_name) == SUCCESS) { error = 1; } + + if (CG(active_class_entry) && !((class_token->EA & ZEND_ACC_ANON_CLASS) == ZEND_ACC_ANON_CLASS)) { + zend_error(E_COMPILE_ERROR, "Class declarations may not be nested"); + return; + } - if (CG(current_namespace)) { + if (CG(current_namespace) && !((class_token->EA & ZEND_ACC_ANON_CLASS) == ZEND_ACC_ANON_CLASS)) { /* Prefix class name with name of current namespace */ znode tmp; @@ -5019,7 +5114,7 @@ void zend_do_begin_class_declaration(const znode *class_token, znode *class_name zend_initialize_class_data(new_class_entry, 1 TSRMLS_CC); new_class_entry->info.user.filename = zend_get_compiled_filename(TSRMLS_C); - new_class_entry->info.user.line_start = class_token->u.op.opline_num; + new_class_entry->info.user.line_start = opline_num; new_class_entry->ce_flags |= class_token->EA; if (parent_class_name && parent_class_name->op_type != IS_UNUSED) { @@ -5063,7 +5158,7 @@ void zend_do_begin_class_declaration(const znode *class_token, znode *class_name CALCULATE_LITERAL_HASH(opline->op2.constant); zend_hash_quick_update(CG(class_table), Z_STRVAL(key), Z_STRLEN(key), Z_HASH_P(&CONSTANT(opline->op1.constant)), &new_class_entry, sizeof(zend_class_entry *), NULL); - CG(active_class_entry) = new_class_entry; + CG(active_class_entry) = new_class_entry; opline->result.var = get_temporary_variable(CG(active_op_array)); opline->result_type = IS_VAR; @@ -5075,6 +5170,7 @@ void zend_do_begin_class_declaration(const znode *class_token, znode *class_name CG(doc_comment) = NULL; CG(doc_comment_len) = 0; } + } /* }}} */ @@ -5146,7 +5242,8 @@ void zend_do_end_class_declaration(const znode *class_token, const znode *parent ce->ce_flags |= ZEND_ACC_IMPLEMENT_INTERFACES; } - CG(active_class_entry) = NULL; + /* restore active class entry*/ + CG(active_class_entry) = class_token->u.op.ptr; } /* }}} */ diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index f9520c98eacf..5122d9499099 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -83,7 +83,7 @@ typedef union _znode_op { zend_op *jmp_addr; zval *zv; zend_literal *literal; - void *ptr; /* Used for passing pointers from the compile to execution phase, currently used for traits */ + void *ptr; /* Used for passing pointers from the compile to execution phase, currently used for traits and nested anonymous class support */ } znode_op; typedef struct _znode { /* used only during compilation */ @@ -159,6 +159,7 @@ typedef struct _zend_try_catch_element { #define ZEND_ACC_FINAL_CLASS 0x40 #define ZEND_ACC_INTERFACE 0x80 #define ZEND_ACC_TRAIT 0x120 +#define ZEND_ACC_ANON_CLASS 0x400 /* op_array flags */ #define ZEND_ACC_INTERACTIVE 0x10 @@ -561,7 +562,8 @@ void zend_do_case_before_statement(const znode *case_list, znode *case_token, co void zend_do_case_after_statement(znode *result, const znode *case_token TSRMLS_DC); void zend_do_default_before_statement(const znode *case_list, znode *default_token TSRMLS_DC); -void zend_do_begin_class_declaration(const znode *class_token, znode *class_name, const znode *parent_class_name TSRMLS_DC); +void zend_do_create_anon_class(znode *result TSRMLS_DC); +void zend_do_begin_class_declaration(znode *class_token, znode *class_name, const znode *parent_class_name TSRMLS_DC); void zend_do_end_class_declaration(const znode *class_token, const znode *parent_token TSRMLS_DC); void zend_do_declare_property(const znode *var_name, const znode *value, zend_uint access_type TSRMLS_DC); void zend_do_declare_class_constant(znode *var_name, const znode *value TSRMLS_DC); diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index b9a5b39914a6..3bc5ffcec71d 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -150,6 +150,9 @@ struct _zend_compiler_globals { zend_bool multibyte; zend_bool detect_unicode; zend_bool encoding_declared; + + /* anonymous classes */ + zend_ulong anon_class_id; #ifdef ZTS zval ***static_members_table; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index d46149957ac5..1df5eb28efc0 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -737,8 +737,25 @@ instance_call: chaining_instance_call { zend_do_pop_object(&$$ TSRMLS_CC); zend_do_end_variable_parse(&$2, BP_VAR_R, 0 TSRMLS_CC); } ; +new_object: + T_NEW class_name_reference { zend_do_extended_fcall_begin(TSRMLS_C); zend_do_begin_new_object(&$1, &$2 TSRMLS_CC); } ctor_arguments { zend_do_end_new_object(&$$, &$1, &$4 TSRMLS_CC); zend_do_extended_fcall_end(TSRMLS_C);} +; + +anon_class_type: + T_CLASS { $$.u.op.opline_num = CG(zend_lineno); $$.EA = ZEND_ACC_ANON_CLASS; } +; + +anon_class_decl: + anon_class_type { zend_do_create_anon_class(&$$ TSRMLS_CC); } extends_from { zend_do_begin_class_declaration(&$1, &$2, &$3 TSRMLS_CC); } implements_list '{' class_statement_list '}' { zend_do_end_class_declaration(&$1, &$3 TSRMLS_CC); zend_do_fetch_class(&$$, &$2 TSRMLS_CC); } +; + +new_anon_object: + T_NEW anon_class_decl { zend_do_extended_fcall_begin(TSRMLS_C); zend_do_begin_new_object(&$1, &$2 TSRMLS_CC); } ctor_arguments { zend_do_end_new_object(&$$, &$1, &$4 TSRMLS_CC); zend_do_extended_fcall_end(TSRMLS_C); } +; + new_expr: - T_NEW class_name_reference { zend_do_extended_fcall_begin(TSRMLS_C); zend_do_begin_new_object(&$1, &$2 TSRMLS_CC); } ctor_arguments { zend_do_end_new_object(&$$, &$1, &$4 TSRMLS_CC); zend_do_extended_fcall_end(TSRMLS_C);} + new_object + | new_anon_object ; expr_without_variable: @@ -875,16 +892,16 @@ function_call: ; class_name: - T_STATIC { $$.op_type = IS_CONST; ZVAL_STRINGL(&$$.u.constant, "static", sizeof("static")-1, 1);} - | namespace_name { $$ = $1; } - | T_NAMESPACE T_NS_SEPARATOR namespace_name { $$.op_type = IS_CONST; ZVAL_EMPTY_STRING(&$$.u.constant); zend_do_build_namespace_name(&$$, &$$, &$3 TSRMLS_CC); } - | T_NS_SEPARATOR namespace_name { char *tmp = estrndup(Z_STRVAL($2.u.constant), Z_STRLEN($2.u.constant)+1); memcpy(&(tmp[1]), Z_STRVAL($2.u.constant), Z_STRLEN($2.u.constant)+1); tmp[0] = '\\'; efree(Z_STRVAL($2.u.constant)); Z_STRVAL($2.u.constant) = tmp; ++Z_STRLEN($2.u.constant); $$ = $2; } + T_STATIC { $$.EA = 0; $$.op_type = IS_CONST; ZVAL_STRINGL(&$$.u.constant, "static", sizeof("static")-1, 1);} + | namespace_name { $$ = $1; $$.EA = 0; } + | T_NAMESPACE T_NS_SEPARATOR namespace_name { $$.EA = 0; $$.op_type = IS_CONST; ZVAL_EMPTY_STRING(&$$.u.constant); zend_do_build_namespace_name(&$$, &$$, &$3 TSRMLS_CC); } + | T_NS_SEPARATOR namespace_name { char *tmp = estrndup(Z_STRVAL($2.u.constant), Z_STRLEN($2.u.constant)+1); memcpy(&(tmp[1]), Z_STRVAL($2.u.constant), Z_STRLEN($2.u.constant)+1); tmp[0] = '\\'; efree(Z_STRVAL($2.u.constant)); Z_STRVAL($2.u.constant) = tmp; ++Z_STRLEN($2.u.constant); $$ = $2; $$.EA = 0; } ; fully_qualified_class_name: - namespace_name { $$ = $1; } - | T_NAMESPACE T_NS_SEPARATOR namespace_name { $$.op_type = IS_CONST; ZVAL_EMPTY_STRING(&$$.u.constant); zend_do_build_namespace_name(&$$, &$$, &$3 TSRMLS_CC); } - | T_NS_SEPARATOR namespace_name { char *tmp = estrndup(Z_STRVAL($2.u.constant), Z_STRLEN($2.u.constant)+1); memcpy(&(tmp[1]), Z_STRVAL($2.u.constant), Z_STRLEN($2.u.constant)+1); tmp[0] = '\\'; efree(Z_STRVAL($2.u.constant)); Z_STRVAL($2.u.constant) = tmp; ++Z_STRLEN($2.u.constant); $$ = $2; } + namespace_name { $$ = $1; $$.EA = 0; } + | T_NAMESPACE T_NS_SEPARATOR namespace_name { $$.EA = 0; $$.op_type = IS_CONST; ZVAL_EMPTY_STRING(&$$.u.constant); zend_do_build_namespace_name(&$$, &$$, &$3 TSRMLS_CC); } + | T_NS_SEPARATOR namespace_name { char *tmp = estrndup(Z_STRVAL($2.u.constant), Z_STRLEN($2.u.constant)+1); memcpy(&(tmp[1]), Z_STRVAL($2.u.constant), Z_STRLEN($2.u.constant)+1); tmp[0] = '\\'; efree(Z_STRVAL($2.u.constant)); Z_STRVAL($2.u.constant) = tmp; ++Z_STRLEN($2.u.constant); $$ = $2; $$.EA = 0; } ; diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 41b4bd25710f..6392e0402c26 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -296,7 +296,13 @@ ZEND_API void destroy_zend_class(zend_class_entry **pce) efree(ce->default_static_members_table); } zend_hash_destroy(&ce->properties_info); - str_efree(ce->name); +#ifdef ZTS + if (!(ce->ce_flags & ZEND_ACC_ANON_CLASS)) { + str_efree(ce->name); + } +#else + str_efree(ce->name); +#endif zend_hash_destroy(&ce->function_table); zend_hash_destroy(&ce->constants_table); if (ce->num_interfaces > 0 && ce->interfaces) {