We need to make sure that the function is fully compiled before we
calculate the stack size. There already was a check for directly
recursive calls, but the same issue exists with indirectly recursive
calls.

I'm using DONE_PASS_TWO as the indication that the function is
fully compiled.
This commit is contained in:
Nikita Popov 2019-09-06 11:30:01 +02:00
parent df35d5cc21
commit e81751ceac
2 changed files with 40 additions and 8 deletions

31
Zend/tests/bug78502.phpt Normal file
View File

@ -0,0 +1,31 @@
--TEST--
Bug #78502: Incorrect stack size calculation for indirectly recursive function call
--FILE--
<?php
$tree = [
'name' => 'a',
'quant' => 1,
'children' => [
['name' => 'b', 'quant' => 1],
['name' => 'c', 'quant' => 1, 'children' => [
['name' => 'd', 'quant' => 1],
]],
],
];
function tree_map($tree, $recursive_attr, closure $callback){
if(isset($tree[$recursive_attr])){
$tree[$recursive_attr] = array_map(function($c) use($recursive_attr, $callback){
return tree_map($c, $recursive_attr, $callback);
}, $tree[$recursive_attr]);
}
return $callback($tree);
}
tree_map($tree, 'children', function ($node) {});
?>
===DONE===
--EXPECT--
===DONE===

View File

@ -3321,6 +3321,12 @@ int zend_compile_func_ord(znode *result, zend_ast_list *args) /* {{{ */
}
/* }}} */
/* We can only calculate the stack size for functions that have been fully compiled, otherwise
* additional CV or TMP slots may still be added. This prevents the use of INIT_FCALL for
* directly or indirectly recursive function calls. */
static zend_bool fbc_is_finalized(zend_function *fbc) {
return !ZEND_USER_CODE(fbc->type) || (fbc->common.fn_flags & ZEND_ACC_DONE_PASS_TWO);
}
static int zend_try_compile_ct_bound_init_user_func(zend_ast *name_ast, uint32_t num_args) /* {{{ */
{
@ -3336,9 +3342,7 @@ static int zend_try_compile_ct_bound_init_user_func(zend_ast *name_ast, uint32_t
lcname = zend_string_tolower(name);
fbc = zend_hash_find_ptr(CG(function_table), lcname);
if (!fbc
/* Don't use INIT_FCALL for recursive calls */
|| (fbc == (zend_function*)CG(active_op_array))
if (!fbc || !fbc_is_finalized(fbc)
|| (fbc->type == ZEND_INTERNAL_FUNCTION && (CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_FUNCTIONS))
|| (fbc->type == ZEND_USER_FUNCTION && (CG(compiler_options) & ZEND_COMPILE_IGNORE_USER_FUNCTIONS))
|| (fbc->type == ZEND_USER_FUNCTION && (CG(compiler_options) & ZEND_COMPILE_IGNORE_OTHER_FILES) && fbc->op_array.filename != CG(active_op_array)->filename)
@ -3461,8 +3465,7 @@ static void zend_compile_assert(znode *result, zend_ast_list *args, zend_string
zend_emit_op(NULL, ZEND_ASSERT_CHECK, NULL, NULL);
/* Don't use INIT_FCALL for recursive calls */
if (fbc && fbc != (zend_function*)CG(active_op_array)) {
if (fbc && fbc_is_finalized(fbc)) {
name_node.op_type = IS_CONST;
ZVAL_STR_COPY(&name_node.u.constant, name);
@ -3838,9 +3841,7 @@ void zend_compile_call(znode *result, zend_ast *ast, uint32_t type) /* {{{ */
return;
}
if (!fbc
/* Don't use INIT_FCALL for recursive calls */
|| (fbc == (zend_function*)CG(active_op_array))
if (!fbc || !fbc_is_finalized(fbc)
|| (fbc->type == ZEND_INTERNAL_FUNCTION && (CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_FUNCTIONS))
|| (fbc->type == ZEND_USER_FUNCTION && (CG(compiler_options) & ZEND_COMPILE_IGNORE_USER_FUNCTIONS))
|| (fbc->type == ZEND_USER_FUNCTION && (CG(compiler_options) & ZEND_COMPILE_IGNORE_OTHER_FILES) && fbc->op_array.filename != CG(active_op_array)->filename)