diff options
Diffstat (limited to 'src/javascript')
-rw-r--r-- | src/javascript/jsc/bindings/exports.zig | 185 | ||||
-rw-r--r-- | src/javascript/jsc/javascript.zig | 169 | ||||
-rw-r--r-- | src/javascript/jsc/webcore/response.zig | 19 |
3 files changed, 255 insertions, 118 deletions
diff --git a/src/javascript/jsc/bindings/exports.zig b/src/javascript/jsc/bindings/exports.zig index 6a8c6328b..c6a6c2b88 100644 --- a/src/javascript/jsc/bindings/exports.zig +++ b/src/javascript/jsc/bindings/exports.zig @@ -5,6 +5,7 @@ const CAPI = @import("../JavaScriptCore.zig"); const JS = @import("../javascript.zig"); const JSBase = @import("../base.zig"); const ZigURL = @import("../../../query_string_map.zig").URL; +const Api = @import("../../../api/schema.zig").Api; const Handler = struct { pub export fn global_signal_handler_fn(sig: i32, info: *const std.os.siginfo_t, ctx_ptr: ?*const c_void) callconv(.C) void { var stdout = std.io.getStdOut(); @@ -169,40 +170,6 @@ pub const ZigErrorType = extern struct { } }; -pub const JSErrorCode = enum(u8) { - Error = 0, - EvalError = 1, - RangeError = 2, - ReferenceError = 3, - SyntaxError = 4, - TypeError = 5, - URIError = 6, - AggregateError = 7, - - // StackOverflow & OutOfMemoryError is not an ErrorType in <JavaScriptCore/ErrorType.h> within JSC, so the number here is just totally made up - OutOfMemoryError = 8, - BundlerError = 252, - StackOverflow = 253, - UserErrorCode = 254, - _, -}; - -pub const JSRuntimeType = enum(u16) { - Nothing = 0x0, - Function = 0x1, - Undefined = 0x2, - Null = 0x4, - Boolean = 0x8, - AnyInt = 0x10, - Number = 0x20, - String = 0x40, - Object = 0x80, - Symbol = 0x100, - BigInt = 0x200, - - _, -}; - pub fn Errorable(comptime Type: type) type { return extern struct { result: Result, @@ -251,6 +218,40 @@ pub const ResolvedSource = extern struct { bytecodecache_fd: u64, }; +pub const JSErrorCode = enum(u8) { + Error = 0, + EvalError = 1, + RangeError = 2, + ReferenceError = 3, + SyntaxError = 4, + TypeError = 5, + URIError = 6, + AggregateError = 7, + + // StackOverflow & OutOfMemoryError is not an ErrorType in <JavaScriptCore/ErrorType.h> within JSC, so the number here is just totally made up + OutOfMemoryError = 8, + BundlerError = 252, + StackOverflow = 253, + UserErrorCode = 254, + _, +}; + +pub const JSRuntimeType = enum(u16) { + Nothing = 0x0, + Function = 0x1, + Undefined = 0x2, + Null = 0x4, + Boolean = 0x8, + AnyInt = 0x10, + Number = 0x20, + String = 0x40, + Object = 0x80, + Symbol = 0x100, + BigInt = 0x200, + + _, +}; + pub const ZigStackFrameCode = enum(u8) { None = 0, // 🏃 @@ -301,6 +302,49 @@ pub const ZigStackTrace = extern struct { frames_ptr: [*c]ZigStackFrame, frames_len: u8, + pub fn toAPI(this: *const ZigStackTrace, allocator: *std.mem.Allocator) !Api.StackTrace { + var stack_trace: Api.StackTrace = std.mem.zeroes(Api.StackTrace); + { + var source_lines_iter = this.sourceLineIterator(); + + var source_line_len: usize = 0; + var count: usize = 0; + while (source_lines_iter.next()) |source| { + count += 1; + source_line_len += source.text.len; + } + + if (count > 0 and source_line_len > 0) { + var source_lines = try allocator.alloc(Api.SourceLine, count); + var source_line_buf = try allocator.alloc(u8, source_line_len); + source_lines_iter = this.sourceLineIterator(); + var remain_buf = source_line_buf[0..]; + var i: usize = 0; + while (source_lines_iter.next()) |source| { + std.mem.copy(u8, remain_buf, source.text); + const copied_line = remain_buf[0..source.text.len]; + remain_buf = remain_buf[source.text.len..]; + source_lines[i] = .{ .text = copied_line, .line = source.line }; + i += 1; + } + stack_trace.source_lines = source_lines; + } + } + { + var _frames = this.frames(); + if (_frames.len > 0) { + var stack_frames = try allocator.alloc(Api.StackFrame, _frames.len); + stack_trace.frames = stack_frames; + + for (_frames) |frame, i| { + stack_frames[i] = try frame.toAPI(allocator); + } + } + } + + return stack_trace; + } + pub fn frames(this: *const ZigStackTrace) []const ZigStackFrame { return this.frames_ptr[0..this.frames_len]; } @@ -344,6 +388,34 @@ pub const ZigStackTrace = extern struct { }; pub const ZigStackFrame = extern struct { + function_name: ZigString, + source_url: ZigString, + position: ZigStackFramePosition, + code_type: ZigStackFrameCode, + + pub fn toAPI(this: *const ZigStackFrame, allocator: *std.mem.Allocator) !Api.StackFrame { + var frame: Api.StackFrame = std.mem.zeroes(Api.StackFrame); + if (this.function_name.len > 0) { + frame.function_name = try allocator.dupe(u8, this.function_name.slice()); + } + + if (this.source_url.len > 0) { + frame.file = try allocator.dupe(u8, this.source_url.slice()); + } + + frame.position.source_offset = this.position.source_offset; + frame.position.line = this.position.line; + frame.position.line_start = this.position.line_start; + frame.position.line_stop = this.position.line_stop; + frame.position.column_start = this.position.column_start; + frame.position.column_stop = this.position.column_stop; + frame.position.expression_start = this.position.expression_start; + frame.position.expression_stop = this.position.expression_stop; + frame.scope = @intToEnum(Api.StackFrameScope, @enumToInt(this.code_type)); + + return frame; + } + pub const SourceURLFormatter = struct { source_url: ZigString, position: ZigStackFramePosition, @@ -415,11 +487,6 @@ pub const ZigStackFrame = extern struct { .position = ZigStackFramePosition.Invalid, }; - function_name: ZigString, - source_url: ZigString, - position: ZigStackFramePosition, - code_type: ZigStackFrameCode, - pub fn nameFormatter(this: *const ZigStackFrame, comptime enable_color: bool) NameFormatter { return NameFormatter{ .function_name = this.function_name, .code_type = this.code_type, .enable_color = enable_color }; } @@ -455,10 +522,6 @@ pub const ZigStackFramePosition = extern struct { }; pub const ZigException = extern struct { - pub const shim = Shimmer("Zig", "Exception", @This()); - pub const name = "ZigException"; - pub const namespace = shim.namespace; - code: JSErrorCode, runtime_type: JSRuntimeType, name: ZigString, @@ -467,6 +530,10 @@ pub const ZigException = extern struct { exception: ?*c_void, + pub const shim = Shimmer("Zig", "Exception", @This()); + pub const name = "ZigException"; + pub const namespace = shim.namespace; + pub const Holder = extern struct { const frame_count = 24; const source_lines_count = 6; @@ -529,6 +596,36 @@ pub const ZigException = extern struct { return shim.cppFn("fromException", .{exception}); } + pub fn addToErrorList(this: *ZigException, error_list: *std.ArrayList(Api.JsException)) !void { + const _name: string = @field(this, "name").slice(); + const message: string = @field(this, "message").slice(); + + var is_empty = true; + var api_exception = Api.JsException{ + .runtime_type = @enumToInt(this.runtime_type), + .code = @enumToInt(this.code), + }; + + if (_name.len > 0) { + api_exception.name = try error_list.allocator.dupe(u8, _name); + is_empty = false; + } + + if (message.len > 0) { + api_exception.message = try error_list.allocator.dupe(u8, message); + is_empty = false; + } + + if (this.stack.frames_len > 0) { + api_exception.stack = try this.stack.toAPI(error_list.allocator); + is_empty = false; + } + + if (!is_empty) { + try error_list.append(api_exception); + } + } + pub const Extern = [_][]const u8{"fromException"}; }; @@ -656,7 +753,7 @@ pub const ZigConsoleClient = struct { switch (@intToEnum(CellType, value.asCell().getType())) { CellType.ErrorInstanceType => { - JS.VirtualMachine.printErrorlikeObject(JS.VirtualMachine.vm, value, null, enable_ansi_colors); + JS.VirtualMachine.printErrorlikeObject(JS.VirtualMachine.vm, value, null, null, enable_ansi_colors); return; }, diff --git a/src/javascript/jsc/javascript.zig b/src/javascript/jsc/javascript.zig index fa942e4a2..8bace87e9 100644 --- a/src/javascript/jsc/javascript.zig +++ b/src/javascript/jsc/javascript.zig @@ -761,8 +761,10 @@ pub const VirtualMachine = struct { return slice; } + + // This double prints pub fn promiseRejectionTracker(global: *JSGlobalObject, promise: *JSPromise, rejection: JSPromiseRejectionOperation) callconv(.C) JSValue { - VirtualMachine.vm.defaultErrorHandler(promise.result(global.vm())); + // VirtualMachine.vm.defaultErrorHandler(promise.result(global.vm()), null); return JSValue.jsUndefined(); } @@ -854,30 +856,52 @@ pub const VirtualMachine = struct { // TODO: pub fn deinit(this: *VirtualMachine) void {} - pub fn printException(this: *VirtualMachine, exception: *Exception) void { + pub const ExceptionList = std.ArrayList(Api.JsException); + + pub fn printException(this: *VirtualMachine, exception: *Exception, exception_list: ?*ExceptionList) void { if (Output.enable_ansi_colors) { - this.printErrorlikeObject(exception.value(), exception, true); + this.printErrorlikeObject(exception.value(), exception, exception_list, true); } else { - this.printErrorlikeObject(exception.value(), exception, false); + this.printErrorlikeObject(exception.value(), exception, exception_list, false); } } - pub fn defaultErrorHandler(this: *VirtualMachine, result: JSValue) void { + pub fn defaultErrorHandler(this: *VirtualMachine, result: JSValue, exception_list: ?*ExceptionList) void { if (result.isException(this.global.vm())) { var exception = @ptrCast(*Exception, result.asVoid()); - this.printException(exception); + this.printException(exception, exception_list); } else if (Output.enable_ansi_colors) { - this.printErrorlikeObject(result, null, true); + this.printErrorlikeObject(result, null, exception_list, true); } else { - this.printErrorlikeObject(result, null, false); + this.printErrorlikeObject(result, null, exception_list, false); } } pub fn loadEntryPoint(this: *VirtualMachine, entry_path: string) !*JSInternalPromise { try this.entry_point.generate(@TypeOf(this.bundler), &this.bundler, Fs.PathName.init(entry_path), main_file_name); this.main = entry_path; - var promise = JSModuleLoader.loadAndEvaluateModule(this.global, ZigString.init(std.mem.span(main_file_name))); + + var promise: *JSInternalPromise = undefined; + // We first import the node_modules bundle. This prevents any potential TDZ issues. + // The contents of the node_modules bundle are lazy, so hopefully this should be pretty quick. + if (this.node_modules != null) { + promise = JSModuleLoader.loadAndEvaluateModule(this.global, ZigString.init(std.mem.span(vm.bundler.linker.nodeModuleBundleImportPath()))); + + this.global.vm().drainMicrotasks(); + + while (promise.status(this.global.vm()) == JSPromise.Status.Pending) { + this.global.vm().drainMicrotasks(); + } + + if (promise.status(this.global.vm()) == JSPromise.Status.Rejected) { + return promise; + } + + _ = promise.result(this.global.vm()); + } + + promise = JSModuleLoader.loadAndEvaluateModule(this.global, ZigString.init(std.mem.span(main_file_name))); this.global.vm().drainMicrotasks(); @@ -896,14 +920,14 @@ pub const VirtualMachine = struct { // In that case, this function becomes recursive. // In all other cases, we will convert it to a ZigException. const errors_property = ZigString.init("errors"); - pub fn printErrorlikeObject(this: *VirtualMachine, value: JSValue, exception: ?*Exception, comptime allow_ansi_color: bool) void { + pub fn printErrorlikeObject(this: *VirtualMachine, value: JSValue, exception: ?*Exception, exception_list: ?*ExceptionList, comptime allow_ansi_color: bool) void { var was_internal = false; defer { if (was_internal) { if (exception) |exception_| { var holder = ZigException.Holder.init(); - var zig_exception = holder.zigException(); + var zig_exception: *ZigException = holder.zigException(); exception_.getStackTrace(&zig_exception.stack); if (zig_exception.stack.frames_len > 0) { var buffered_writer = std.io.bufferedWriter(Output.errorWriter()); @@ -917,12 +941,17 @@ pub const VirtualMachine = struct { buffered_writer.flush() catch {}; } + + if (exception_list) |list| { + zig_exception.addToErrorList(list) catch {}; + } } } } if (value.isAggregateError(this.global)) { const AggregateErrorIterator = struct { + pub var current_exception_list: ?*ExceptionList = null; pub fn iteratorWithColor(_vm: [*c]VM, globalObject: [*c]JSGlobalObject, nextValue: JSValue) callconv(.C) void { iterator(_vm, globalObject, nextValue, true); } @@ -930,9 +959,11 @@ pub const VirtualMachine = struct { iterator(_vm, globalObject, nextValue, false); } inline fn iterator(_vm: [*c]VM, globalObject: [*c]JSGlobalObject, nextValue: JSValue, comptime color: bool) void { - VirtualMachine.vm.printErrorlikeObject(nextValue, null, color); + VirtualMachine.vm.printErrorlikeObject(nextValue, null, current_exception_list, color); } }; + AggregateErrorIterator.current_exception_list = exception_list; + defer AggregateErrorIterator.current_exception_list = null; if (comptime allow_ansi_color) { value.getErrorsProperty(this.global).forEach(this.global, AggregateErrorIterator.iteratorWithColor); } else { @@ -943,34 +974,51 @@ pub const VirtualMachine = struct { if (js.JSValueIsObject(vm.global.ref(), value.asRef())) { if (js.JSObjectGetPrivate(value.asRef())) |priv| { - was_internal = this.printErrorFromMaybePrivateData(priv, allow_ansi_color); + was_internal = this.printErrorFromMaybePrivateData(priv, exception_list, allow_ansi_color); return; } } - was_internal = this.printErrorFromMaybePrivateData(value.asRef(), allow_ansi_color); + was_internal = this.printErrorFromMaybePrivateData(value.asRef(), exception_list, allow_ansi_color); } - pub fn printErrorFromMaybePrivateData(this: *VirtualMachine, value: ?*c_void, comptime allow_ansi_color: bool) bool { + pub fn printErrorFromMaybePrivateData(this: *VirtualMachine, value: ?*c_void, exception_list: ?*ExceptionList, comptime allow_ansi_color: bool) bool { const private_data_ptr = JSPrivateDataPtr.from(value); switch (private_data_ptr.tag()) { .BuildError => { defer Output.flush(); - const build_error = private_data_ptr.as(BuildError); - var writer = Output.errorWriter(); - build_error.msg.formatWriter(@TypeOf(writer), writer, allow_ansi_color) catch {}; + var build_error = private_data_ptr.as(BuildError); + if (!build_error.logged) { + var writer = Output.errorWriter(); + build_error.msg.formatWriter(@TypeOf(writer), writer, allow_ansi_color) catch {}; + build_error.logged = true; + } + if (exception_list != null) { + this.log.addMsg( + build_error.msg, + ) catch {}; + } return true; }, .ResolveError => { defer Output.flush(); - const resolve_error = private_data_ptr.as(ResolveError); - var writer = Output.errorWriter(); - resolve_error.msg.formatWriter(@TypeOf(writer), writer, allow_ansi_color) catch {}; + var resolve_error = private_data_ptr.as(ResolveError); + if (!resolve_error.logged) { + var writer = Output.errorWriter(); + resolve_error.msg.formatWriter(@TypeOf(writer), writer, allow_ansi_color) catch {}; + resolve_error.logged = true; + } + + if (exception_list != null) { + this.log.addMsg( + resolve_error.msg, + ) catch {}; + } return true; }, else => { - this.printErrorInstance(@intToEnum(JSValue, @intCast(i64, (@ptrToInt(value)))), allow_ansi_color) catch |err| { + this.printErrorInstance(@intToEnum(JSValue, @intCast(i64, (@ptrToInt(value)))), exception_list, allow_ansi_color) catch |err| { if (comptime isDebug) { // yo dawg Output.printErrorln("Error while printing Error-like object: {s}", .{@errorName(err)}); @@ -1053,10 +1101,13 @@ pub const VirtualMachine = struct { } } - pub fn printErrorInstance(this: *VirtualMachine, error_instance: JSValue, comptime allow_ansi_color: bool) !void { + pub fn printErrorInstance(this: *VirtualMachine, error_instance: JSValue, exception_list: ?*ExceptionList, comptime allow_ansi_color: bool) !void { var exception_holder = ZigException.Holder.init(); var exception = exception_holder.zigException(); error_instance.toZigException(vm.global, exception); + if (exception_list) |list| { + try exception.addToErrorList(list); + } var stderr: std.fs.File = Output.errorStream(); var buffered = std.io.bufferedWriter(stderr.writer()); @@ -1223,40 +1274,36 @@ pub const EventListenerMixin = struct { } }; - pub fn emitFetchEventError( - request: *http.RequestContext, - comptime fmt: string, - args: anytype, - ) void { - Output.prettyErrorln(fmt, args); - request.sendInternalError(error.FetchEventError) catch {}; - } - pub fn emitFetchEvent( vm: *VirtualMachine, request_context: *http.RequestContext, + comptime CtxType: type, + ctx: *CtxType, + comptime onError: fn (ctx: *CtxType, err: anyerror, value: JSValue, request_ctx: *http.RequestContext) anyerror!void, ) !void { - var listeners = vm.event_listeners.get(EventType.fetch) orelse return emitFetchEventError( - request_context, - "Missing \"fetch\" handler. Did you run \"addEventListener(\"fetch\", (event) => {{}})\"?", - .{}, - ); - if (listeners.items.len == 0) return emitFetchEventError( - request_context, - "Missing \"fetch\" handler. Did you run \"addEventListener(\"fetch\", (event) => {{}})\"?", - .{}, - ); + var listeners = vm.event_listeners.get(EventType.fetch) orelse (return onError(ctx, error.NoListeners, JSValue.jsUndefined(), request_context) catch {}); + if (listeners.items.len == 0) return onError(ctx, error.NoListeners, JSValue.jsUndefined(), request_context) catch {}; + const FetchEventRejectionHandler = struct { + pub fn onRejection(_ctx: *c_void, err: anyerror, fetch_event: *FetchEvent, value: JSValue) void { + onError( + @intToPtr(*CtxType, @ptrToInt(_ctx)), + err, + value, + fetch_event.request_context, + ) catch {}; + } + }; // Rely on JS finalizer var fetch_event = try vm.allocator.create(FetchEvent); fetch_event.* = FetchEvent{ .request_context = request_context, .request = Request{ .request_context = request_context }, + .onPromiseRejectionCtx = @as(*c_void, ctx), + .onPromiseRejectionHandler = FetchEventRejectionHandler.onRejection, }; var fetch_args: [1]js.JSObjectRef = undefined; - var exception: ?*Exception = null; - const failed_str = "Failed"; for (listeners.items) |listener_ref| { var listener = @intToEnum(JSValue, @intCast(i64, @ptrToInt(listener_ref))); @@ -1266,19 +1313,11 @@ pub const EventListenerMixin = struct { var promise = JSPromise.resolvedPromise(vm.global, result); vm.global.vm().drainMicrotasks(); + if (fetch_event.rejected) return; + if (promise.status(vm.global.vm()) == .Rejected) { - if (exception == null) { - var res = promise.result(vm.global.vm()); - if (res.isException(vm.global.vm())) { - exception = @ptrCast(*Exception, res.asVoid()); - } else { - vm.defaultErrorHandler(res); - if (!request_context.has_called_done) { - request_context.sendInternalError(error.JavaScriptErrorNeedARealErrorPageSorryAboutThisSeeTheTerminal) catch {}; - } - return; - } - } + onError(ctx, error.JSError, promise.result(vm.global.vm()), request_context) catch {}; + return; } else { _ = promise.result(vm.global.vm()); } @@ -1290,21 +1329,9 @@ pub const EventListenerMixin = struct { } } - if (exception) |except| { - vm.printException(except); - - if (!request_context.has_called_done) { - request_context.sendInternalError(error.JavaScriptErrorNeedARealErrorPageSorryAboutThisSeeTheTerminal) catch {}; - } - return; - } - if (!request_context.has_called_done) { - return emitFetchEventError( - request_context, - "\"fetch\" handler never called event.respondWith()", - .{}, - ); + onError(ctx, error.FetchHandlerRespondWithNeverCalled, JSValue.jsUndefined(), request_context) catch {}; + return; } } @@ -1368,6 +1395,7 @@ pub const ResolveError = struct { msg: logger.Msg, allocator: *std.mem.Allocator, referrer: ?Fs.Path = null, + logged: bool = false, pub fn fmt(allocator: *std.mem.Allocator, specifier: string, referrer: string, err: anyerror) !string { switch (err) { @@ -1515,6 +1543,7 @@ pub const BuildError = struct { msg: logger.Msg, // resolve_result: Resolver.Result, allocator: *std.mem.Allocator, + logged: bool = false, pub const Class = NewClass( BuildError, diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig index 09014c17f..55e4a753e 100644 --- a/src/javascript/jsc/webcore/response.zig +++ b/src/javascript/jsc/webcore/response.zig @@ -1009,6 +1009,10 @@ pub const FetchEvent = struct { request_context: *http.RequestContext, request: Request, + onPromiseRejectionCtx: *c_void = undefined, + onPromiseRejectionHandler: ?fn (ctx: *c_void, err: anyerror, fetch_event: *FetchEvent, value: JSValue) void = null, + rejected: bool = false, + pub const Class = NewClass( FetchEvent, .{ @@ -1101,8 +1105,13 @@ pub const FetchEvent = struct { switch (status) { .Fulfilled => {}, else => { - VirtualMachine.vm.defaultErrorHandler(resolved.result(VirtualMachine.vm.global.vm())); - this.request_context.sendInternalError(error.rejectedPromiseSeeConsole) catch {}; + this.rejected = true; + this.onPromiseRejectionHandler.?( + this.onPromiseRejectionCtx, + error.PromiseRejection, + this, + resolved.result(VirtualMachine.vm.global.vm()), + ); return js.JSValueMakeUndefined(ctx); }, } @@ -1110,14 +1119,16 @@ pub const FetchEvent = struct { var arg = resolved.result(VirtualMachine.vm.global.vm()).asObjectRef(); if (!js.JSValueIsObjectOfClass(ctx, arg, Response.Class.ref)) { + this.rejected = true; JSError(getAllocator(ctx), "event.respondWith() must be a Response or a Promise<Response>.", .{}, ctx, exception); - this.request_context.sendInternalError(error.respondWithWasEmpty) catch {}; + this.onPromiseRejectionHandler.?(this.onPromiseRejectionCtx, error.RespondWithInvalidType, this, JSValue.fromRef(exception.*)); return js.JSValueMakeUndefined(ctx); } var response: *Response = GetJSPrivateData(Response, arg) orelse { + this.rejected = true; JSError(getAllocator(ctx), "event.respondWith()'s Response object was invalid. This may be an internal error.", .{}, ctx, exception); - this.request_context.sendInternalError(error.respondWithWasInvalid) catch {}; + this.onPromiseRejectionHandler.?(this.onPromiseRejectionCtx, error.RespondWithInvalidTypeInternal, this, JSValue.fromRef(exception.*)); return js.JSValueMakeUndefined(ctx); }; |