From c383c6cd810a80c6080096bbad737f9fa17f2e7b Mon Sep 17 00:00:00 2001 From: Julian <29632054+Parzival-3141@users.noreply.github.com> Date: Thu, 20 Jul 2023 17:50:54 -0400 Subject: Pass constructor arguments to TextDecoder (#3692) * Make TextDecoder constructor use options parameter The constructor now actually sets TextDecoder properties using the options parameter. * Defer decoder allocation to end of constructor * Verify types of TextDecoder options * TextDecoder throw TypeError on failure * Tidying --- src/bun.js/webcore/encoding.zig | 65 ++++++++++++++++++++++++------- test/js/web/encoding/text-decoder.test.js | 19 +++++++++ 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/src/bun.js/webcore/encoding.zig b/src/bun.js/webcore/encoding.zig index 42256a9ca..41a27ccd4 100644 --- a/src/bun.js/webcore/encoding.zig +++ b/src/bun.js/webcore/encoding.zig @@ -630,11 +630,13 @@ pub const TextDecoder = struct { } else |err| { switch (err) { error.InvalidByteSequence => { - globalThis.throw("Invalid byte sequence", .{}); + globalThis.throwValue( + globalThis.createTypeErrorInstance("Invalid byte sequence", .{}), + ); return JSValue.zero; }, error.OutOfMemory => { - globalThis.throw("Out of memory", .{}); + globalThis.throwOutOfMemory(); return JSValue.zero; }, } @@ -647,7 +649,7 @@ pub const TextDecoder = struct { } else |err| { switch (err) { error.OutOfMemory => { - globalThis.throw("Out of memory", .{}); + globalThis.throwOutOfMemory(); return JSValue.zero; }, } @@ -676,26 +678,59 @@ pub const TextDecoder = struct { globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame, ) callconv(.C) ?*TextDecoder { - var args_ = callframe.arguments(1); + var args_ = callframe.arguments(2); var arguments: []const JSC.JSValue = args_.ptr[0..args_.len]; - var encoding = EncodingLabel.@"UTF-8"; + var decoder = TextDecoder{}; + if (arguments.len > 0) { - if (!arguments[0].isString()) { + // encoding + if (arguments[0].isString()) { + var str = arguments[0].toSlice(globalThis, default_allocator); + defer if (str.isAllocated()) str.deinit(); + + if (EncodingLabel.which(str.slice())) |label| { + decoder.encoding = label; + } else { + globalThis.throwInvalidArguments("Unsupported encoding label \"{s}\"", .{str.slice()}); + return null; + } + } else { globalThis.throwInvalidArguments("TextDecoder(encoding) label is invalid", .{}); return null; } - var str = arguments[0].toSlice(globalThis, default_allocator); - defer if (str.isAllocated()) str.deinit(); - encoding = EncodingLabel.which(str.slice()) orelse { - globalThis.throwInvalidArguments("Unsupported encoding label \"{s}\"", .{str.slice()}); - return null; - }; + if (arguments.len >= 2) { + const options = arguments[1]; + + if (!options.isObject()) { + globalThis.throwInvalidArguments("TextDecoder(options) is invalid", .{}); + return null; + } + + if (options.get(globalThis, "fatal")) |fatal| { + if (fatal.isBoolean()) { + decoder.fatal = fatal.asBoolean(); + } else { + globalThis.throwInvalidArguments("TextDecoder(options) fatal is invalid. Expected boolean value", .{}); + return null; + } + } + + if (options.get(globalThis, "ignoreBOM")) |ignoreBOM| { + if (ignoreBOM.isBoolean()) { + decoder.ignore_bom = ignoreBOM.asBoolean(); + } else { + globalThis.throwInvalidArguments("TextDecoder(options) ignoreBOM is invalid. Expected boolean value", .{}); + return null; + } + } + } } - var decoder = getAllocator(globalThis).create(TextDecoder) catch unreachable; - decoder.* = TextDecoder{ .encoding = encoding }; - return decoder; + + var result = getAllocator(globalThis).create(TextDecoder) catch unreachable; + result.* = decoder; + return result; } }; diff --git a/test/js/web/encoding/text-decoder.test.js b/test/js/web/encoding/text-decoder.test.js index abd4c2a72..d8038e628 100644 --- a/test/js/web/encoding/text-decoder.test.js +++ b/test/js/web/encoding/text-decoder.test.js @@ -225,6 +225,25 @@ describe("TextDecoder", () => { expect(decoder.decode(bytes.subarray(0, amount.written))).toBe(text); gcTrace(true); }); + + it("should respect fatal when encountering invalid data", () => { + const decoder = new TextDecoder("utf-8", { fatal: true }); + expect(() => { + decoder.decode(new Uint8Array([0xC0])) // Invalid UTF8 + }).toThrow(TypeError); + }); + + it("constructor should set values", () => { + const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }); + expect(decoder.fatal).toBe(true); + // expect(decoder.ignoreBOM).toBe(false); // currently the getter for ignoreBOM doesn't work and always returns undefined + }); + + it("should throw on invalid input", () => { + expect(() => { + const decoder = new TextDecoder("utf-8", { fatal: 10, ignoreBOM: {} }); + }).toThrow(); + }); }); it("truncated sequences", () => { -- cgit v1.2.3