diff options
author | 2023-07-02 20:16:52 -0700 | |
---|---|---|
committer | 2023-07-02 20:16:52 -0700 | |
commit | eb90ce50c362149829590dc78db4d4d73fb2b36e (patch) | |
tree | 546640ccdc27deff653c17e8b7f5d5a2e8c39e39 | |
parent | c3f8593f8cb1571b41b8233e5ded98e3d3f99fb0 (diff) | |
download | bun-eb90ce50c362149829590dc78db4d4d73fb2b36e.tar.gz bun-eb90ce50c362149829590dc78db4d4d73fb2b36e.tar.zst bun-eb90ce50c362149829590dc78db4d4d73fb2b36e.zip |
Use fast path for Base64 in `btoa` (#3504)
* Use fast path for Base64 in `atob`
* Fix utf16, crash on linux
---------
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
-rw-r--r-- | src/bun.js/bindings/ZigGlobalObject.cpp | 74 | ||||
-rw-r--r-- | test/js/web/util/atob.test.js | 9 |
2 files changed, 56 insertions, 27 deletions
diff --git a/src/bun.js/bindings/ZigGlobalObject.cpp b/src/bun.js/bindings/ZigGlobalObject.cpp index 7ba0f0b30..102e5e4e8 100644 --- a/src/bun.js/bindings/ZigGlobalObject.cpp +++ b/src/bun.js/bindings/ZigGlobalObject.cpp @@ -181,6 +181,8 @@ namespace JSCastingHelpers = JSC::JSCastingHelpers; #include "DOMWrapperWorld-class.h" #include "CommonJSModuleRecord.h" #include <wtf/RAMSize.h> +#include <wtf/text/Base64.h> +#include "simdutf.h" constexpr size_t DEFAULT_ERROR_STACK_TRACE_LIMIT = 10; @@ -194,6 +196,24 @@ constexpr size_t DEFAULT_ERROR_STACK_TRACE_LIMIT = 10; // #include <iostream> static bool has_loaded_jsc = false; +namespace WebCore { +class Base64Utilities { +public: + static ExceptionOr<String> atob(const String& encodedString) + { + if (encodedString.isNull()) + return String(); + + auto decodedData = base64Decode(encodedString, Base64DecodeMode::DefaultValidatePaddingAndIgnoreWhitespace); + if (!decodedData) + return Exception { InvalidCharacterError }; + + return String(decodedData->data(), decodedData->size()); + } +}; + +} + extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(const char* ptr, size_t length)) { if (has_loaded_jsc) @@ -1164,53 +1184,65 @@ JSC_DEFINE_HOST_FUNCTION(functionBTOA, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm()); if (callFrame->argumentCount() == 0) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - JSC::throwTypeError(globalObject, scope, "btoa requires 1 argument (a string)"_s); + JSC::throwTypeError(globalObject, throwScope, "btoa requires 1 argument (a string)"_s); return JSC::JSValue::encode(JSC::JSValue {}); } - const String& stringToEncode = callFrame->argument(0).toWTFString(globalObject); + JSValue arg0 = callFrame->uncheckedArgument(0); + WTF::String encodedString = arg0.toWTFString(globalObject); + RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::JSValue {})); - if (!stringToEncode || stringToEncode.isNull()) { - return JSC::JSValue::encode(JSC::jsString(vm, WTF::String())); + if (encodedString.isEmpty()) { + return JSC::JSValue::encode(JSC::jsEmptyString(vm)); } - if (!stringToEncode.isAllLatin1()) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - throwException(globalObject, scope, createDOMException(globalObject, ExceptionCode::InvalidCharacterError)); + if (!encodedString.isAllLatin1()) { + throwException(globalObject, throwScope, createDOMException(globalObject, InvalidCharacterError)); return JSC::JSValue::encode(JSC::JSValue {}); } - return JSC::JSValue::encode(JSC::jsString(vm, WTF::base64EncodeToString(stringToEncode.latin1()))); + if (!encodedString.is8Bit()) { + LChar* ptr; + unsigned length = encodedString.length(); + auto dest = WTF::String::createUninitialized(length, ptr); + WTF::StringImpl::copyCharacters(ptr, encodedString.characters16(), length); + encodedString = WTFMove(dest); + } + + unsigned length = encodedString.length(); + RELEASE_AND_RETURN( + throwScope, + Bun__encoding__toString( + encodedString.characters8(), + length, + globalObject, + static_cast<uint8_t>(WebCore::BufferEncodingType::base64))); } static JSC_DEFINE_HOST_FUNCTION(functionATOB, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame)) { JSC::VM& vm = globalObject->vm(); + auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm()); if (callFrame->argumentCount() == 0) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - JSC::throwTypeError(globalObject, scope, "atob requires 1 argument (a string)"_s); + JSC::throwTypeError(globalObject, throwScope, "atob requires 1 argument (a string)"_s); return JSC::JSValue::encode(JSC::JSValue {}); } - const WTF::String& encodedString = callFrame->argument(0).toWTFString(globalObject); + WTF::String encodedString = callFrame->uncheckedArgument(0).toWTFString(globalObject); + RETURN_IF_EXCEPTION(throwScope, JSC::JSValue::encode(JSC::JSValue {})); - if (encodedString.isNull()) { - return JSC::JSValue::encode(JSC::jsEmptyString(vm)); - } - - auto decodedData = WTF::base64Decode(encodedString, Base64DecodeMode::DefaultValidatePaddingAndIgnoreWhitespace); - if (!decodedData) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - throwException(globalObject, scope, createDOMException(globalObject, ExceptionCode::InvalidCharacterError)); + auto result = WebCore::Base64Utilities::atob(encodedString); + if (result.hasException()) { + throwException(globalObject, throwScope, createDOMException(*globalObject, result.releaseException())); return JSC::JSValue::encode(JSC::JSValue {}); } - return JSC::JSValue::encode(JSC::jsString(vm, WTF::String(decodedData->data(), decodedData->size()))); + RELEASE_AND_RETURN(throwScope, JSValue::encode(jsString(vm, result.releaseReturnValue()))); } static JSC_DEFINE_HOST_FUNCTION(functionHashCode, diff --git a/test/js/web/util/atob.test.js b/test/js/web/util/atob.test.js index 4945829e1..20c029f14 100644 --- a/test/js/web/util/atob.test.js +++ b/test/js/web/util/atob.test.js @@ -60,18 +60,15 @@ it("btoa", () => { expect(btoa("abcde")).toBe("YWJjZGU="); expect(btoa("abcdef")).toBe("YWJjZGVm"); expect(typeof btoa).toBe("function"); - try { - btoa(); - throw new Error("Expected error"); - } catch (error) { - expect(error.name).toBe("TypeError"); - } + expect(() => btoa()).toThrow("btoa requires 1 argument (a string)"); var window = "[object Window]"; expect(btoa("")).toBe(""); expect(btoa(null)).toBe("bnVsbA=="); expect(btoa(undefined)).toBe("dW5kZWZpbmVk"); expect(btoa(window)).toBe("W29iamVjdCBXaW5kb3dd"); expect(btoa("éé")).toBe("6ek="); + // check for utf16 + expect(btoa("🧐éé".substring("🧐".length))).toBe("6ek="); expect(btoa("\u0080\u0081")).toBe("gIE="); expect(btoa(Bun)).toBe(btoa("[object Bun]")); }); |