Skip to content

Commit 914c1ba

Browse files
committed
Merge branch 'PHP-8.1'
2 parents 083b64e + 16dcede commit 914c1ba

File tree

2 files changed

+108
-79
lines changed

2 files changed

+108
-79
lines changed

Zend/tests/generators/gh8289.phpt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
GH-8289 (Exceptions thrown within a yielded from iterator are not rethrown into the generator)
3+
--FILE--
4+
<?php
5+
6+
function yieldFromIteratorGeneratorThrows() {
7+
try {
8+
yield from new class(new ArrayIterator([1, -2])) extends IteratorIterator {
9+
public function key() {
10+
if ($k = parent::key()) {
11+
throw new Exception;
12+
}
13+
return $k;
14+
}
15+
};
16+
} catch (Exception $e) {
17+
echo "$e\n";
18+
yield 2;
19+
}
20+
}
21+
22+
foreach (yieldFromIteratorGeneratorThrows() as $k => $v) {
23+
var_dump($v);
24+
}
25+
26+
?>
27+
--EXPECTF--
28+
int(1)
29+
Exception in %s:%d
30+
Stack trace:
31+
#0 %s(%d): IteratorIterator@anonymous->key()
32+
#1 %s(%d): yieldFromIteratorGeneratorThrows()
33+
#2 {main}
34+
int(2)

Zend/zend_generators.c

