Skip to content

Commit 2596e19

Browse files
committed
[JSC] add support for Uint8Array.prototype.toBase64 and Uint8Array.prototype.toHex
https://bugs.webkit.org/show_bug.cgi?id=274635 Reviewed by Yusuke Suzuki. These methods will make it easier for developers to encode typed character/byte data. Note that there are other methods in the related proposal, but for ease of reviewing they will be implemented separately. Spec: <https://tc39.es/proposal-arraybuffer-base64/spec/> Proposal: <https://github.com/tc39/proposal-arraybuffer-base64> * Source/JavaScriptCore/runtime/JSGenericTypedArrayViewPrototype.h: * Source/JavaScriptCore/runtime/JSGenericTypedArrayViewPrototypeInlines.h: (JSC::JSGenericTypedArrayViewPrototype<ViewClass>::finishCreation): * Source/JavaScriptCore/runtime/JSGenericTypedArrayViewPrototype.cpp: Added. (JSC::uint8ArrayPrototypeToBase64): (JSC::uint8ArrayPrototypeToHex): * Source/JavaScriptCore/runtime/CommonIdentifiers.h: Create builtin identifiers for `alphabet` and `omitPadding`. * Source/JavaScriptCore/runtime/OptionsList.h: Add a `useUint8ArrayBase64Methods` feature flag for these new methods (and the others that are part of the proposal that haven't been implemented yet). * Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj: * Source/JavaScriptCore/Sources.txt: * JSTests/stress/uint8array-toBase64.js: Added. * JSTests/stress/uint8array-toHex.js: Added. Canonical link: https://commits.webkit.org/280654@main
1 parent a919970 commit 2596e19

File tree

9 files changed

+406
-0
lines changed

9 files changed

+406
-0
lines changed

JSTests/stress/uint8array-toBase64.js

Lines changed: 258 additions & 0 deletions
Large diffs are not rendered by default.

JSTests/stress/uint8array-toHex.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//@ requireOptions("--useUint8ArrayBase64Methods=1")
2+
3+
function shouldBe(actual, expected) {
4+
if (actual !== expected)
5+
throw new Error(`FAIL: expected '${expected}' actual '${actual}'`);
6+
}
7+
8+
shouldBe((new Uint8Array([])).toHex(), "");
9+
shouldBe((new Uint8Array([0])).toHex(), "00");
10+
shouldBe((new Uint8Array([1])).toHex(), "01");
11+
shouldBe((new Uint8Array([128])).toHex(), "80");
12+
shouldBe((new Uint8Array([254])).toHex(), "fe");
13+
shouldBe((new Uint8Array([255])).toHex(), "ff");
14+
shouldBe((new Uint8Array([0, 1])).toHex(), "0001");
15+
shouldBe((new Uint8Array([254, 255])).toHex(), "feff");
16+
shouldBe((new Uint8Array([0, 1, 128, 254, 255])).toHex(), "000180feff");
17+
18+
try {
19+
let uint8array = new Uint8Array;
20+
$.detachArrayBuffer(uint8array.buffer);
21+
uint8array.toHex();
22+
} catch (e) {
23+
shouldBe(e instanceof TypeError, true);
24+
}

Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4726,6 +4726,7 @@
47264726
918E15BD2447B22600447A56 /* AggregateErrorPrototype.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AggregateErrorPrototype.h; sourceTree = "<group>"; };
47274727
918E15BE2447B22700447A56 /* AggregateErrorPrototype.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = AggregateErrorPrototype.cpp; sourceTree = "<group>"; };
47284728
918E15BF2447B22700447A56 /* AggregateErrorConstructor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AggregateErrorConstructor.h; sourceTree = "<group>"; };
4729+
91C3265D2BFFB2BF009C7E2B /* JSGenericTypedArrayViewPrototype.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSGenericTypedArrayViewPrototype.cpp; sourceTree = "<group>"; };
47294730
91D1578C24E0BE35001F4CED /* Breakpoint.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Breakpoint.cpp; sourceTree = "<group>"; };
47304731
91F548A52A4CF448007292DE /* MapConstructor.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = MapConstructor.js; sourceTree = "<group>"; };
47314732
91FD55322A4CF52100F5D482 /* MapConstructor.lut.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MapConstructor.lut.h; sourceTree = "<group>"; };
@@ -8335,6 +8336,7 @@
83358336
0F2B66C417B6B5AB00A7AE3F /* JSGenericTypedArrayViewConstructor.h */,
83368337
0F2B66C517B6B5AB00A7AE3F /* JSGenericTypedArrayViewConstructorInlines.h */,
83378338
0F2B66C617B6B5AB00A7AE3F /* JSGenericTypedArrayViewInlines.h */,
8339+
91C3265D2BFFB2BF009C7E2B /* JSGenericTypedArrayViewPrototype.cpp */,
83388340
0F2B66C717B6B5AB00A7AE3F /* JSGenericTypedArrayViewPrototype.h */,
83398341
53917E7A1B7906E4000EBD33 /* JSGenericTypedArrayViewPrototypeFunctions.h */,
83408342
0F2B66C817B6B5AB00A7AE3F /* JSGenericTypedArrayViewPrototypeInlines.h */,

