Skip to content

Commit

Permalink
Improved traits implementation. Now to support __CLASS__ constant in …
Browse files Browse the repository at this point in the history
…traits php doesn't have to copy the complete compiled method, but can reuse the same code. The resolution of __CLASS__ constants in methods defined in traits are delayed till run-time. This approach also made possible to use __CLASS__ constant as default value for traits properties and method arguments.
  • Loading branch information
dstogov committed Jan 17, 2012
1 parent 6b31413 commit b515bfb
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 154 deletions.
23 changes: 23 additions & 0 deletions Zend/tests/traits/bug55214.phpt
Expand Up @@ -4,13 +4,24 @@ Bug #55214 (Use of __CLASS__ within trait returns trait name not class name)
<?php <?php


trait ATrait { trait ATrait {
public static $static_var = __CLASS__;
public $var = __CLASS__;

public static function get_class_name() { public static function get_class_name() {
return __CLASS__; return __CLASS__;
} }


public function get_class_name_obj() { public function get_class_name_obj() {
return __CLASS__; return __CLASS__;
} }

public static function get_class_name2() {
return self::$static_var;
}

public function get_class_name_obj2() {
return $this->var;
}
} }


trait Indirect { trait Indirect {
Expand All @@ -27,22 +38,34 @@ class UsingIndirect {


$r = SomeClass::get_class_name(); $r = SomeClass::get_class_name();
var_dump($r); var_dump($r);
$r = SomeClass::get_class_name2();
var_dump($r);


$o = new SomeClass(); $o = new SomeClass();
$r = $o->get_class_name_obj(); $r = $o->get_class_name_obj();
var_dump($r); var_dump($r);
$r = $o->get_class_name_obj2();
var_dump($r);


$r = UsingIndirect::get_class_name(); $r = UsingIndirect::get_class_name();
var_dump($r); var_dump($r);
$r = UsingIndirect::get_class_name2();
var_dump($r);


$o = new UsingIndirect(); $o = new UsingIndirect();
$r = $o->get_class_name_obj(); $r = $o->get_class_name_obj();
var_dump($r); var_dump($r);
$r = $o->get_class_name_obj2();
var_dump($r);




?> ?>
--EXPECT-- --EXPECT--
string(9) "SomeClass" string(9) "SomeClass"
string(9) "SomeClass" string(9) "SomeClass"
string(9) "SomeClass"
string(9) "SomeClass"
string(13) "UsingIndirect"
string(13) "UsingIndirect"
string(13) "UsingIndirect" string(13) "UsingIndirect"
string(13) "UsingIndirect" string(13) "UsingIndirect"
146 changes: 7 additions & 139 deletions Zend/zend_compile.c
Expand Up @@ -3670,142 +3670,6 @@ static int zend_traits_merge_functions(zend_function *fn TSRMLS_DC, int num_args
} }
/* }}} */ /* }}} */


/* {{{ Originates from php_runkit_function_copy_ctor
Duplicate structures in an op_array where necessary to make an outright duplicate */
static void zend_traits_duplicate_function(zend_function *fe, zend_class_entry *target_ce, char *newname TSRMLS_DC)
{
zend_literal *literals_copy;
zend_compiled_variable *dupvars;
zend_op *opcode_copy;
zval class_name_zv;
int class_name_literal;
int i;
int number_of_literals;

if (fe->op_array.static_variables) {
HashTable *tmpHash;

ALLOC_HASHTABLE(tmpHash);
zend_hash_init(tmpHash, zend_hash_num_elements(fe->op_array.static_variables), NULL, ZVAL_PTR_DTOR, 0);
zend_hash_apply_with_arguments(fe->op_array.static_variables TSRMLS_CC, (apply_func_args_t)zval_copy_static_var, 1, tmpHash);

fe->op_array.static_variables = tmpHash;
}

number_of_literals = fe->op_array.last_literal;
literals_copy = (zend_literal*)emalloc(number_of_literals * sizeof(zend_literal));

for (i = 0; i < number_of_literals; i++) {
literals_copy[i] = fe->op_array.literals[i];
zval_copy_ctor(&literals_copy[i].constant);
}
fe->op_array.literals = literals_copy;


fe->op_array.refcount = emalloc(sizeof(zend_uint));
*(fe->op_array.refcount) = 1;

if (fe->op_array.vars) {
i = fe->op_array.last_var;
dupvars = safe_emalloc(fe->op_array.last_var, sizeof(zend_compiled_variable), 0);
while (i > 0) {
i--;
dupvars[i].name = estrndup(fe->op_array.vars[i].name, fe->op_array.vars[i].name_len);
dupvars[i].name_len = fe->op_array.vars[i].name_len;
dupvars[i].hash_value = fe->op_array.vars[i].hash_value;
}
fe->op_array.vars = dupvars;
} else {
fe->op_array.vars = NULL;
}

opcode_copy = safe_emalloc(sizeof(zend_op), fe->op_array.last, 0);
for(i = 0; i < fe->op_array.last; i++) {
opcode_copy[i] = fe->op_array.opcodes[i];
if (opcode_copy[i].op1_type != IS_CONST) {
switch (opcode_copy[i].opcode) {
case ZEND_GOTO:
case ZEND_JMP:
if (opcode_copy[i].op1.jmp_addr && opcode_copy[i].op1.jmp_addr >= fe->op_array.opcodes &&
opcode_copy[i].op1.jmp_addr < fe->op_array.opcodes + fe->op_array.last) {
opcode_copy[i].op1.jmp_addr = opcode_copy + (fe->op_array.opcodes[i].op1.jmp_addr - fe->op_array.opcodes);
}
break;
}
} else {
/* if __CLASS__ i.e. T_CLASS_C was used, we need to fix it up here */
if (target_ce
/* REM: used a IS_NULL place holder with a special marker LVAL */
&& Z_TYPE_P(opcode_copy[i].op1.zv) == IS_NULL
&& Z_LVAL_P(opcode_copy[i].op1.zv) == ZEND_ACC_TRAIT
/* Only on merge into an actual class */
&& (ZEND_ACC_TRAIT != (target_ce->ce_flags & ZEND_ACC_TRAIT)))
{
INIT_ZVAL(class_name_zv);
ZVAL_STRINGL(&class_name_zv, target_ce->name, target_ce->name_length, 1);
class_name_literal = zend_append_individual_literal(&fe->op_array, &class_name_zv TSRMLS_CC);
opcode_copy[i].op1.zv = &fe->op_array.literals[class_name_literal].constant;
}
}

if (opcode_copy[i].op2_type != IS_CONST) {
switch (opcode_copy[i].opcode) {
case ZEND_JMPZ:
case ZEND_JMPNZ:
case ZEND_JMPZ_EX:
case ZEND_JMPNZ_EX:
case ZEND_JMP_SET:
case ZEND_JMP_SET_VAR:
if (opcode_copy[i].op2.jmp_addr && opcode_copy[i].op2.jmp_addr >= fe->op_array.opcodes &&
opcode_copy[i].op2.jmp_addr < fe->op_array.opcodes + fe->op_array.last) {
opcode_copy[i].op2.jmp_addr = opcode_copy + (fe->op_array.opcodes[i].op2.jmp_addr - fe->op_array.opcodes);
}
break;
}
} else {
/* if __CLASS__ i.e. T_CLASS_C was used, we need to fix it up here */
if (target_ce
/* REM: used a IS_NULL place holder with a special marker LVAL */
&& Z_TYPE_P(opcode_copy[i].op2.zv) == IS_NULL
&& Z_LVAL_P(opcode_copy[i].op2.zv) == ZEND_ACC_TRAIT
/* Only on merge into an actual class */
&& (ZEND_ACC_TRAIT != (target_ce->ce_flags & ZEND_ACC_TRAIT)))
{
INIT_ZVAL(class_name_zv);
ZVAL_STRINGL(&class_name_zv, target_ce->name, target_ce->name_length, 1);
class_name_literal = zend_append_individual_literal(&fe->op_array, &class_name_zv TSRMLS_CC);
opcode_copy[i].op2.zv = &fe->op_array.literals[class_name_literal].constant;
}
}
}
fe->op_array.opcodes = opcode_copy;
fe->op_array.function_name = newname;

/* was setting it to fe which does not work since fe is stack allocated and not a stable address */
/* fe->op_array.prototype = fe->op_array.prototype; */
if (fe->op_array.arg_info) {
zend_arg_info *tmpArginfo;

tmpArginfo = safe_emalloc(sizeof(zend_arg_info), fe->op_array.num_args, 0);
for(i = 0; i < fe->op_array.num_args; i++) {
tmpArginfo[i] = fe->op_array.arg_info[i];

tmpArginfo[i].name = estrndup(tmpArginfo[i].name, tmpArginfo[i].name_len);
if (tmpArginfo[i].class_name) {
tmpArginfo[i].class_name = estrndup(tmpArginfo[i].class_name, tmpArginfo[i].class_name_len);
}
}
fe->op_array.arg_info = tmpArginfo;
}

fe->op_array.doc_comment = estrndup(fe->op_array.doc_comment, fe->op_array.doc_comment_len);
fe->op_array.try_catch_array = (zend_try_catch_element*)estrndup((char*)fe->op_array.try_catch_array, sizeof(zend_try_catch_element) * fe->op_array.last_try_catch);

fe->op_array.brk_cont_array = (zend_brk_cont_element*)estrndup((char*)fe->op_array.brk_cont_array, sizeof(zend_brk_cont_element) * fe->op_array.last_brk_cont);

}
/* }}} */

static void zend_add_magic_methods(zend_class_entry* ce, const char* mname, uint mname_len, zend_function* fe TSRMLS_DC) /* {{{ */ static void zend_add_magic_methods(zend_class_entry* ce, const char* mname, uint mname_len, zend_function* fe TSRMLS_DC) /* {{{ */
{ {
if (!strncmp(mname, ZEND_CLONE_FUNC_NAME, mname_len)) { if (!strncmp(mname, ZEND_CLONE_FUNC_NAME, mname_len)) {
Expand Down Expand Up @@ -3916,7 +3780,7 @@ static int zend_traits_merge_functions_to_class(zend_function *fn TSRMLS_DC, int
ce->ce_flags |= ZEND_HAS_STATIC_IN_METHODS; ce->ce_flags |= ZEND_HAS_STATIC_IN_METHODS;
} }
fn_copy = *fn; fn_copy = *fn;
zend_traits_duplicate_function(&fn_copy, ce, estrdup(fn->common.function_name) TSRMLS_CC); function_add_ref(&fn_copy);


if (zend_hash_quick_update(&ce->function_table, hash_key->arKey, hash_key->nKeyLength, hash_key->h, &fn_copy, sizeof(zend_function), (void**)&fn_copy_p)==FAILURE) { if (zend_hash_quick_update(&ce->function_table, hash_key->arKey, hash_key->nKeyLength, hash_key->h, &fn_copy, sizeof(zend_function), (void**)&fn_copy_p)==FAILURE) {
zend_error(E_COMPILE_ERROR, "Trait method %s has not been applied, because failure occured during updating class method table", hash_key->arKey); zend_error(E_COMPILE_ERROR, "Trait method %s has not been applied, because failure occured during updating class method table", hash_key->arKey);
Expand Down Expand Up @@ -3961,7 +3825,11 @@ static int zend_traits_copy_functions(zend_function *fn TSRMLS_DC, int num_args,
&& aliases[i]->trait_method->mname_len == fnname_len && aliases[i]->trait_method->mname_len == fnname_len
&& (zend_binary_strcasecmp(aliases[i]->trait_method->method_name, aliases[i]->trait_method->mname_len, fn->common.function_name, fnname_len) == 0)) { && (zend_binary_strcasecmp(aliases[i]->trait_method->method_name, aliases[i]->trait_method->mname_len, fn->common.function_name, fnname_len) == 0)) {
fn_copy = *fn; fn_copy = *fn;
zend_traits_duplicate_function(&fn_copy, NULL, estrndup(aliases[i]->alias, aliases[i]->alias_len) TSRMLS_CC); function_add_ref(&fn_copy);
/* this function_name is never destroyed, because its refcount
greater than 1, classes are always destoyed in reverse order
and trait is declared early than this class */
fn_copy.common.function_name = aliases[i]->alias;


/* if it is 0, no modifieres has been changed */ /* if it is 0, no modifieres has been changed */
if (aliases[i]->modifiers) { if (aliases[i]->modifiers) {
Expand Down Expand Up @@ -3993,7 +3861,7 @@ static int zend_traits_copy_functions(zend_function *fn TSRMLS_DC, int num_args,
if (exclude_table == NULL || zend_hash_find(exclude_table, lcname, fnname_len, &dummy) == FAILURE) { if (exclude_table == NULL || zend_hash_find(exclude_table, lcname, fnname_len, &dummy) == FAILURE) {
/* is not in hashtable, thus, function is not to be excluded */ /* is not in hashtable, thus, function is not to be excluded */
fn_copy = *fn; fn_copy = *fn;
zend_traits_duplicate_function(&fn_copy, NULL, estrndup(fn->common.function_name, fnname_len) TSRMLS_CC); function_add_ref(&fn_copy);


/* apply aliases which are not qualified by a class name, or which have not /* apply aliases which are not qualified by a class name, or which have not
* alias name, just setting visibility */ * alias name, just setting visibility */
Expand Down
40 changes: 36 additions & 4 deletions Zend/zend_constants.c
Expand Up @@ -224,13 +224,45 @@ ZEND_API void zend_register_string_constant(const char *name, uint name_len, cha
zend_register_stringl_constant(name, name_len, strval, strlen(strval), flags, module_number TSRMLS_CC); zend_register_stringl_constant(name, name_len, strval, strlen(strval), flags, module_number TSRMLS_CC);
} }


static int zend_get_halt_offset_constant(const char *name, uint name_len, zend_constant **c TSRMLS_DC) static int zend_get_special_constant(const char *name, uint name_len, zend_constant **c TSRMLS_DC)
{ {
int ret; int ret;
static char haltoff[] = "__COMPILER_HALT_OFFSET__"; static char haltoff[] = "__COMPILER_HALT_OFFSET__";


if (!EG(in_execution)) { if (!EG(in_execution)) {
return 0; return 0;
} else if (name_len == sizeof("__CLASS__")-1 &&
!memcmp(name, "__CLASS__", sizeof("__CLASS__")-1)) {
zend_constant tmp;

/* Returned constants may be cached, so they have to be stored */
if (EG(scope) && EG(scope)->name) {
int const_name_len;
char *const_name;
ALLOCA_FLAG(use_heap)

const_name_len = sizeof("\0__CLASS__") + EG(scope)->name_length;
const_name = do_alloca(const_name_len, use_heap);
memcpy(const_name, "\0__CLASS__", sizeof("\0__CLASS__")-1);
zend_str_tolower_copy(const_name + sizeof("\0__CLASS__")-1, EG(scope)->name, EG(scope)->name_length);
if (zend_hash_find(EG(zend_constants), const_name, const_name_len, (void**)c) == FAILURE) {
zend_hash_add(EG(zend_constants), const_name, const_name_len, (void*)&tmp, sizeof(zend_constant), (void**)c);
memset(*c, 0, sizeof(zend_constant));
Z_STRVAL((**c).value) = estrndup(EG(scope)->name, EG(scope)->name_length);
Z_STRLEN((**c).value) = EG(scope)->name_length;
Z_TYPE((**c).value) = IS_STRING;
}
free_alloca(const_name, use_heap);
} else {
if (zend_hash_find(EG(zend_constants), "\0__CLASS__", sizeof("\0__CLASS__"), (void**)c) == FAILURE) {
zend_hash_add(EG(zend_constants), "\0__CLASS__", sizeof("\0__CLASS__"), (void*)&tmp, sizeof(zend_constant), (void**)c);
memset(*c, 0, sizeof(zend_constant));
Z_STRVAL((**c).value) = estrndup("", 0);
Z_STRLEN((**c).value) = 0;
Z_TYPE((**c).value) = IS_STRING;
}
}
return 1;
} else if (name_len == sizeof("__COMPILER_HALT_OFFSET__")-1 && } else if (name_len == sizeof("__COMPILER_HALT_OFFSET__")-1 &&
!memcmp(name, "__COMPILER_HALT_OFFSET__", sizeof("__COMPILER_HALT_OFFSET__")-1)) { !memcmp(name, "__COMPILER_HALT_OFFSET__", sizeof("__COMPILER_HALT_OFFSET__")-1)) {
const char *cfilename; const char *cfilename;
Expand Down Expand Up @@ -265,7 +297,7 @@ ZEND_API int zend_get_constant(const char *name, uint name_len, zval *result TSR
retval=0; retval=0;
} }
} else { } else {
retval = zend_get_halt_offset_constant(name, name_len, &c TSRMLS_CC); retval = zend_get_special_constant(name, name_len, &c TSRMLS_CC);
} }
efree(lookup_name); efree(lookup_name);
} }
Expand Down Expand Up @@ -432,14 +464,14 @@ zend_constant *zend_quick_get_constant(const zend_literal *key, ulong flags TSRM
(c->flags & CONST_CS) != 0) { (c->flags & CONST_CS) != 0) {


key--; key--;
if (!zend_get_halt_offset_constant(Z_STRVAL(key->constant), Z_STRLEN(key->constant), &c TSRMLS_CC)) { if (!zend_get_special_constant(Z_STRVAL(key->constant), Z_STRLEN(key->constant), &c TSRMLS_CC)) {
return NULL; return NULL;
} }
} }
} }
} else { } else {
key--; key--;
if (!zend_get_halt_offset_constant(Z_STRVAL(key->constant), Z_STRLEN(key->constant), &c TSRMLS_CC)) { if (!zend_get_special_constant(Z_STRVAL(key->constant), Z_STRLEN(key->constant), &c TSRMLS_CC)) {
return NULL; return NULL;
} }
} }
Expand Down
8 changes: 4 additions & 4 deletions Zend/zend_execute_API.c
Expand Up @@ -110,7 +110,7 @@ static int clean_non_persistent_function(zend_function *function TSRMLS_DC) /* {


static int clean_non_persistent_function_full(zend_function *function TSRMLS_DC) /* {{{ */ static int clean_non_persistent_function_full(zend_function *function TSRMLS_DC) /* {{{ */
{ {
return (function->type != ZEND_INTERNAL_FUNCTION); return (function->type == ZEND_INTERNAL_FUNCTION) ? ZEND_HASH_APPLY_KEEP : ZEND_HASH_APPLY_REMOVE;
} }
/* }}} */ /* }}} */


Expand All @@ -122,7 +122,7 @@ static int clean_non_persistent_class(zend_class_entry **ce TSRMLS_DC) /* {{{ */


static int clean_non_persistent_class_full(zend_class_entry **ce TSRMLS_DC) /* {{{ */ static int clean_non_persistent_class_full(zend_class_entry **ce TSRMLS_DC) /* {{{ */
{ {
return ((*ce)->type != ZEND_INTERNAL_CLASS); return ((*ce)->type == ZEND_INTERNAL_CLASS) ? ZEND_HASH_APPLY_KEEP : ZEND_HASH_APPLY_REMOVE;
} }
/* }}} */ /* }}} */


Expand Down Expand Up @@ -298,8 +298,8 @@ void shutdown_executor(TSRMLS_D) /* {{{ */


/* Destroy all op arrays */ /* Destroy all op arrays */
if (EG(full_tables_cleanup)) { if (EG(full_tables_cleanup)) {
zend_hash_apply(EG(function_table), (apply_func_t) clean_non_persistent_function_full TSRMLS_CC); zend_hash_reverse_apply(EG(function_table), (apply_func_t) clean_non_persistent_function_full TSRMLS_CC);
zend_hash_apply(EG(class_table), (apply_func_t) clean_non_persistent_class_full TSRMLS_CC); zend_hash_reverse_apply(EG(class_table), (apply_func_t) clean_non_persistent_class_full TSRMLS_CC);
} else { } else {
zend_hash_reverse_apply(EG(function_table), (apply_func_t) clean_non_persistent_function TSRMLS_CC); zend_hash_reverse_apply(EG(function_table), (apply_func_t) clean_non_persistent_function TSRMLS_CC);
zend_hash_reverse_apply(EG(class_table), (apply_func_t) clean_non_persistent_class TSRMLS_CC); zend_hash_reverse_apply(EG(class_table), (apply_func_t) clean_non_persistent_class TSRMLS_CC);
Expand Down
3 changes: 2 additions & 1 deletion Zend/zend_language_parser.y
Expand Up @@ -907,7 +907,6 @@ common_scalar:
| T_LINE { $$ = $1; } | T_LINE { $$ = $1; }
| T_FILE { $$ = $1; } | T_FILE { $$ = $1; }
| T_DIR { $$ = $1; } | T_DIR { $$ = $1; }
| T_CLASS_C { $$ = $1; }
| T_TRAIT_C { $$ = $1; } | T_TRAIT_C { $$ = $1; }
| T_METHOD_C { $$ = $1; } | T_METHOD_C { $$ = $1; }
| T_FUNC_C { $$ = $1; } | T_FUNC_C { $$ = $1; }
Expand All @@ -927,6 +926,7 @@ static_scalar: /* compile-time evaluated scalars */
| T_ARRAY '(' static_array_pair_list ')' { $$ = $3; Z_TYPE($$.u.constant) = IS_CONSTANT_ARRAY; } | T_ARRAY '(' static_array_pair_list ')' { $$ = $3; Z_TYPE($$.u.constant) = IS_CONSTANT_ARRAY; }
| '[' static_array_pair_list ']' { $$ = $2; Z_TYPE($$.u.constant) = IS_CONSTANT_ARRAY; } | '[' static_array_pair_list ']' { $$ = $2; Z_TYPE($$.u.constant) = IS_CONSTANT_ARRAY; }
| static_class_constant { $$ = $1; } | static_class_constant { $$ = $1; }
| T_CLASS_C { $$ = $1; }
; ;


static_class_constant: static_class_constant:
Expand All @@ -942,6 +942,7 @@ scalar:
| common_scalar { $$ = $1; } | common_scalar { $$ = $1; }
| '"' encaps_list '"' { $$ = $2; } | '"' encaps_list '"' { $$ = $2; }
| T_START_HEREDOC encaps_list T_END_HEREDOC { $$ = $2; CG(heredoc) = Z_STRVAL($1.u.constant); CG(heredoc_len) = Z_STRLEN($1.u.constant); } | T_START_HEREDOC encaps_list T_END_HEREDOC { $$ = $2; CG(heredoc) = Z_STRVAL($1.u.constant); CG(heredoc_len) = Z_STRLEN($1.u.constant); }
| T_CLASS_C { if (Z_TYPE($1.u.constant) == IS_CONSTANT) {zend_do_fetch_constant(&$$, NULL, &$1, ZEND_RT, 1 TSRMLS_CC);} else {$$ = $1;} }
; ;




Expand Down
10 changes: 5 additions & 5 deletions Zend/zend_language_scanner.l
Expand Up @@ -1564,11 +1564,11 @@ NEWLINE ("\r"|"\n"|"\r\n")
if (CG(active_class_entry) if (CG(active_class_entry)
&& (ZEND_ACC_TRAIT == && (ZEND_ACC_TRAIT ==
(CG(active_class_entry)->ce_flags & ZEND_ACC_TRAIT))) { (CG(active_class_entry)->ce_flags & ZEND_ACC_TRAIT))) {
// This is a hack, we abuse IS_NULL to indicate an invalid value /* We create a special __CLASS__ constant that is going to be resolved
// if __CLASS__ is encountered in a trait, however, we also not that we at run-time */
// should fix it up when we copy the method into an actual class zendlval->value.str.len = sizeof("__CLASS__")-1;
zendlval->value.lval = ZEND_ACC_TRAIT; zendlval->value.str.val = estrndup("__CLASS__", zendlval->value.str.len);
zendlval->type = IS_NULL; zendlval->type = IS_CONSTANT;
} else { } else {
if (CG(active_class_entry)) { if (CG(active_class_entry)) {
class_name = CG(active_class_entry)->name; class_name = CG(active_class_entry)->name;
Expand Down
1 change: 0 additions & 1 deletion ext/reflection/tests/traits001.phpt
Expand Up @@ -63,7 +63,6 @@ Class [ <user> class Bar ] {
@@ %straits001.php 9 - 9 @@ %straits001.php 9 - 9
} }



Method [ <user> public method someMethod ] { Method [ <user> public method someMethod ] {
@@ %straits001.php 3 - 3 @@ %straits001.php 3 - 3
} }
Expand Down

0 comments on commit b515bfb

Please sign in to comment.