Lines changed: 74 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -655,22 +655,17 @@ static zend_result zend_generator_get_next_delegated_value(zend_generator *gener
655655
if (iter->index++ > 0) {
656656
iter->funcs->move_forward(iter);
657657
if (UNEXPECTED(EG(exception) != NULL)) {
658-
goto exception;
658+
goto failure;
659659
}
660660
}
661661

662662
if (iter->funcs->valid(iter) == FAILURE) {
663-
if (UNEXPECTED(EG(exception) != NULL)) {
664-
goto exception;
665-
}
666663
/* reached end of iteration */
667664
goto failure;
668665
}
669666

670667
value = iter->funcs->get_current_data(iter);
671-
if (UNEXPECTED(EG(exception) != NULL)) {
672-
goto exception;
673-
} else if (UNEXPECTED(!value)) {
668+
if (UNEXPECTED(EG(exception) != NULL) || UNEXPECTED(!value)) {
674669
goto failure;
675670
}
676671

@@ -682,17 +677,14 @@ static zend_result zend_generator_get_next_delegated_value(zend_generator *gener
682677
iter->funcs->get_current_key(iter, &generator->key);
683678
if (UNEXPECTED(EG(exception) != NULL)) {
684679
ZVAL_UNDEF(&generator->key);
685-
goto exception;
680+
goto failure;
686681
}
687682
} else {
688683
ZVAL_LONG(&generator->key, iter->index);
689684
}
690685
}
691686
return SUCCESS;
692687

693-
exception:
694-
zend_generator_throw_exception(generator, NULL);
695-
696688
failure:
697689
zval_ptr_dtor(&generator->values);
698690
ZVAL_UNDEF(&generator->values);
@@ -724,94 +716,97 @@ ZEND_API void zend_generator_resume(zend_generator *orig_generator) /* {{{ */
724716
/* Drop the AT_FIRST_YIELD flag */
725717
orig_generator->flags &= ~ZEND_GENERATOR_AT_FIRST_YIELD;
726718

719+
/* Backup executor globals */
720+
zend_execute_data *original_execute_data = EG(current_execute_data);
721+
uint32_t original_jit_trace_num = EG(jit_trace_num);
722+
723+
/* Set executor globals */
724+
EG(current_execute_data) = generator->execute_data;
725+
EG(jit_trace_num) = 0;
726+
727+
/* We want the backtrace to look as if the generator function was
728+
* called from whatever method we are current running (e.g. next()).
729+
* So we have to link generator call frame with caller call frame. */
730+
if (generator == orig_generator) {
731+
generator->execute_data->prev_execute_data = original_execute_data;
732+
} else {
733+
/* We need some execute_data placeholder in stacktrace to be replaced
734+
* by the real stack trace when needed */
735+
generator->execute_data->prev_execute_data = &orig_generator->execute_fake;
736+
orig_generator->execute_fake.prev_execute_data = original_execute_data;
737+
}
738+
739+
/* Ensure this is run after executor_data swap to have a proper stack trace */
727740
if (UNEXPECTED(!Z_ISUNDEF(generator->values))) {
728741
if (EXPECTED(zend_generator_get_next_delegated_value(generator) == SUCCESS)) {
742+
/* Restore executor globals */
743+
EG(current_execute_data) = original_execute_data;
744+
EG(jit_trace_num) = original_jit_trace_num;
745+
729746
orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT;
730747
return;
731748
}
732749
/* If there are no more delegated values, resume the generator
733750
* after the "yield from" expression. */
734751
}
735752

736-
{
737-
/* Backup executor globals */
738-
zend_execute_data *original_execute_data = EG(current_execute_data);
739-
uint32_t original_jit_trace_num = EG(jit_trace_num);
740-
741-
/* Set executor globals */
742-
EG(current_execute_data) = generator->execute_data;
743-
EG(jit_trace_num) = 0;
744-
745-
/* We want the backtrace to look as if the generator function was
746-
* called from whatever method we are current running (e.g. next()).
747-
* So we have to link generator call frame with caller call frame. */
748-
if (generator == orig_generator) {
749-
generator->execute_data->prev_execute_data = original_execute_data;
750-
} else {
751-
/* We need some execute_data placeholder in stacktrace to be replaced
752-
* by the real stack trace when needed */
753-
generator->execute_data->prev_execute_data = &orig_generator->execute_fake;
754-
orig_generator->execute_fake.prev_execute_data = original_execute_data;
755-
}
753+
if (UNEXPECTED(generator->frozen_call_stack)) {
754+
/* Restore frozen call-stack */
755+
zend_generator_restore_call_stack(generator);
756+
}
756757

757-
if (UNEXPECTED(generator->frozen_call_stack)) {
758-
/* Restore frozen call-stack */
759-
zend_generator_restore_call_stack(generator);
758+
/* Resume execution */
759+
generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
760+
if (!ZEND_OBSERVER_ENABLED) {
761+
zend_execute_ex(generator->execute_data);
762+
} else {
763+
zend_observer_generator_resume(generator->execute_data);
764+
zend_execute_ex(generator->execute_data);
765+
if (generator->execute_data) {
766+
/* On the final return, this will be called from ZEND_GENERATOR_RETURN */
767+
zend_observer_fcall_end(generator->execute_data, &generator->value);
760768
}
769+
}
770+
generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING;
761771

762-
/* Resume execution */
763-
generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING;
764-
if (!ZEND_OBSERVER_ENABLED) {
765-
zend_execute_ex(generator->execute_data);
766-
} else {
767-
zend_observer_generator_resume(generator->execute_data);
768-
zend_execute_ex(generator->execute_data);
769-
if (generator->execute_data) {
770-
/* On the final return, this will be called from ZEND_GENERATOR_RETURN */
771-
zend_observer_fcall_end(generator->execute_data, &generator->value);
772-
}
773-
}
774-
generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING;
772+
generator->frozen_call_stack = NULL;
773+
if (EXPECTED(generator->execute_data) &&
774+
UNEXPECTED(generator->execute_data->call)) {
775+
/* Frize call-stack */
776+
generator->frozen_call_stack = zend_generator_freeze_call_stack(generator->execute_data);
777+
}
775778

776-
generator->frozen_call_stack = NULL;
777-
if (EXPECTED(generator->execute_data) &&
778-
UNEXPECTED(generator->execute_data->call)) {
779-
/* Frize call-stack */
780-
generator->frozen_call_stack = zend_generator_freeze_call_stack(generator->execute_data);
781-
}
779+
/* Restore executor globals */
780+
EG(current_execute_data) = original_execute_data;
781+
EG(jit_trace_num) = original_jit_trace_num;
782782

783-
/* Restore executor globals */
784-
EG(current_execute_data) = original_execute_data;
785-
EG(jit_trace_num) = original_jit_trace_num;
786-
787-
/* If an exception was thrown in the generator we have to internally
788-
* rethrow it in the parent scope.
789-
* In case we did yield from, the Exception must be rethrown into
790-
* its calling frame (see above in if (check_yield_from). */
791-
if (UNEXPECTED(EG(exception) != NULL)) {
792-
if (generator == orig_generator) {
793-
zend_generator_close(generator, 0);
794-
if (!EG(current_execute_data)) {
795-
zend_throw_exception_internal(NULL);
796-
} else if (EG(current_execute_data)->func &&
797-
ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) {
798-
zend_rethrow_exception(EG(current_execute_data));
799-
}
800-
} else {
801-
generator = zend_generator_get_current(orig_generator);
802-
zend_generator_throw_exception(generator, NULL);
803-
orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT;
804-
goto try_again;
783+
/* If an exception was thrown in the generator we have to internally
784+
* rethrow it in the parent scope.
785+
* In case we did yield from, the Exception must be rethrown into
786+
* its calling frame (see above in if (check_yield_from). */
787+
if (UNEXPECTED(EG(exception) != NULL)) {
788+
if (generator == orig_generator) {
789+
zend_generator_close(generator, 0);
790+
if (!EG(current_execute_data)) {
791+
zend_throw_exception_internal(NULL);
792+
} else if (EG(current_execute_data)->func &&
793+
ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) {
794+
zend_rethrow_exception(EG(current_execute_data));
805795
}
806-
}
807-
808-
/* yield from was used, try another resume. */
809-
if (UNEXPECTED((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && (generator->execute_data->opline - 1)->opcode == ZEND_YIELD_FROM))) {
796+
} else {
810797
generator = zend_generator_get_current(orig_generator);
798+
zend_generator_throw_exception(generator, NULL);
799+
orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT;
811800
goto try_again;
812801
}
813802
}
814803

804+
/* yield from was used, try another resume. */
805+
if (UNEXPECTED((generator != orig_generator && !Z_ISUNDEF(generator->retval)) || (generator->execute_data && (generator->execute_data->opline - 1)->opcode == ZEND_YIELD_FROM))) {
806+
generator = zend_generator_get_current(orig_generator);
807+
goto try_again;
808+
}
809+
815810
orig_generator->flags &= ~ZEND_GENERATOR_DO_INIT;
816811
}
817812
/* }}} */

0 commit comments

Comments
 (0)