-
Notifications
You must be signed in to change notification settings - Fork 7.8k
Delayed notice again #12805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Delayed notice again #12805
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
--TEST-- | ||
Bug #78598: Changing array during undef index RW error segfaults | ||
--INI-- | ||
opcache.jit=0 | ||
--FILE-- | ||
<?php | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
--TEST-- | ||
Delayed error 001 | ||
--INI-- | ||
opcache.jit=0 | ||
--FILE-- | ||
<?php | ||
$array[0][1] .= 'foo'; | ||
$array[2][3]++; | ||
$array[3][4]--; | ||
++$array[5][6]; | ||
--$array[7][8]; | ||
$array[9][10] += 42; | ||
?> | ||
--EXPECTF-- | ||
Warning: Undefined variable $array in %s on line %d | ||
|
||
Warning: Undefined array key 0 in %s on line %d | ||
|
||
Warning: Undefined array key 1 in %s on line %d | ||
|
||
Warning: Undefined array key 2 in %s on line %d | ||
|
||
Warning: Undefined array key 3 in %s on line %d | ||
|
||
Warning: Decrement on type null has no effect, this will change in the next major version of PHP in %s on line %d | ||
|
||
Warning: Undefined array key 3 in %s on line %d | ||
|
||
Warning: Undefined array key 4 in %s on line %d | ||
|
||
Warning: Undefined array key 5 in %s on line %d | ||
|
||
Warning: Undefined array key 6 in %s on line %d | ||
|
||
Warning: Decrement on type null has no effect, this will change in the next major version of PHP in %s on line %d | ||
|
||
Warning: Undefined array key 7 in %s on line %d | ||
|
||
Warning: Undefined array key 8 in %s on line %d | ||
|
||
Warning: Undefined array key 9 in %s on line %d | ||
|
||
Warning: Undefined array key 10 in %s on line %d |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -672,6 +672,18 @@ static void zend_init_exception_op(void) /* {{{ */ | |
} | ||
/* }}} */ | ||
|
||
static void zend_init_delayed_error_op(void) /* {{{ */ | ||
{ | ||
memset(EG(delayed_error_op), 0, sizeof(EG(delayed_error_op))); | ||
EG(delayed_error_op)[0].opcode = ZEND_HANDLE_DELAYED_ERROR; | ||
ZEND_VM_SET_OPCODE_HANDLER(EG(delayed_error_op)); | ||
EG(delayed_error_op)[1].opcode = ZEND_HANDLE_DELAYED_ERROR; | ||
ZEND_VM_SET_OPCODE_HANDLER(EG(delayed_error_op)+1); | ||
EG(delayed_error_op)[2].opcode = ZEND_HANDLE_DELAYED_ERROR; | ||
ZEND_VM_SET_OPCODE_HANDLER(EG(delayed_error_op)+2); | ||
} | ||
/* }}} */ | ||
|
||
static void zend_init_call_trampoline_op(void) /* {{{ */ | ||
{ | ||
memset(&EG(call_trampoline_op), 0, sizeof(EG(call_trampoline_op))); | ||
|
@@ -786,6 +798,7 @@ static void executor_globals_ctor(zend_executor_globals *executor_globals) /* {{ | |
zend_copy_constants(executor_globals->zend_constants, GLOBAL_CONSTANTS_TABLE); | ||
zend_init_rsrc_plist(); | ||
zend_init_exception_op(); | ||
zend_init_delayed_error_op(); | ||
zend_init_call_trampoline_op(); | ||
memset(&executor_globals->trampoline, 0, sizeof(zend_op_array)); | ||
executor_globals->capture_warnings_during_sccp = 0; | ||
|
@@ -1025,6 +1038,7 @@ void zend_startup(zend_utility_functions *utility_functions) /* {{{ */ | |
#ifndef ZTS | ||
zend_init_rsrc_plist(); | ||
zend_init_exception_op(); | ||
zend_init_delayed_error_op(); | ||
zend_init_call_trampoline_op(); | ||
#endif | ||
|
||
|
@@ -1709,6 +1723,30 @@ ZEND_API void zend_free_recorded_errors(void) | |
EG(num_errors) = 0; | ||
} | ||
|
||
ZEND_API ZEND_COLD void zend_error_delayed(int type, const char *format, ...) | ||
{ | ||
ZEND_ASSERT(!(type & E_FATAL_ERRORS) && "Cannot delay fatal error"); | ||
zend_error_info *info = emalloc(sizeof(zend_error_info)); | ||
info->type = type; | ||
get_filename_lineno(type, &info->filename, &info->lineno); | ||
zend_string_addref(info->filename); | ||
|
||
va_list args; | ||
va_start(args, format); | ||
info->message = zend_vstrpprintf(0, format, args); | ||
va_end(args); | ||
|
||
zend_hash_next_index_insert_ptr(&EG(delayed_errors), info); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess a single linked list in zend_error_info would be more straightforward than using a hashtable for that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's used as a stack, essentially, and avoids an allocation for each warning. Should I use |
||
|
||
if (EG(current_execute_data)->opline != EG(delayed_error_op)) { | ||
EG(opline_before_exception) = EG(current_execute_data)->opline; | ||
EG(current_execute_data)->opline = EG(delayed_error_op); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do you like to implement this branch in JIT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh... I assumed JIT handled exception the same way as the VM, checking
I will have to look more closely how this works for exceptions to understand what changes are required. I assumed exceptions trigger deoptimization but I guess that's not correct. |
||
/* Reset to ZEND_HANDLE_DELAYED_ERROR */ | ||
EG(delayed_error_op)[0] = EG(delayed_error_op)[2]; | ||
EG(delayed_error_op)[1] = EG(delayed_error_op)[2]; | ||
} | ||
} | ||
|
||
ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const char *format, ...) /* {{{ */ | ||
{ | ||
va_list va; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8022,6 +8022,10 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY) | |
{ | ||
const zend_op *throw_op = EG(opline_before_exception); | ||
|
||
if (zend_hash_num_elements(&EG(delayed_errors))) { | ||
zend_handle_delayed_errors(); | ||
} | ||
|
||
/* Exception was thrown before executing any op */ | ||
if (UNEXPECTED(!throw_op)) { | ||
ZEND_VM_DISPATCH_TO_HELPER(zend_dispatch_try_catch_finally_helper, try_catch_offset, -1, 0, 0); | ||
|
@@ -8091,6 +8095,74 @@ ZEND_VM_HANDLER(149, ZEND_HANDLE_EXCEPTION, ANY, ANY) | |
ZEND_VM_DISPATCH_TO_HELPER(zend_dispatch_try_catch_finally_helper, try_catch_offset, current_try_catch_offset, op_num, throw_op_num); | ||
} | ||
|
||
ZEND_VM_HANDLER(204, ZEND_HANDLE_DELAYED_ERROR, ANY, ANY) | ||
{ | ||
const zend_op *prev_op = EG(opline_before_exception); | ||
bool delay = false; | ||
switch (prev_op->opcode) { | ||
case ZEND_FETCH_W: | ||
case ZEND_FETCH_RW: | ||
case ZEND_FETCH_FUNC_ARG: | ||
case ZEND_FETCH_UNSET: | ||
case ZEND_FETCH_DIM_W: | ||
case ZEND_FETCH_DIM_RW: | ||
case ZEND_FETCH_DIM_UNSET: | ||
case ZEND_FETCH_LIST_W: | ||
case ZEND_FETCH_OBJ_W: | ||
case ZEND_FETCH_OBJ_RW: | ||
case ZEND_FETCH_OBJ_UNSET: | ||
delay = true; | ||
break; | ||
} | ||
|
||
// FIXME: Is this guaranteed to be there? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the locations where a delayed error may be thrown currently, yes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's probably ok to just check, this handler is part of a slow path, and not intended to be particularly fast. |
||
const zend_op *next_op = prev_op + 1; | ||
if (next_op->opcode == ZEND_OP_DATA) { | ||
next_op++; | ||
} | ||
|
||
if (delay) { | ||
zend_op *delayed_op = &EG(delayed_error_op)[0]; | ||
*delayed_op = *next_op; | ||
Comment on lines
+8125
to
+8126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In case you have few There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes. The idea is that each instruction producing indirect values only delays to the next opcode. If that opcode also produces an indirect value, then it wil once again set Note that this happens only if the first FETCH emits a warning. The error is still delayed until the fetch+assign chain.
I didn't test this, but I think one unexpected things could be that we don't associate |
||
if (delayed_op->op1_type == IS_CONST) { | ||
ZVAL_COPY_VALUE(&EG(delayed_error_consts)[0], RT_CONSTANT(next_op, next_op->op1)); | ||
delayed_op->op1.num = (char *)&EG(delayed_error_consts)[0] - (char *)delayed_op; | ||
} | ||
if (delayed_op->op2_type == IS_CONST) { | ||
ZVAL_COPY_VALUE(&EG(delayed_error_consts)[1], RT_CONSTANT(next_op, next_op->op2)); | ||
delayed_op->op2.num = (char *)&EG(delayed_error_consts)[1] - (char *)delayed_op; | ||
} | ||
// FIXME: Is this guaranteed to be there? | ||
if (next_op[1].opcode == ZEND_OP_DATA) { | ||
const zend_op *next_opdata = &next_op[1]; | ||
zend_op *delayed_opdata = &EG(delayed_error_op)[1]; | ||
*delayed_opdata = *next_opdata; | ||
if (delayed_opdata->op1_type == IS_CONST) { | ||
ZVAL_COPY_VALUE(&EG(delayed_error_consts)[2], RT_CONSTANT(next_opdata, next_opdata->op1)); | ||
delayed_opdata->op1.num = (char *)&EG(delayed_error_consts)[2] - (char *)delayed_opdata; | ||
} | ||
} else { | ||
/* Reset to ZEND_HANDLE_DELAYED_ERROR */ | ||
EG(delayed_error_op)[1] = EG(delayed_error_op)[2]; | ||
} | ||
EG(opline_before_exception) = next_op; | ||
|
||
ZEND_VM_SET_NEXT_OPCODE(delayed_op); | ||
} else { | ||
EX(opline) = prev_op; | ||
zend_handle_delayed_errors(); | ||
|
||
if (EG(exception)) { | ||
HANDLE_EXCEPTION(); | ||
} | ||
|
||
EG(opline_before_exception) = NULL; | ||
ZEND_VM_SET_NEXT_OPCODE(next_op); | ||
} | ||
|
||
ZEND_VM_CONTINUE(); | ||
} | ||
|
||
ZEND_VM_HANDLER(150, ZEND_USER_OPCODE, ANY, ANY) | ||
{ | ||
USE_OPLINE | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you disable JIT. Its not supported yet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, JIT doesn't seem to work but I have not looked into where it goes wrong.