diff --git a/.github/actions/install/action.yml b/.github/actions/install/action.yml
index 0c4069fb..5b656bfa 100644
--- a/.github/actions/install/action.yml
+++ b/.github/actions/install/action.yml
@@ -13,7 +13,7 @@ inputs:
zig-v8:
description: 'zig v8 version to install'
required: false
- default: 'v0.3.4'
+ default: 'v0.3.6'
v8:
description: 'v8 version to install'
required: false
diff --git a/Dockerfile b/Dockerfile
index 62755482..f10d546b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@ FROM debian:stable-slim
ARG MINISIG=0.12
ARG ZIG_MINISIG=RWSGOq2NVecA2UPNdBUZykf1CCb147pkmdtYxgb3Ti+JO/wCYvhbAb/U
ARG V8=14.0.365.4
-ARG ZIG_V8=v0.3.4
+ARG ZIG_V8=v0.3.6
ARG TARGETPLATFORM
RUN apt-get update -yq && \
diff --git a/build.zig.zon b/build.zig.zon
index ad393bde..a51429f1 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -5,8 +5,8 @@
.minimum_zig_version = "0.15.2",
.dependencies = .{
.v8 = .{
- .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.3.4.tar.gz",
- .hash = "v8-0.0.0-xddH6_F3BAAiFvKY6R1H-gkuQlk19BkDQ0--uZuTrSup",
+ .url = "https://github.com/lightpanda-io/zig-v8-fork/archive/refs/tags/v0.3.6.tar.gz",
+ .hash = "v8-0.0.0-xddH6yl-BAALKs8rYDIjqpnDRQAI5sKCFX5-z7WUn7jx",
},
// .v8 = .{ .path = "../zig-v8-fork" },
.brotli = .{
diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig
index 9ecd65b2..28da9f2a 100644
--- a/src/browser/js/Caller.zig
+++ b/src/browser/js/Caller.zig
@@ -505,6 +505,7 @@ pub const Function = struct {
pub const Opts = struct {
noop: bool = false,
static: bool = false,
+ deletable: bool = true,
dom_exception: bool = false,
as_typed_array: bool = false,
null_as_undefined: bool = false,
diff --git a/src/browser/js/Snapshot.zig b/src/browser/js/Snapshot.zig
index 7aa2125c..de4ac308 100644
--- a/src/browser/js/Snapshot.zig
+++ b/src/browser/js/Snapshot.zig
@@ -137,7 +137,7 @@ pub fn create() !Snapshot {
defer v8.v8__HandleScope__DESTRUCT(&handle_scope);
// Create templates (constructors only) FIRST
- var templates: [JsApis.len]*v8.FunctionTemplate = undefined;
+ var templates: [JsApis.len]*const v8.FunctionTemplate = undefined;
inline for (JsApis, 0..) |JsApi, i| {
@setEvalBranchQuota(10_000);
templates[i] = generateConstructor(JsApi, isolate);
@@ -419,7 +419,7 @@ fn collectExternalReferences() [countExternalReferences()]isize {
// via `new ClassName()` - but they could, for example, be created in
// Zig and returned from a function call, which is why we need the
// FunctionTemplate.
-fn generateConstructor(comptime JsApi: type, isolate: *v8.Isolate) *v8.FunctionTemplate {
+fn generateConstructor(comptime JsApi: type, isolate: *v8.Isolate) *const v8.FunctionTemplate {
const callback = blk: {
if (@hasDecl(JsApi, "constructor")) {
break :blk JsApi.constructor.func;
@@ -429,7 +429,7 @@ fn generateConstructor(comptime JsApi: type, isolate: *v8.Isolate) *v8.FunctionT
break :blk illegalConstructorCallback;
};
- const template = @constCast(v8.v8__FunctionTemplate__New__DEFAULT2(isolate, callback).?);
+ const template = v8.v8__FunctionTemplate__New__DEFAULT2(isolate, callback).?;
{
const internal_field_count = comptime countInternalFields(JsApi);
if (internal_field_count > 0) {
@@ -482,10 +482,15 @@ pub fn countInternalFields(comptime JsApi: type) u8 {
}
// Attaches JsApi members to the prototype template (normal case)
-fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.FunctionTemplate) void {
+fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *const v8.FunctionTemplate) void {
const instance = v8.v8__FunctionTemplate__InstanceTemplate(template);
const prototype = v8.v8__FunctionTemplate__PrototypeTemplate(template);
+ // Create a signature that validates the receiver is an instance of this template.
+ // This prevents crashes when JavaScript extracts a getter/method and calls it
+ // with the wrong `this` (e.g., documentGetter.call(null)).
+ const signature = v8.v8__Signature__New(isolate, template);
+
const declarations = @typeInfo(JsApi).@"struct".decls;
var has_named_index_getter = false;
@@ -497,23 +502,47 @@ fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.Functio
switch (definition) {
bridge.Accessor => {
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
- const getter_callback = @constCast(v8.v8__FunctionTemplate__New__Config(isolate, &.{ .callback = value.getter }).?);
+ const getter_signature = if (value.static) null else signature;
+ const getter_callback = v8.v8__FunctionTemplate__New__Config(isolate, &.{
+ .callback = value.getter,
+ .signature = getter_signature,
+ }).?;
+ const setter_callback = if (value.setter) |setter|
+ v8.v8__FunctionTemplate__New__Config(isolate, &.{
+ .callback = setter,
+ .signature = getter_signature,
+ }).?
+ else
+ null;
+
+ var attribute: v8.PropertyAttribute = 0;
if (value.setter == null) {
- if (value.static) {
- v8.v8__Template__SetAccessorProperty__DEFAULT(@ptrCast(template), js_name, getter_callback);
- } else {
- v8.v8__ObjectTemplate__SetAccessorProperty__DEFAULT(prototype, js_name, getter_callback);
- }
+ attribute |= v8.ReadOnly;
+ }
+ if (value.deletable == false) {
+ attribute |= v8.DontDelete;
+ }
+
+ if (value.static) {
+ // Static accessors: use Template's SetAccessorProperty
+ v8.v8__Template__SetAccessorProperty(@ptrCast(template), js_name, getter_callback, setter_callback, attribute);
} else {
- if (comptime IS_DEBUG) {
- std.debug.assert(value.static == false);
- }
- const setter_callback = @constCast(v8.v8__FunctionTemplate__New__Config(isolate, &.{ .callback = value.setter.? }).?);
- v8.v8__ObjectTemplate__SetAccessorProperty__DEFAULT2(prototype, js_name, getter_callback, setter_callback);
+ v8.v8__ObjectTemplate__SetAccessorProperty__Config(prototype, &.{
+ .key = js_name,
+ .getter = getter_callback,
+ .setter = setter_callback,
+ .attribute = attribute,
+ });
}
},
bridge.Function => {
- const function_template = @constCast(v8.v8__FunctionTemplate__New__Config(isolate, &.{ .callback = value.func, .length = value.arity }).?);
+ // For non-static functions, use the signature to validate the receiver
+ const func_signature = if (value.static) null else signature;
+ const function_template = v8.v8__FunctionTemplate__New__Config(isolate, &.{
+ .callback = value.func,
+ .length = value.arity,
+ .signature = func_signature,
+ }).?;
const js_name = v8.v8__String__NewFromUtf8(isolate, name.ptr, v8.kNormal, @intCast(name.len));
if (value.static) {
v8.v8__Template__Set(@ptrCast(template), js_name, @ptrCast(function_template), v8.None);
@@ -551,7 +580,7 @@ fn attachClass(comptime JsApi: type, isolate: *v8.Isolate, template: *v8.Functio
has_named_index_getter = true;
},
bridge.Iterator => {
- const function_template = @constCast(v8.v8__FunctionTemplate__New__Config(isolate, &.{ .callback = value.func }).?);
+ const function_template = v8.v8__FunctionTemplate__New__Config(isolate, &.{ .callback = value.func }).?;
const js_name = if (value.async)
v8.v8__Symbol__GetAsyncIterator(isolate)
else
diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig
index b7da3c13..fb49e01e 100644
--- a/src/browser/js/bridge.zig
+++ b/src/browser/js/bridge.zig
@@ -198,6 +198,7 @@ pub const Function = struct {
pub const Accessor = struct {
static: bool = false,
+ deletable: bool = true,
cache: ?Caller.Function.Opts.Caching = null,
getter: ?*const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void = null,
setter: ?*const fn (?*const v8.FunctionCallbackInfo) callconv(.c) void = null,
@@ -206,6 +207,7 @@ pub const Accessor = struct {
var accessor = Accessor{
.cache = opts.cache,
.static = opts.static,
+ .deletable = opts.deletable,
};
if (@typeInfo(@TypeOf(getter)) != .null) {
diff --git a/src/browser/tests/window/window.html b/src/browser/tests/window/window.html
index 13cd40e8..9623f1ef 100644
--- a/src/browser/tests/window/window.html
+++ b/src/browser/tests/window/window.html
@@ -262,6 +262,31 @@
}
+
+