Source/JavaScriptCore/Sources.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,7 @@ runtime/JSFinalizationRegistry.cpp
917917
runtime/JSFunction.cpp
918918
runtime/JSGenerator.cpp
919919
runtime/JSGeneratorFunction.cpp
920+
runtime/JSGenericTypedArrayViewPrototype.cpp
920921
runtime/JSGlobalLexicalEnvironment.cpp
921922
runtime/JSGlobalObject.cpp
922923
runtime/JSGlobalObjectDebuggable.cpp

Source/JavaScriptCore/runtime/CommonIdentifiers.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
macro(__lookupSetter__) \
7373
macro(add) \
7474
macro(additionalJettisonReason) \
75+
macro(alphabet) \
7576
macro(anonymous) \
7677
macro(arguments) \
7778
macro(as) \
@@ -221,6 +222,7 @@
221222
macro(numberingSystem) \
222223
macro(numeric) \
223224
macro(of) \
225+
macro(omitPadding) \
224226
macro(opcode) \
225227
macro(origin) \
226228
macro(osrExitSites) \
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* Copyright (C) 2024 Devin Rousso <[email protected]>. All rights reserved.
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions
6+
* are met:
7+
* 1. Redistributions of source code must retain the above copyright
8+
* notice, this list of conditions and the following disclaimer.
9+
* 2. Redistributions in binary form must reproduce the above copyright
10+
* notice, this list of conditions and the following disclaimer in the
11+
* documentation and/or other materials provided with the distribution.
12+
*
13+
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15+
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17+
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23+
* THE POSSIBILITY OF SUCH DAMAGE.
24+
*/
25+
26+
#include "config.h"
27+
#include "JSGenericTypedArrayViewPrototype.h"
28+
29+
#include "ParseInt.h"
30+
#include <wtf/text/Base64.h>
31+
32+
namespace JSC {
33+
34+
JSC_DEFINE_HOST_FUNCTION(uint8ArrayPrototypeToBase64, (JSGlobalObject* globalObject, CallFrame* callFrame))
35+
{
36+
VM& vm = globalObject->vm();
37+
auto scope = DECLARE_THROW_SCOPE(vm);
38+
39+
JSUint8Array* uint8Array = jsDynamicCast<JSUint8Array*>(callFrame->thisValue());
40+
if (UNLIKELY(!uint8Array))
41+
return throwVMTypeError(globalObject, scope, "Uint8Array.prototype.toBase64 requires that |this| be a Uint8Array"_s);
42+
43+
OptionSet<Base64EncodeOption> options;
44+
45+
JSValue optionsValue = callFrame->argument(0);
46+
if (!optionsValue.isUndefined()) {
47+
JSObject* optionsObject = jsDynamicCast<JSObject*>(optionsValue);
48+
if (UNLIKELY(!optionsObject))
49+
return throwVMTypeError(globalObject, scope, "Uint8Array.prototype.toBase64 requires that options be an object"_s);
50+
51+
JSValue alphabetValue = optionsObject->get(globalObject, vm.propertyNames->alphabet);
52+
RETURN_IF_EXCEPTION(scope, { });
53+
if (!alphabetValue.isUndefined()) {
54+
JSString* alphabetString = jsDynamicCast<JSString*>(alphabetValue);
55+
if (UNLIKELY(!alphabetString))
56+
return throwVMTypeError(globalObject, scope, "Uint8Array.prototype.toBase64 requires that alphabet be \"base64\" or \"base64url\""_s);
57+
58+
StringView alphabetStringView = alphabetString->view(globalObject);
59+
if (alphabetStringView == "base64url"_s)
60+
options.add(Base64EncodeOption::URL);
61+
else if (alphabetStringView != "base64"_s)
62+
return throwVMTypeError(globalObject, scope, "Uint8Array.prototype.toBase64 requires that alphabet be \"base64\" or \"base64url\""_s);
63+
}
64+
65+
JSValue omitPaddingValue = optionsObject->get(globalObject, vm.propertyNames->omitPadding);
66+
RETURN_IF_EXCEPTION(scope, { });
67+
if (omitPaddingValue.toBoolean(globalObject))
68+
options.add(Base64EncodeOption::OmitPadding);
69+
}
70+
71+
IdempotentArrayBufferByteLengthGetter<std::memory_order_seq_cst> byteLengthGetter;
72+
if (UNLIKELY(isIntegerIndexedObjectOutOfBounds(uint8Array, byteLengthGetter)))
73+
return throwVMTypeError(globalObject, scope, typedArrayBufferHasBeenDetachedErrorMessage);
74+
75+
const uint8_t* data = uint8Array->typedVector();
76+
size_t length = uint8Array->length();
77+
RELEASE_AND_RETURN(scope, JSValue::encode(jsString(vm, base64EncodeToString({ data, length }, options))));
78+
}
79+
80+
JSC_DEFINE_HOST_FUNCTION(uint8ArrayPrototypeToHex, (JSGlobalObject* globalObject, CallFrame* callFrame))
81+
{
82+
VM& vm = globalObject->vm();
83+
auto scope = DECLARE_THROW_SCOPE(vm);
84+
85+
JSUint8Array* uint8Array = jsDynamicCast<JSUint8Array*>(callFrame->thisValue());
86+
if (UNLIKELY(!uint8Array))
87+
return throwVMTypeError(globalObject, scope, "Uint8Array.prototype.toHex requires that |this| be a Uint8Array"_s);
88+
89+
IdempotentArrayBufferByteLengthGetter<std::memory_order_seq_cst> byteLengthGetter;
90+
if (UNLIKELY(isIntegerIndexedObjectOutOfBounds(uint8Array, byteLengthGetter)))
91+
return throwVMTypeError(globalObject, scope, typedArrayBufferHasBeenDetachedErrorMessage);
92+
93+
StringBuilder builder;
94+
builder.reserveCapacity(uint8Array->length() * 2);
95+
96+
const uint8_t* data = uint8Array->typedVector();
97+
size_t length = uint8Array->length();
98+
for (size_t i = 0; i < length; ++i) {
99+
builder.append(radixDigits[data[i] / 16]);
100+
builder.append(radixDigits[data[i] % 16]);
101+
}
102+
RELEASE_AND_RETURN(scope, JSValue::encode(jsString(vm, builder.toString())));
103+
}
104+
105+
} // namespace JSC

