Skip to content

Commit df7b5a7

Browse files
author
Elior Nguyen
committed
fix: pass previousRealm to AsyncGeneratorCompleteStep in AsyncGeneratorYield
1 parent 965f38c commit df7b5a7

File tree

4 files changed

+71
-9
lines changed

4 files changed

+71
-9
lines changed

core/engine/src/builtins/generator/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ impl GeneratorContext {
100100
resume_kind: GeneratorResumeKind,
101101
context: &mut Context,
102102
) -> CompletionRecord {
103+
// Capture the caller's realm for AsyncGeneratorYield (spec §27.6.3.8 step 8).
104+
let caller_realm = Some(context.realm().clone());
105+
103106
std::mem::swap(&mut context.vm.stack, &mut self.stack);
104107
let frame = self.call_frame.take().expect("should have a call frame");
105108
let fp = frame.fp;
@@ -109,6 +112,7 @@ impl GeneratorContext {
109112
let frame = context.vm.frame_mut();
110113
frame.fp = fp;
111114
frame.rp = rp;
115+
frame.caller_realm = caller_realm;
112116
frame.set_exit_early(true);
113117

114118
if let Some(value) = value {

core/engine/src/tests/async_generator.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
Context, JsValue, TestAction, builtins::promise::PromiseState, object::JsPromise,
2+
Context, JsValue, Source, TestAction, builtins::promise::PromiseState, object::JsPromise,
33
run_test_actions,
44
};
55
use boa_macros::js_str;
@@ -120,3 +120,49 @@ fn return_on_then_queue() {
120120
TestAction::assert_eq("count", JsValue::from(2)),
121121
]);
122122
}
123+
124+
#[test]
125+
fn cross_realm_async_generator_yield() {
126+
// Exercises AsyncGeneratorYield spec steps 6-8 (previousRealm handling)
127+
// by creating a generator in one realm and consuming it from another.
128+
let mut context = Context::default();
129+
130+
let generator_realm = context.create_realm().unwrap();
131+
132+
let old_realm = context.enter_realm(generator_realm);
133+
let generator = context
134+
.eval(Source::from_bytes(
135+
b"(async function* g() { yield 42; yield 99; })()",
136+
))
137+
.unwrap();
138+
context.enter_realm(old_realm);
139+
140+
let next_fn = generator
141+
.as_object()
142+
.unwrap()
143+
.get(js_str!("next"), &mut context)
144+
.unwrap();
145+
146+
let call_next = |ctx: &mut Context| -> JsValue {
147+
let result = next_fn
148+
.as_callable()
149+
.unwrap()
150+
.call(&generator, &[], ctx)
151+
.unwrap();
152+
ctx.run_jobs().unwrap();
153+
result
154+
};
155+
156+
assert_promise_iter_value(
157+
&call_next(&mut context),
158+
&JsValue::from(42),
159+
false,
160+
&mut context,
161+
);
162+
assert_promise_iter_value(
163+
&call_next(&mut context),
164+
&JsValue::from(99),
165+
false,
166+
&mut context,
167+
);
168+
}

core/engine/src/vm/call_frame/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ pub struct CallFrame {
7777
/// \[\[Realm\]\]
7878
pub(crate) realm: Realm,
7979

80+
/// The caller's realm, captured during generator resume.
81+
/// Used by `AsyncGeneratorYield` to pass `previousRealm` to `AsyncGeneratorCompleteStep`.
82+
pub(crate) caller_realm: Option<Realm>,
83+
8084
// SAFETY: Nothing in `CallFrameFlags` requires tracing, so this is safe.
8185
#[unsafe_ignore_trace]
8286
pub(crate) flags: CallFrameFlags,
@@ -154,6 +158,7 @@ impl CallFrame {
154158
active_runnable,
155159
environments,
156160
realm,
161+
caller_realm: None,
157162
flags: CallFrameFlags::empty(),
158163
}
159164
}

core/engine/src/vm/opcode/generator/yield_stm.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,19 @@ impl AsyncGeneratorYield {
6666
let value = context.vm.get_register(value.into());
6767
let completion = Ok(value.clone());
6868

69-
// TODO: 6. Assert: The execution context stack has at least two elements.
70-
// TODO: 7. Let previousContext be the second to top element of the execution context stack.
71-
// TODO: 8. Let previousRealm be previousContext's Realm.
69+
// 6. Assert: The execution context stack has at least two elements.
70+
// 7. Let previousContext be the second to top element of the execution context stack.
71+
// 8. Let previousRealm be previousContext's Realm.
72+
let previous_realm = context.vm.frame().caller_realm.clone();
73+
7274
// 9. Perform AsyncGeneratorCompleteStep(generator, completion, false, previousRealm).
73-
if let Err(err) =
74-
AsyncGenerator::complete_step(&async_generator_object, completion, false, None, context)
75-
{
75+
if let Err(err) = AsyncGenerator::complete_step(
76+
&async_generator_object,
77+
completion,
78+
false,
79+
previous_realm,
80+
context,
81+
) {
7682
return context.handle_error(err);
7783
}
7884

@@ -114,8 +120,9 @@ impl AsyncGeneratorYield {
114120
// a. Set generator.[[AsyncGeneratorState]] to suspended-yield.
115121
r#gen.data_mut().state = AsyncGeneratorState::SuspendedYield;
116122

117-
// TODO: b. Remove genContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
118-
// TODO: c. Let callerContext be the running execution context.
123+
// b. Remove genContext from the execution context stack and restore the execution context
124+
// that is at the top of the execution context stack as the running execution context.
125+
// c. Let callerContext be the running execution context.
119126
// d. Resume callerContext passing undefined. If genContext is ever resumed again, let resumptionValue be the Completion Record with which it is resumed.
120127
// e. Assert: If control reaches here, then genContext is the running execution context again.
121128
// f. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue).

0 commit comments

Comments
 (0)