Don't make argument nullable based on AST null initializer

Closes GH-4720.
This commit is contained in:
Nikita Popov 2019-09-18 15:43:32 +02:00
parent d6ef63db53
commit cdd4e591a4
9 changed files with 56 additions and 74 deletions

View File

@ -76,6 +76,16 @@ PHP 8.0 UPGRADE NOTES
. The precedence of the concatenation operator has changed relative to
bitshifts and addition as well as subtraction.
RFC: https://wiki.php.net/rfc/concatenation_precedence
. Arguments with a default-value that resolves to null at run-time will no
longer implicitly mark the argument type as nullable. Either use an explicit
nullable type, or an explicit null default value instead.
// Replace
function test(int $arg = CONST_RESOLVING_TO_NULL) {}
// With
function test(?int $arg = CONST_RESOLVING_TO_NULL) {}
// Or
function test(int $arg = null) {}
- COM:
. Removed the ability to import case-insensitive constants from type

View File

@ -9,7 +9,7 @@ function a(array $a = FOO) {
var_dump($a);
}
function b(array $b = BAR) {
function b(?array $b = BAR) {
var_dump($b);
}

View File

@ -39,6 +39,10 @@ function int_val_default_null(int $a = NULL_VAL) {
return $a;
}
function nullable_int_val_default_null(?int $a = NULL_VAL) {
return $a;
}
echo "Testing int val" . PHP_EOL;
var_dump(int_val());
@ -58,13 +62,27 @@ echo "Testing string add val" . PHP_EOL;
var_dump(string_add_val());
echo "Testing int with default null constant" . PHP_EOL;
var_dump(int_val_default_null());
try {
var_dump(int_val_default_null());
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
echo "Testing int with null null constant" . PHP_EOL;
var_dump(int_val_default_null(null));
try {
var_dump(int_val_default_null(null));
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
echo "Testing nullable int with default null constant" . PHP_EOL;
var_dump(nullable_int_val_default_null());
echo "Testing nullable int with null null constant" . PHP_EOL;
var_dump(nullable_int_val_default_null(null));
?>
--EXPECT--
--EXPECTF--
Testing int val
int(10)
Testing float val
@ -78,6 +96,10 @@ float(10.7)
Testing string add val
string(14) "this is a test"
Testing int with default null constant
NULL
Argument 1 passed to int_val_default_null() must be of the type int, null given, called in %s on line %d
Testing int with null null constant
Argument 1 passed to int_val_default_null() must be of the type int, null given, called in %s on line %d
Testing nullable int with default null constant
NULL
Testing nullable int with null null constant
NULL

View File

@ -793,23 +793,6 @@ static ZEND_COLD void zend_verify_arg_error(
}
}
static int is_null_constant(zend_class_entry *scope, zval *default_value)
{
if (Z_TYPE_P(default_value) == IS_CONSTANT_AST) {
zval constant;
ZVAL_COPY(&constant, default_value);
if (UNEXPECTED(zval_update_constant_ex(&constant, scope) != SUCCESS)) {
return 0;
}
if (Z_TYPE(constant) == IS_NULL) {
return 1;
}
zval_ptr_dtor_nogc(&constant);
}
return 0;
}
static zend_bool zend_verify_weak_scalar_type_hint(zend_uchar type_hint, zval *arg)
{
switch (type_hint) {
@ -1043,7 +1026,7 @@ static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *inf
static zend_always_inline zend_bool zend_check_type(
zend_type type,
zval *arg, zend_class_entry **ce, void **cache_slot,
zval *default_value, zend_class_entry *scope,
zend_class_entry *scope,
zend_bool is_return_type, zend_bool is_internal)
{
zend_reference *ref = NULL;
@ -1063,19 +1046,19 @@ static zend_always_inline zend_bool zend_check_type(
} else {
*ce = zend_fetch_class(ZEND_TYPE_NAME(type), (ZEND_FETCH_CLASS_AUTO | ZEND_FETCH_CLASS_NO_AUTOLOAD));
if (UNEXPECTED(!*ce)) {
return Z_TYPE_P(arg) == IS_NULL && (ZEND_TYPE_ALLOW_NULL(type) || (default_value && is_null_constant(scope, default_value)));
return Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(type);
}
*cache_slot = (void *) *ce;
}
if (EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
return instanceof_function(Z_OBJCE_P(arg), *ce);
}
return Z_TYPE_P(arg) == IS_NULL && (ZEND_TYPE_ALLOW_NULL(type) || (default_value && is_null_constant(scope, default_value)));
return Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(type);
} else if (EXPECTED(ZEND_TYPE_CODE(type) == Z_TYPE_P(arg))) {
return 1;
}
if (Z_TYPE_P(arg) == IS_NULL && (ZEND_TYPE_ALLOW_NULL(type) || (default_value && is_null_constant(scope, default_value)))) {
if (Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(type)) {
/* Null passed to nullable type */
return 1;
}
@ -1104,7 +1087,7 @@ static zend_always_inline zend_bool zend_check_type(
* because this case is already checked at compile-time. */
}
static zend_always_inline int zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, zval *default_value, void **cache_slot)
static zend_always_inline int zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot)
{
zend_arg_info *cur_arg_info = &zf->common.arg_info[arg_num-1];
zend_class_entry *ce;
@ -1113,7 +1096,7 @@ static zend_always_inline int zend_verify_recv_arg_type(zend_function *zf, uint3
cur_arg_info = &zf->common.arg_info[arg_num-1];
ce = NULL;
if (UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, &ce, cache_slot, default_value, zf->common.scope, 0, 0))) {
if (UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, &ce, cache_slot, zf->common.scope, 0, 0))) {
zend_verify_arg_error(zf, cur_arg_info, arg_num, ce, arg);
return 0;
}
@ -1121,7 +1104,7 @@ static zend_always_inline int zend_verify_recv_arg_type(zend_function *zf, uint3
return 1;
}
static zend_always_inline int zend_verify_variadic_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, zval *default_value, void **cache_slot)
static zend_always_inline int zend_verify_variadic_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot)
{
zend_arg_info *cur_arg_info;
zend_class_entry *ce;
@ -1131,7 +1114,7 @@ static zend_always_inline int zend_verify_variadic_arg_type(zend_function *zf, u
cur_arg_info = &zf->common.arg_info[zf->common.num_args];
ce = NULL;
if (UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, &ce, cache_slot, default_value, zf->common.scope, 0, 0))) {
if (UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, &ce, cache_slot, zf->common.scope, 0, 0))) {
zend_verify_arg_error(zf, cur_arg_info, arg_num, ce, arg);
return 0;
}
@ -1158,7 +1141,7 @@ static zend_never_inline ZEND_ATTRIBUTE_UNUSED int zend_verify_internal_arg_type
break;
}
if (UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, &ce, &dummy_cache_slot, NULL, fbc->common.scope, 0, /* is_internal */ 1))) {
if (UNEXPECTED(!zend_check_type(cur_arg_info->type, arg, &ce, &dummy_cache_slot, fbc->common.scope, 0, /* is_internal */ 1))) {
return 0;
}
arg++;
@ -1254,7 +1237,7 @@ static int zend_verify_internal_return_type(zend_function *zf, zval *ret)
return 1;
}
if (UNEXPECTED(!zend_check_type(ret_info->type, ret, &ce, &dummy_cache_slot, NULL, NULL, 1, /* is_internal */ 1))) {
if (UNEXPECTED(!zend_check_type(ret_info->type, ret, &ce, &dummy_cache_slot, NULL, 1, /* is_internal */ 1))) {
zend_verify_internal_return_error(zf, ce, ret);
return 0;
}
@ -1268,7 +1251,7 @@ static zend_always_inline void zend_verify_return_type(zend_function *zf, zval *
zend_arg_info *ret_info = zf->common.arg_info - 1;
zend_class_entry *ce = NULL;
if (UNEXPECTED(!zend_check_type(ret_info->type, ret, &ce, cache_slot, NULL, NULL, 1, 0))) {
if (UNEXPECTED(!zend_check_type(ret_info->type, ret, &ce, cache_slot, NULL, 1, 0))) {
zend_verify_return_error(zf, ce, ret);
}
}

View File

@ -5198,7 +5198,7 @@ ZEND_VM_HOT_HANDLER(63, ZEND_RECV, NUM, UNUSED|CACHE_SLOT)
zval *param = EX_VAR(opline->result.var);
SAVE_OPLINE();
if (UNEXPECTED(!zend_verify_recv_arg_type(EX(func), arg_num, param, NULL, CACHE_ADDR(opline->op2.num)) || EG(exception))) {
if (UNEXPECTED(!zend_verify_recv_arg_type(EX(func), arg_num, param, CACHE_ADDR(opline->op2.num)) || EG(exception))) {
HANDLE_EXCEPTION();
}
}
@ -5243,10 +5243,8 @@ ZEND_VM_HOT_HANDLER(64, ZEND_RECV_INIT, NUM, CONST, CACHE_SLOT)
}
if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) != 0)) {
zval *default_value = RT_CONSTANT(opline, opline->op2);
SAVE_OPLINE();
if (UNEXPECTED(!zend_verify_recv_arg_type(EX(func), arg_num, param, default_value, CACHE_ADDR(opline->extended_value)) || EG(exception))) {
if (UNEXPECTED(!zend_verify_recv_arg_type(EX(func), arg_num, param, CACHE_ADDR(opline->extended_value)) || EG(exception))) {
HANDLE_EXCEPTION();
}
}
@ -5275,7 +5273,7 @@ ZEND_VM_HANDLER(164, ZEND_RECV_VARIADIC, NUM, UNUSED|CACHE_SLOT)
param = EX_VAR_NUM(EX(func)->op_array.last_var + EX(func)->op_array.T);
if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) != 0)) {
do {
zend_verify_variadic_arg_type(EX(func), arg_num, param, NULL, CACHE_ADDR(opline->op2.num));
zend_verify_variadic_arg_type(EX(func), arg_num, param, CACHE_ADDR(opline->op2.num));
if (Z_OPT_REFCOUNTED_P(param)) Z_ADDREF_P(param);
ZEND_HASH_FILL_ADD(param);
param++;

View File

@ -3046,10 +3046,8 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RECV_INIT_SPEC_CON
}
if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) != 0)) {
zval *default_value = RT_CONSTANT(opline, opline->op2);
SAVE_OPLINE();
if (UNEXPECTED(!zend_verify_recv_arg_type(EX(func), arg_num, param, default_value, CACHE_ADDR(opline->extended_value)) || EG(exception))) {
if (UNEXPECTED(!zend_verify_recv_arg_type(EX(func), arg_num, param, CACHE_ADDR(opline->extended_value)) || EG(exception))) {
HANDLE_EXCEPTION();
}
}
@ -3127,7 +3125,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RECV_SPEC_UNUSED_H
zval *param = EX_VAR(opline->result.var);
SAVE_OPLINE();
if (UNEXPECTED(!zend_verify_recv_arg_type(EX(func), arg_num, param, NULL, CACHE_ADDR(opline->op2.num)) || EG(exception))) {
if (UNEXPECTED(!zend_verify_recv_arg_type(EX(func), arg_num, param, CACHE_ADDR(opline->op2.num)) || EG(exception))) {
HANDLE_EXCEPTION();
}
}
@ -3155,7 +3153,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_RECV_VARIADIC_SPEC_UNUSED_HAND
param = EX_VAR_NUM(EX(func)->op_array.last_var + EX(func)->op_array.T);
if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_HAS_TYPE_HINTS) != 0)) {
do {
zend_verify_variadic_arg_type(EX(func), arg_num, param, NULL, CACHE_ADDR(opline->op2.num));
zend_verify_variadic_arg_type(EX(func), arg_num, param, CACHE_ADDR(opline->op2.num));
if (Z_OPT_REFCOUNTED_P(param)) Z_ADDREF_P(param);
ZEND_HASH_FILL_ADD(param);
param++;

View File

@ -3104,11 +3104,6 @@ static int zend_update_type_info(const zend_op_array *op_array,
ce = NULL;
if (arg_info) {
tmp = zend_fetch_arg_info_type(script, arg_info, &ce);
if (opline->opcode == ZEND_RECV_INIT &&
Z_TYPE_P(CRT_CONSTANT_EX(op_array, opline, opline->op2, ssa->rt_constants)) == IS_CONSTANT_AST) {
/* The constant may resolve to NULL */
tmp |= MAY_BE_NULL;
}
if (arg_info->pass_by_reference) {
tmp |= MAY_BE_REF;
}

View File

@ -1115,23 +1115,6 @@ static zval* ZEND_FASTCALL zend_jit_fetch_global_helper(zend_execute_data *execu
return value;
}
static int is_null_constant(zend_class_entry *scope, zval *default_value)
{
if (Z_CONSTANT_P(default_value)) {
zval constant;
ZVAL_COPY(&constant, default_value);
if (UNEXPECTED(zval_update_constant_ex(&constant, scope) != SUCCESS)) {
return 0;
}
if (Z_TYPE(constant) == IS_NULL) {
return 1;
}
zval_ptr_dtor(&constant);
}
return 0;
}
static zend_bool zend_verify_weak_scalar_type_hint(zend_uchar type_hint, zval *arg)
{
switch (type_hint) {
@ -1313,12 +1296,11 @@ static void ZEND_FASTCALL zend_jit_verify_arg_object(zval *arg, zend_op_array *o
}
}
static void ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_op_array *op_array, uint32_t arg_num, zend_arg_info *arg_info, void **cache_slot, zval *default_value)
static void ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_op_array *op_array, uint32_t arg_num, zend_arg_info *arg_info, void **cache_slot)
{
zend_class_entry *ce = NULL;
if (Z_TYPE_P(arg) == IS_NULL &&
(ZEND_TYPE_ALLOW_NULL(arg_info->type) || (default_value && is_null_constant(op_array->scope, default_value)))) {
if (Z_TYPE_P(arg) == IS_NULL && ZEND_TYPE_ALLOW_NULL(arg_info->type)) {
/* Null passed to nullable type */
return;
}

View File

@ -9021,16 +9021,13 @@ static int zend_jit_recv(dasm_State **Dst, const zend_op *opline, zend_op_array
| mov CARG3, arg_num
| LOAD_ADDR CARG4, (ptrdiff_t)arg_info
| mov aword A5, r0
| mov aword A6, 0
| EXT_CALL zend_jit_verify_arg_slow, r0
|.elif X64
| mov CARG3, arg_num
| LOAD_ADDR CARG4, (ptrdiff_t)arg_info
| mov CARG5, r0
| xor CARG6, CARG6
| EXT_CALL zend_jit_verify_arg_slow, r0
|.else
| push 0
| push r0
| push (ptrdiff_t)arg_info
| push arg_num
@ -9190,16 +9187,13 @@ static int zend_jit_recv_init(dasm_State **Dst, const zend_op *opline, zend_op_a
| mov CARG3, arg_num
| LOAD_ADDR CARG4, (ptrdiff_t)arg_info
| mov aword A5, r0
| ADDR_OP2_2 mov, aword A6, zv, r0
| EXT_CALL zend_jit_verify_arg_slow, r0
|.elif X64
| mov CARG3, arg_num
| LOAD_ADDR CARG4, (ptrdiff_t)arg_info
| mov CARG5, r0
| LOAD_ADDR CARG6, zv
| EXT_CALL zend_jit_verify_arg_slow, r0
|.else
| push zv
| push r0
| push (ptrdiff_t)arg_info
| push arg_num