diff --git a/UPGRADING b/UPGRADING index 20c3071bd70..00dfe8ae5df 100644 --- a/UPGRADING +++ b/UPGRADING @@ -125,6 +125,11 @@ PHP 8.0 UPGRADE NOTES warning. . Uncaught exceptions now go through "clean shutdown", which means that destructors will be called after an uncaught exception. + . Compile time fatal error "Only variables can be passed by reference" has been + delayed until run-time and converted to "Cannot pass parameter by reference" + exception. + . Some "Only variables should be passed by reference" notices have been converted + to "Cannot pass parameter by reference" exception. - COM: . Removed the ability to import case-insensitive constants from type diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS index 8e7cb668265..297d1d7b2cc 100644 --- a/UPGRADING.INTERNALS +++ b/UPGRADING.INTERNALS @@ -11,6 +11,8 @@ PHP 8.0 INTERNALS UPGRADE NOTES h. zend_value_error() i. get_closure() object handler j. compare_objects() and compare() object handlers + k. The 'I' length modifier + l. Some VM instructions switched to IS_TMP_VAR result insted of IS_VAR 2. Build system changes a. Abstract @@ -89,6 +91,13 @@ PHP 8.0 INTERNALS UPGRADE NOTES The 'v' format from the custom snprintf and spprintf implementations has been removed. Use the standard 's' format instead. + l. Some VM instructions switched to IS_TMP_VAR result insted of IS_VAR. + Actually, all assignments (ZEND_ASSIGN, ZEND_ASSIGN_DIM, ZEND_ASSIGN_OBJ, + ZEND_ASSIGN_STATIC_PROP), all compound assignments (ZEND_ASSIGN_OP, + ZEND_ASSIGN_DIM_OP, ZEND_ASSIGN_OBJ_OP, ZEND_ASSIGN_STATIC_PROP_OP) and all + pre increments/decrements (ZEND_PRE_INC, ZEND_PRE_DEC, ZEND_PRE_INC_OBJ + ZEND_PRE_DEC_OBJ, ZEND_PRE_INC_STATIC_PROP ZEND_PRE_DEC_STATIC_PROP). + ======================== 2. Build system changes ======================== diff --git a/Zend/tests/bug72038.phpt b/Zend/tests/bug72038.phpt index 9ce82268c16..44207af965d 100644 --- a/Zend/tests/bug72038.phpt +++ b/Zend/tests/bug72038.phpt @@ -3,23 +3,31 @@ Bug #72038 (Function calls with values to a by-ref parameter don't always throw --FILE-- getMessage() . "\n"; +} +try { + test($bar = 2); + var_dump($bar); +} catch (Throwable $e) { + echo "Exception: " . $e->getMessage() . "\n"; +} +try { + test($baz = &$bar); + var_dump($baz); +} catch (Throwable $e) { + echo "Exception: " . $e->getMessage() . "\n"; +} function test(&$param) { $param = 1; } ?> ---EXPECTF-- -Notice: Only variables should be passed by reference in %s on line %d -object(stdClass)#1 (0) { -} - -Notice: Only variables should be passed by reference in %s on line %d -int(2) +--EXPECT-- +Exception: Cannot pass parameter 1 by reference +Exception: Cannot pass parameter 1 by reference int(1) diff --git a/Zend/tests/bug73663_2.phpt b/Zend/tests/bug73663_2.phpt index 6c82fd28db0..72fb44df894 100644 --- a/Zend/tests/bug73663_2.phpt +++ b/Zend/tests/bug73663_2.phpt @@ -12,4 +12,7 @@ change(list($val) = $array); var_dump($array); ?> --EXPECTF-- -Fatal error: Only variables can be passed by reference in %s on line %d +Fatal error: Uncaught Error: Cannot pass parameter 1 by reference in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/bug78154.phpt b/Zend/tests/bug78154.phpt index 2a69ebe8110..4016a88973e 100644 --- a/Zend/tests/bug78154.phpt +++ b/Zend/tests/bug78154.phpt @@ -4,20 +4,23 @@ Bug #78154: SEND_VAR_NO_REF does not always send reference getMessage() . "\n"; + } } namespace Foo { - var_dump(similar_text('a', 'a', $d=0x44444444)); - var_dump($d); + try { + var_dump(similar_text('a', 'a', $d=0x44444444)); + var_dump($d); + } catch (\Throwable $e) { + echo "Exception: " . $e->getMessage() . "\n"; + } } ?> ---EXPECTF-- -Notice: Only variables should be passed by reference in %s on line %d -int(1) -int(1145324612) - -Notice: Only variables should be passed by reference in %s on line %d -int(1) -int(1145324612) +--EXPECT-- +Exception: Cannot pass parameter 3 by reference +Exception: Cannot pass parameter 3 by reference diff --git a/Zend/tests/errmsg_022.phpt b/Zend/tests/errmsg_022.phpt index aba8d750c6e..068ce147f37 100644 --- a/Zend/tests/errmsg_022.phpt +++ b/Zend/tests/errmsg_022.phpt @@ -11,4 +11,7 @@ foo(1); echo "Done\n"; ?> --EXPECTF-- -Fatal error: Only variables can be passed by reference in %s on line %d +Fatal error: Uncaught Error: Cannot pass parameter 1 by reference in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/variadic/by_ref_error.phpt b/Zend/tests/variadic/by_ref_error.phpt index 7f210141464..bc7378785a8 100644 --- a/Zend/tests/variadic/by_ref_error.phpt +++ b/Zend/tests/variadic/by_ref_error.phpt @@ -9,4 +9,7 @@ test(1); ?> --EXPECTF-- -Fatal error: Only variables can be passed by reference in %s on line %d +Fatal error: Uncaught Error: Cannot pass parameter 1 by reference in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b915b7f8d69..d17185fbb8a 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -718,7 +718,8 @@ void zend_do_free(znode *op1) /* {{{ */ if (op1->op_type == IS_TMP_VAR) { zend_op *opline = &CG(active_op_array)->opcodes[CG(active_op_array)->last-1]; - while (opline->opcode == ZEND_END_SILENCE) { + while (opline->opcode == ZEND_END_SILENCE || + opline->opcode == ZEND_OP_DATA) { opline--; } @@ -738,6 +739,22 @@ void zend_do_free(znode *op1) /* {{{ */ opline->opcode -= 2; opline->result_type = IS_UNUSED; return; + case ZEND_ASSIGN: + case ZEND_ASSIGN_DIM: + case ZEND_ASSIGN_OBJ: + case ZEND_ASSIGN_STATIC_PROP: + case ZEND_ASSIGN_OP: + case ZEND_ASSIGN_DIM_OP: + case ZEND_ASSIGN_OBJ_OP: + case ZEND_ASSIGN_STATIC_PROP_OP: + case ZEND_PRE_INC_STATIC_PROP: + case ZEND_PRE_DEC_STATIC_PROP: + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_PRE_INC: + case ZEND_PRE_DEC: + opline->result_type = IS_UNUSED; + return; } } @@ -2921,7 +2938,7 @@ void zend_compile_assign(znode *result, zend_ast *ast) /* {{{ */ zend_delayed_compile_var(&var_node, var_ast, BP_VAR_W, 0); zend_compile_expr(&expr_node, expr_ast); zend_delayed_compile_end(offset); - zend_emit_op(result, ZEND_ASSIGN, &var_node, &expr_node); + zend_emit_op_tmp(result, ZEND_ASSIGN, &var_node, &expr_node); return; case ZEND_AST_STATIC_PROP: offset = zend_delayed_compile_begin(); @@ -2930,6 +2947,8 @@ void zend_compile_assign(znode *result, zend_ast *ast) /* {{{ */ opline = zend_delayed_compile_end(offset); opline->opcode = ZEND_ASSIGN_STATIC_PROP; + opline->result_type = IS_TMP_VAR; + result->op_type = IS_TMP_VAR; zend_emit_op_data(&expr_node); return; @@ -2953,6 +2972,8 @@ void zend_compile_assign(znode *result, zend_ast *ast) /* {{{ */ opline = zend_delayed_compile_end(offset); opline->opcode = ZEND_ASSIGN_DIM; + opline->result_type = IS_TMP_VAR; + result->op_type = IS_TMP_VAR; opline = zend_emit_op_data(&expr_node); return; @@ -2963,6 +2984,8 @@ void zend_compile_assign(znode *result, zend_ast *ast) /* {{{ */ opline = zend_delayed_compile_end(offset); opline->opcode = ZEND_ASSIGN_OBJ; + opline->result_type = IS_TMP_VAR; + result->op_type = IS_TMP_VAR; zend_emit_op_data(&expr_node); return; @@ -3086,7 +3109,7 @@ void zend_compile_compound_assign(znode *result, zend_ast *ast) /* {{{ */ zend_delayed_compile_var(&var_node, var_ast, BP_VAR_RW, 0); zend_compile_expr(&expr_node, expr_ast); zend_delayed_compile_end(offset); - opline = zend_emit_op(result, ZEND_ASSIGN_OP, &var_node, &expr_node); + opline = zend_emit_op_tmp(result, ZEND_ASSIGN_OP, &var_node, &expr_node); opline->extended_value = opcode; return; case ZEND_AST_STATIC_PROP: @@ -3098,6 +3121,8 @@ void zend_compile_compound_assign(znode *result, zend_ast *ast) /* {{{ */ cache_slot = opline->extended_value; opline->opcode = ZEND_ASSIGN_STATIC_PROP_OP; opline->extended_value = opcode; + opline->result_type = IS_TMP_VAR; + result->op_type = IS_TMP_VAR; opline = zend_emit_op_data(&expr_node); opline->extended_value = cache_slot; @@ -3110,6 +3135,8 @@ void zend_compile_compound_assign(znode *result, zend_ast *ast) /* {{{ */ opline = zend_delayed_compile_end(offset); opline->opcode = ZEND_ASSIGN_DIM_OP; opline->extended_value = opcode; + opline->result_type = IS_TMP_VAR; + result->op_type = IS_TMP_VAR; zend_emit_op_data(&expr_node); return; @@ -3122,6 +3149,8 @@ void zend_compile_compound_assign(znode *result, zend_ast *ast) /* {{{ */ cache_slot = opline->extended_value; opline->opcode = ZEND_ASSIGN_OBJ_OP; opline->extended_value = opcode; + opline->result_type = IS_TMP_VAR; + result->op_type = IS_TMP_VAR; opline = zend_emit_op_data(&expr_node); opline->extended_value = cache_slot; @@ -3236,11 +3265,9 @@ uint32_t zend_compile_args(zend_ast *ast, zend_function *fbc) /* {{{ */ opcode = ZEND_SEND_VAR_EX; } } else { - if (fbc) { + /* Delay "Only variables can be passed by reference" error to execution */ + if (fbc && !ARG_MUST_BE_SENT_BY_REF(fbc, arg_num)) { opcode = ZEND_SEND_VAL; - if (ARG_MUST_BE_SENT_BY_REF(fbc, arg_num)) { - zend_error_noreturn(E_COMPILE_ERROR, "Only variables can be passed by reference"); - } } else { opcode = ZEND_SEND_VAL_EX; } @@ -7577,13 +7604,17 @@ void zend_compile_pre_incdec(znode *result, zend_ast *ast) /* {{{ */ if (var_ast->kind == ZEND_AST_PROP) { zend_op *opline = zend_compile_prop(result, var_ast, BP_VAR_RW, 0); opline->opcode = ast->kind == ZEND_AST_PRE_INC ? ZEND_PRE_INC_OBJ : ZEND_PRE_DEC_OBJ; + opline->result_type = IS_TMP_VAR; + result->op_type = IS_TMP_VAR; } else if (var_ast->kind == ZEND_AST_STATIC_PROP) { zend_op *opline = zend_compile_static_prop(result, var_ast, BP_VAR_RW, 0, 0); opline->opcode = ast->kind == ZEND_AST_PRE_INC ? ZEND_PRE_INC_STATIC_PROP : ZEND_PRE_DEC_STATIC_PROP; + opline->result_type = IS_TMP_VAR; + result->op_type = IS_TMP_VAR; } else { znode var_node; zend_compile_var(&var_node, var_ast, BP_VAR_RW, 0); - zend_emit_op(result, ast->kind == ZEND_AST_PRE_INC ? ZEND_PRE_INC : ZEND_PRE_DEC, + zend_emit_op_tmp(result, ast->kind == ZEND_AST_PRE_INC ? ZEND_PRE_INC : ZEND_PRE_DEC, &var_node, NULL); } } @@ -7764,20 +7795,26 @@ void zend_compile_assign_coalesce(znode *result, zend_ast *ast) /* {{{ */ opline = &CG(active_op_array)->opcodes[CG(active_op_array)->last-1]; switch (var_ast->kind) { case ZEND_AST_VAR: - zend_emit_op(&assign_node, ZEND_ASSIGN, &var_node_w, &default_node); + zend_emit_op_tmp(&assign_node, ZEND_ASSIGN, &var_node_w, &default_node); break; case ZEND_AST_STATIC_PROP: opline->opcode = ZEND_ASSIGN_STATIC_PROP; + opline->result_type = IS_TMP_VAR; + var_node_w.op_type = IS_TMP_VAR; zend_emit_op_data(&default_node); assign_node = var_node_w; break; case ZEND_AST_DIM: opline->opcode = ZEND_ASSIGN_DIM; + opline->result_type = IS_TMP_VAR; + var_node_w.op_type = IS_TMP_VAR; zend_emit_op_data(&default_node); assign_node = var_node_w; break; case ZEND_AST_PROP: opline->opcode = ZEND_ASSIGN_OBJ; + opline->result_type = IS_TMP_VAR; + var_node_w.op_type = IS_TMP_VAR; zend_emit_op_data(&default_node); assign_node = var_node_w; break; diff --git a/ext/opcache/Optimizer/block_pass.c b/ext/opcache/Optimizer/block_pass.c index bd1946e960b..baf01bbb362 100644 --- a/ext/opcache/Optimizer/block_pass.c +++ b/ext/opcache/Optimizer/block_pass.c @@ -250,13 +250,38 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array case ZEND_FREE: if (opline->op1_type == IS_TMP_VAR) { src = VAR_SOURCE(opline->op1); - if (src && - (src->opcode == ZEND_BOOL || src->opcode == ZEND_BOOL_NOT)) { - /* T = BOOL(X), FREE(T) => T = BOOL(X) */ - /* The remaining BOOL is removed by a separate optimization */ - VAR_SOURCE(opline->op1) = NULL; - MAKE_NOP(opline); - ++(*opt_count); + if (src) { + switch (src->opcode) { + case ZEND_BOOL: + case ZEND_BOOL_NOT: + /* T = BOOL(X), FREE(T) => T = BOOL(X) */ + /* The remaining BOOL is removed by a separate optimization */ + VAR_SOURCE(opline->op1) = NULL; + MAKE_NOP(opline); + ++(*opt_count); + break; + case ZEND_ASSIGN: + case ZEND_ASSIGN_DIM: + case ZEND_ASSIGN_OBJ: + case ZEND_ASSIGN_STATIC_PROP: + case ZEND_ASSIGN_OP: + case ZEND_ASSIGN_DIM_OP: + case ZEND_ASSIGN_OBJ_OP: + case ZEND_ASSIGN_STATIC_PROP_OP: + case ZEND_PRE_INC: + case ZEND_PRE_DEC: + case ZEND_PRE_INC_OBJ: + case ZEND_PRE_DEC_OBJ: + case ZEND_PRE_INC_STATIC_PROP: + case ZEND_PRE_DEC_STATIC_PROP: + src->result_type = IS_UNUSED; + VAR_SOURCE(opline->op1) = NULL; + MAKE_NOP(opline); + ++(*opt_count); + break; + default: + break; + } } } else if (opline->op1_type == IS_VAR) { src = VAR_SOURCE(opline->op1); @@ -1649,7 +1674,7 @@ static void zend_t_usage(zend_cfg *cfg, zend_op_array *op_array, zend_bitset use while (opline >= end) { /* usage checks */ - if (opline->result_type == IS_VAR) { + if (opline->result_type & (IS_VAR|IS_TMP_VAR)) { if (!zend_bitset_in(usage, VAR_NUM(opline->result.var))) { switch (opline->opcode) { case ZEND_ASSIGN_OP: @@ -1666,13 +1691,6 @@ static void zend_t_usage(zend_cfg *cfg, zend_op_array *op_array, zend_bitset use case ZEND_DO_FCALL_BY_NAME: opline->result_type = IS_UNUSED; break; - } - } else { - zend_bitset_excl(usage, VAR_NUM(opline->result.var)); - } - } else if (opline->result_type == IS_TMP_VAR) { - if (!zend_bitset_in(usage, VAR_NUM(opline->result.var))) { - switch (opline->opcode) { case ZEND_POST_INC: case ZEND_POST_DEC: case ZEND_POST_INC_OBJ: diff --git a/ext/opcache/Optimizer/zend_ssa.c b/ext/opcache/Optimizer/zend_ssa.c index 947602eb989..41a5f10372c 100644 --- a/ext/opcache/Optimizer/zend_ssa.c +++ b/ext/opcache/Optimizer/zend_ssa.c @@ -427,7 +427,7 @@ static void place_essa_pis( pi_range_not_equals(pi, -1, 1); } } - } else if (opline->op1_type == IS_VAR && + } else if (opline->op1_type == IS_TMP_VAR && ((opline-1)->opcode == ZEND_PRE_INC || (opline-1)->opcode == ZEND_PRE_DEC) && opline->op1.var == (opline-1)->result.var && diff --git a/ext/pcre/tests/preg_match_all_error3.phpt b/ext/pcre/tests/preg_match_all_error3.phpt index 6559b22d2eb..2a25b472057 100644 --- a/ext/pcre/tests/preg_match_all_error3.phpt +++ b/ext/pcre/tests/preg_match_all_error3.phpt @@ -16,4 +16,9 @@ var_dump(preg_match_all($regex, $subject, 'test')); echo "Done"; ?> --EXPECTF-- -Fatal error: Only variables can be passed by reference in %spreg_match_all_error3.php on line %d +*** Testing preg_match_all() : error conditions *** + +Fatal error: Uncaught Error: Cannot pass parameter 3 by reference in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/ext/standard/tests/array/array_next_error2.phpt b/ext/standard/tests/array/array_next_error2.phpt index d4f10e90e5f..75aa7788591 100644 --- a/ext/standard/tests/array/array_next_error2.phpt +++ b/ext/standard/tests/array/array_next_error2.phpt @@ -8,4 +8,7 @@ function f() { var_dump(next(array(1, 2))); ?> --EXPECTF-- -Fatal error: Only variables can be passed by reference in %s on line %d +Fatal error: Uncaught Error: Cannot pass parameter 1 by reference in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/ext/standard/tests/array/prev_error3.phpt b/ext/standard/tests/array/prev_error3.phpt index 6f0beaf1153..44c7b1e8ad6 100644 --- a/ext/standard/tests/array/prev_error3.phpt +++ b/ext/standard/tests/array/prev_error3.phpt @@ -15,4 +15,7 @@ prev - ensure we cannot pass a temporary var_dump(prev(array(1, 2))); ?> --EXPECTF-- -Fatal error: Only variables can be passed by reference in %s on line %d +Fatal error: Uncaught Error: Cannot pass parameter 1 by reference in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/classes/constants_error_003.phpt b/tests/classes/constants_error_003.phpt index fa7984f06e4..a0ee23506be 100644 --- a/tests/classes/constants_error_003.phpt +++ b/tests/classes/constants_error_003.phpt @@ -16,4 +16,7 @@ Basic class support - attempting to pass a class constant by reference. var_dump(aclass::myConst); ?> --EXPECTF-- -Fatal error: Only variables can be passed by reference in %s on line 12 +Fatal error: Uncaught Error: Cannot pass parameter 1 by reference in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/lang/passByReference_002.phpt b/tests/lang/passByReference_002.phpt index 81868177a82..2c3de8879aa 100644 --- a/tests/lang/passByReference_002.phpt +++ b/tests/lang/passByReference_002.phpt @@ -12,4 +12,7 @@ f(2); ?> --EXPECTF-- -Fatal error: Only variables can be passed by reference in %s on line 8 +Fatal error: Uncaught Error: Cannot pass parameter 1 by reference in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/tests/lang/passByReference_010.phpt b/tests/lang/passByReference_010.phpt index f996073c0f6..9606d4f8429 100644 --- a/tests/lang/passByReference_010.phpt +++ b/tests/lang/passByReference_010.phpt @@ -9,42 +9,52 @@ function f(&$a) { } echo "\n\n---> Pass constant assignment by reference:\n"; -f($a="a.original"); -var_dump($a); +try { + f($a="a.original"); + var_dump($a); +} catch (Throwable $e) { + echo "Exception: " . $e->getMessage() ."\n"; +} echo "\n\n---> Pass variable assignment by reference:\n"; -unset($a); -$a = "a.original"; -f($b = $a); -var_dump($a); +try { + unset($a); + $a = "a.original"; + f($b = $a); + var_dump($a); +} catch (Throwable $e) { + echo "Exception: " . $e->getMessage() ."\n"; +} echo "\n\n---> Pass reference assignment by reference:\n"; -unset($a, $b); -$a = "a.original"; -f($b =& $a); -var_dump($a); +try { + unset($a, $b); + $a = "a.original"; + f($b =& $a); + var_dump($a); +} catch (Throwable $e) { + echo "Exception: " . $e->getMessage() ."\n"; +} echo "\n\n---> Pass concat assignment by reference:\n"; -unset($a, $b); -$b = "b.original"; -$a = "a.original"; -f($b .= $a); -var_dump($a); +try { + unset($a, $b); + $b = "b.original"; + $a = "a.original"; + f($b .= $a); + var_dump($a); +} catch (Throwable $e) { + echo "Exception: " . $e->getMessage() ."\n"; +} ?> ---EXPECTF-- +--EXPECT-- ---> Pass constant assignment by reference: - -Notice: Only variables should be passed by reference in %s on line 9 -string(10) "a.original" -string(10) "a.original" +Exception: Cannot pass parameter 1 by reference ---> Pass variable assignment by reference: - -Notice: Only variables should be passed by reference in %s on line 15 -string(10) "a.original" -string(10) "a.original" +Exception: Cannot pass parameter 1 by reference ---> Pass reference assignment by reference: @@ -53,7 +63,4 @@ string(9) "a.changed" ---> Pass concat assignment by reference: - -Notice: Only variables should be passed by reference in %s on line 28 -string(20) "b.originala.original" -string(10) "a.original" +Exception: Cannot pass parameter 1 by reference