diff --git a/NEWS b/NEWS index 030de087fca..926c35de12a 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,8 @@ PHP NEWS . Fixed bug GH-8108 (Timezone doesn't work as intended). (Derick) . Fixed bug #81660 (DateTimeZone::getTransitions() returns invalid data). (Derick) + . Fixed bug GH-8289 (Exceptions thrown within a yielded from iterator are + not rethrown into the generator). (Bob) 31 Mar 2022, PHP 8.1.5 diff --git a/Zend/tests/generators/gh8289.phpt b/Zend/tests/generators/gh8289.phpt new file mode 100644 index 00000000000..6329da896c6 --- /dev/null +++ b/Zend/tests/generators/gh8289.phpt @@ -0,0 +1,34 @@ +--TEST-- +GH-8289 (Exceptions thrown within a yielded from iterator are not rethrown into the generator) +--FILE-- + $v) { + var_dump($v); +} + +?> +--EXPECTF-- +int(1) +Exception in %s:%d +Stack trace: +#0 %s(%d): IteratorIterator@anonymous->key() +#1 %s(%d): yieldFromIteratorGeneratorThrows() +#2 {main} +int(2) \ No newline at end of file diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index 24248d4f7b0..65aede77f78 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -637,22 +637,17 @@ static zend_result zend_generator_get_next_delegated_value(zend_generator *gener if (iter->index++ > 0) { iter->funcs->move_forward(iter); if (UNEXPECTED(EG(exception) != NULL)) { - goto exception; + goto failure; } } if (iter->funcs->valid(iter) == FAILURE) { - if (UNEXPECTED(EG(exception) != NULL)) { - goto exception; - } /* reached end of iteration */ goto failure; } value = iter->funcs->get_current_data(iter); - if (UNEXPECTED(EG(exception) != NULL)) { - goto exception; - } else if (UNEXPECTED(!value)) { + if (UNEXPECTED(EG(exception) != NULL) || UNEXPECTED(!value)) { goto failure; } @@ -664,7 +659,7 @@ static zend_result zend_generator_get_next_delegated_value(zend_generator *gener iter->funcs->get_current_key(iter, &generator->key); if (UNEXPECTED(EG(exception) != NULL)) { ZVAL_UNDEF(&generator->key); - goto exception; + goto failure; } } else { ZVAL_LONG(&generator->key, iter->index); @@ -672,9 +667,6 @@ static zend_result zend_generator_get_next_delegated_value(zend_generator *gener } return SUCCESS; -exception: - zend_generator_throw_exception(generator, NULL); - failure: zval_ptr_dtor(&generator->values); ZVAL_UNDEF(&generator->values); @@ -706,8 +698,33 @@ try_again: /* Drop the AT_FIRST_YIELD flag */ orig_generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD; + /* Backup executor globals */ + zend_execute_data *original_execute_data = EG(current_execute_data); + uint32_t original_jit_trace_num = EG(jit_trace_num); + + /* Set executor globals */ + EG(current_execute_data) = generator->execute_data; + EG(jit_trace_num) = 0; + + /* We want the backtrace to look as if the generator function was + * called from whatever method we are current running (e.g. next()). + * So we have to link generator call frame with caller call frame. */ + if (generator == orig_generator) { + generator->execute_data->prev_execute_data = original_execute_data; + } else { + /* We need some execute_data placeholder in stacktrace to be replaced + * by the real stack trace when needed */ + generator->execute_data->prev_execute_data = &orig_generator->execute_fake; + orig_generator->execute_fake.prev_execute_data = original_execute_data; + } + + /* Ensure this is run after executor_data swap to have a proper stack trace */ if (UNEXPECTED(!Z_ISUNDEF(generator->values))) { if (EXPECTED(zend_generator_get_next_delegated_value(generator) == SUCCESS)) { + /* Restore executor globals */ + EG(current_execute_data) = original_execute_data; + EG(jit_trace_num) = original_jit_trace_num; + orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT; return; } @@ -715,85 +732,63 @@ try_again: * after the "yield from" expression. */ } - { - /* Backup executor globals */ - zend_execute_data *original_execute_data = EG(current_execute_data); - uint32_t original_jit_trace_num = EG(jit_trace_num); + if (UNEXPECTED(generator->frozen_call_stack)) { + /* Restore frozen call-stack */ + zend_generator_restore_call_stack(generator); + } - /* Set executor globals */ - EG(current_execute_data) = generator->execute_data; - EG(jit_trace_num) = 0; + /* Resume execution */ + generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING; + if (!ZEND_OBSERVER_ENABLED) { + zend_execute_ex(generator->execute_data); + } else { + zend_observer_generator_resume(generator->execute_data); + zend_execute_ex(generator->execute_data); + if (generator->execute_data) { + /* On the final return, this will be called from ZEND_GENERATOR_RETURN */ + zend_observer_fcall_end(generator->execute_data, &generator->value); + } + } + generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING; - /* We want the backtrace to look as if the generator function was - * called from whatever method we are current running (e.g. next()). - * So we have to link generator call frame with caller call frame. */ + generator->frozen_call_stack = NULL; + if (EXPECTED(generator->execute_data) && + UNEXPECTED(generator->execute_data->call)) { + /* Frize call-stack */ + generator->frozen_call_stack = zend_generator_freeze_call_stack(generator->execute_data); + } + + /* Restore executor globals */ + EG(current_execute_data) = original_execute_data; + EG(jit_trace_num) = original_jit_trace_num; + + /* If an exception was thrown in the generator we have to internally + * rethrow it in the parent scope. + * In case we did yield from, the Exception must be rethrown into + * its calling frame (see above in if (check_yield_from). */ + if (UNEXPECTED(EG(exception) != NULL)) { if (generator == orig_generator) { - generator->execute_data->prev_execute_data = original_execute_data; - } else { - /* We need some execute_data placeholder in stacktrace to be replaced - * by the real stack trace when needed */ - generator->execute_data->prev_execute_data = &orig_generator->execute_fake; - orig_generator->execute_fake.prev_execute_data = original_execute_data; - } - - if (UNEXPECTED(generator->frozen_call_stack)) { - /* Restore frozen call-stack */ - zend_generator_restore_call_stack(generator); - } - - /* Resume execution */ - generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING; - if (!ZEND_OBSERVER_ENABLED) { - zend_execute_ex(generator->execute_data); - } else { - zend_observer_generator_resume(generator->execute_data); - zend_execute_ex(generator->execute_data); - if (generator->execute_data) { - /* On the final return, this will be called from ZEND_GENERATOR_RETURN */ - zend_observer_fcall_end(generator->execute_data, &generator->value); + zend_generator_close(generator, 0); + if (!EG(current_execute_data)) { + zend_throw_exception_internal(NULL); + } else if (EG(current_execute_data)->func && + ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) { + zend_rethrow_exception(EG(current_execute_data)); } - } - generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING; - - generator->frozen_call_stack = NULL; - if (EXPECTED(generator->execute_data) && - UNEXPECTED(generator->execute_data->call)) { - /* Frize call-stack */ - generator->frozen_call_stack = zend_generator_freeze_call_stack(generator->execute_data); - } - - /* Restore executor globals */ - EG(current_execute_data) = original_execute_data; - EG(jit_trace_num) = original_jit_trace_num; - - /* If an exception was thrown in the generator we have to internally - * rethrow it in the parent scope. - * In case we did yield from, the Exception must be rethrown into - * its calling frame (see above in if (check_yield_from). */ - if (UNEXPECTED(EG(exception) != NULL)) { - if (generator == orig_generator) { - zend_generator_close(generator, 0); - if (!EG(current_execute_data)) { - zend_throw_exception_internal(NULL); - } else if (EG(current_execute_data)->func && - ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) { - zend_rethrow_exception(EG(current_execute_data)); - } - } else { - generator = zend_generator_get_current(orig_generator); - zend_generator_throw_exception(generator, NULL); - orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT; - goto try_again; - } - } - - /* yield from was used, try another resume. */ - if (UNEXPECTED((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && (generator->execute_data->opline - 1)->opcode == ZEND_YIELD_FROM))) { + } else { generator = zend_generator_get_current(orig_generator); + zend_generator_throw_exception(generator, NULL); + orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT; goto try_again; } } + /* yield from was used, try another resume. */ + if (UNEXPECTED((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && (generator->execute_data->opline - 1)->opcode == ZEND_YIELD_FROM))) { + generator = zend_generator_get_current(orig_generator); + goto try_again; + } + orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT; } /* }}} */