aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2023-07-02 20:16:52 -0700
committerGravatar GitHub <noreply@github.com> 2023-07-02 20:16:52 -0700
commiteb90ce50c362149829590dc78db4d4d73fb2b36e (patch)
tree546640ccdc27deff653c17e8b7f5d5a2e8c39e39
parentc3f8593f8cb1571b41b8233e5ded98e3d3f99fb0 (diff)
downloadbun-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.cpp74
-rw-r--r--test/js/web/util/atob.test.js9
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]"));
});