Source/JavaScriptCore/runtime/JSGenericTypedArrayViewPrototype.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,7 @@ class JSGenericTypedArrayViewPrototype final : public JSNonFinalObject {
5656
void finishCreation(VM&, JSGlobalObject*);
5757
};
5858

59+
JSC_DECLARE_HOST_FUNCTION(uint8ArrayPrototypeToBase64);
60+
JSC_DECLARE_HOST_FUNCTION(uint8ArrayPrototypeToHex);
61+
5962
} // namespace JSC

Source/JavaScriptCore/runtime/JSGenericTypedArrayViewPrototypeInlines.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727

2828
#include "JSGenericTypedArrayViewPrototype.h"
2929

30+
#include "JSTypedArrays.h"
31+
3032
namespace JSC {
3133

3234
template<typename ViewClass>
@@ -44,6 +46,14 @@ void JSGenericTypedArrayViewPrototype<ViewClass>::finishCreation(
4446
ASSERT(inherits(info()));
4547

4648
putDirectWithoutTransition(vm, vm.propertyNames->BYTES_PER_ELEMENT, jsNumber(ViewClass::elementSize), PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly | PropertyAttribute::DontDelete);
49+
50+
if constexpr (std::is_same_v<ViewClass, JSUint8Array>) {
51+
if (Options::useUint8ArrayBase64Methods()) {
52+
JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toBase64"_s, uint8ArrayPrototypeToBase64, static_cast<unsigned>(PropertyAttribute::DontEnum), 0, ImplementationVisibility::Public);
53+
JSC_NATIVE_FUNCTION_WITHOUT_TRANSITION("toHex"_s, uint8ArrayPrototypeToHex, static_cast<unsigned>(PropertyAttribute::DontEnum), 0, ImplementationVisibility::Public);
54+
}
55+
}
56+
4757
globalObject->installTypedArrayIteratorProtocolWatchpoint(this, ViewClass::TypedArrayStorageType);
4858
}
4959

Source/JavaScriptCore/runtime/OptionsList.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,7 @@ bool hasCapacityToUseLargeGigacage();
599599
v(Bool, useStringWellFormed, true, Normal, "Expose the String well-formed methods."_s) \
600600
v(Bool, useTemporal, false, Normal, "Expose the Temporal object."_s) \
601601
v(Bool, useTrustedTypes, false, Normal, "Enable trusted types eval protection feature."_s) \
602+
v(Bool, useUint8ArrayBase64Methods, false, Normal, "Expose methods for converting Uint8Array to/from base64 and hex."_s) \
602603
v(Bool, useWebAssemblyTypedFunctionReferences, true, Normal, "Allow function types from the wasm typed function references spec."_s) \
603604
v(Bool, useWebAssemblyGC, false, Normal, "Allow gc types from the wasm gc proposal."_s) \
604605
v(Bool, useWebAssemblySIMD, true, Normal, "Allow the new simd instructions and types from the wasm simd spec."_s) \

0 commit comments

Comments
 (0)