Skip to content

Commit 6bf2ff9

Browse files
committed
Protect against context changing during module resolution.
1 parent 252fd78 commit 6bf2ff9

File tree

2 files changed

+41
-11
lines changed

2 files changed

+41
-11
lines changed

src/runtime/js.zig

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
177177

178178
meta_lookup: [Types.len]TypeMeta,
179179

180+
context_id: usize,
181+
180182
const Self = @This();
181183

182184
const TYPE_LOOKUP = TypeLookup{};
@@ -222,6 +224,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
222224
errdefer allocator.destroy(env);
223225

224226
env.* = .{
227+
.context_id = 0,
225228
.platform = platform,
226229
.isolate = isolate,
227230
.templates = undefined,
@@ -528,9 +531,12 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
528531
return error.ConsoleDeleteError;
529532
}
530533
}
534+
const context_id = env.context_id;
535+
env.context_id = context_id + 1;
531536

532537
self.js_context = JsContext{
533538
.state = state,
539+
.id = context_id,
534540
.isolate = isolate,
535541
.v8_context = v8_context,
536542
.templates = &env.templates,
@@ -634,6 +640,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
634640

635641
// Loosely maps to a Browser Page.
636642
pub const JsContext = struct {
643+
id: usize,
637644
state: State,
638645
isolate: v8.Isolate,
639646
// This context is a persistent object. The persistent needs to be recovered and reset.
@@ -1700,6 +1707,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
17001707
// Will get passed to ScriptManager and then passed back to us when
17011708
// the src of the module is loaded
17021709
const DynamicModuleResolveState = struct {
1710+
// this is what we're trying to get from the module and resolve
1711+
// on our promise
1712+
namespace: ?v8.Value,
1713+
context_id: usize,
17031714
js_context: *JsContext,
17041715
specifier: [:0]const u8,
17051716
resolver: v8.PromiseResolver,
@@ -1725,6 +1736,8 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
17251736
.js_context = self,
17261737
.resolver = resolver,
17271738
.specifier = specifier,
1739+
.context_id = self.id,
1740+
.namespace = null,
17281741
};
17291742

17301743
const promise = resolver.getPromise();
@@ -1817,10 +1830,10 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
18171830

18181831
// we can only be here if the module has been evaluated and if
18191832
// we have a resolve loading this asynchronously.
1820-
std.debug.assert(module_entry.module != null);
18211833
std.debug.assert(module_entry.module_promise != null);
18221834
std.debug.assert(module_entry.resolver_promise != null);
18231835
std.debug.assert(self.module_cache.contains(state.specifier));
1836+
state.namespace = module_entry.module.?.castToModule().getModuleNamespace();
18241837

18251838
// We've gotten the source for the module and are evaluating it.
18261839
// You might thing we're done, but the module evaluation is
@@ -1831,23 +1844,36 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
18311844

18321845
const then_callback = v8.Function.initWithData(ctx, struct {
18331846
pub fn callback(raw_info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void {
1834-
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
1835-
const isolate = info.getIsolate();
1847+
var info = v8.FunctionCallbackInfo.initFromV8(raw_info);
1848+
var caller = Caller(JsContext, State).init(info);
1849+
defer caller.deinit();
18361850

18371851
const s: *DynamicModuleResolveState = @ptrCast(@alignCast(info.getExternalValue()));
1838-
const js_context = s.js_context;
1839-
const me = js_context.module_cache.getPtr(s.specifier).?;
1840-
const namespace = me.module.?.castToModule().getModuleNamespace();
1841-
_ = s.resolver.resolve(isolate.getCurrentContext(), namespace);
1852+
1853+
if (s.context_id != caller.js_context.id) {
1854+
// The microtask is tied to the isolate, not the context
1855+
// it can be resolved while another context is active
1856+
// (Which seems crazy to me). If that happens, then
1857+
// another page was loaded and we MUST ignore thise
1858+
// (most of the fields in state are not valid)
1859+
return;
1860+
}
1861+
1862+
_ = s.resolver.resolve(caller.js_context.v8_context, s.namespace.?);
18421863
}
18431864
}.callback, external);
18441865

18451866
const catch_callback = v8.Function.initWithData(ctx, struct {
18461867
pub fn callback(raw_info: ?*const v8.c.FunctionCallbackInfo) callconv(.c) void {
1847-
const info = v8.FunctionCallbackInfo.initFromV8(raw_info);
1848-
const isolate = info.getIsolate();
1849-
const data: *DynamicModuleResolveState = @ptrCast(@alignCast(info.getExternalValue()));
1850-
_ = data.resolver.reject(isolate.getCurrentContext(), info.getData());
1868+
var info = v8.FunctionCallbackInfo.initFromV8(raw_info);
1869+
var caller = Caller(JsContext, State).init(info);
1870+
defer caller.deinit();
1871+
1872+
const s: *DynamicModuleResolveState = @ptrCast(@alignCast(info.getExternalValue()));
1873+
if (s.context_id != caller.js_context.id) {
1874+
return;
1875+
}
1876+
_ = s.resolver.reject(caller.js_context.v8_context, info.getData());
18511877
}
18521878
}.callback, external);
18531879

src/testing.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,10 @@ pub fn htmlRunner(file: []const u8) !void {
403403
const url = try std.fmt.allocPrint(arena_allocator, "http://localhost:9582/src/tests/{s}", .{file});
404404
try page.navigate(url, .{});
405405
_ = page.wait(2000);
406+
// page exits more aggressively in tests. We want to make sure this is called
407+
// at lease once.
408+
page.session.browser.runMicrotasks();
409+
page.session.browser.runMessageLoop();
406410

407411
@import("root").js_runner_duration += std.time.Instant.since(try std.time.Instant.now(), start);
408412

0 commit comments

Comments
 (0)