Skip to content

Commit e8293d2

Browse files
rmahdavV8 LUCI CQ
authored andcommitted
[base64] Add FromBase64
This CL adds `Uint8Array.fromBase64()`. Bug: 42204568 Change-Id: Ib68683a2d5ead9720077197c0f895787214b183e Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6026354 Commit-Queue: Rezvan Mahdavi Hezaveh <[email protected]> Reviewed-by: Shu-yu Guo <[email protected]> Cr-Commit-Position: refs/heads/main@{#98239}
1 parent 93df92d commit e8293d2

17 files changed

Lines changed: 714 additions & 414 deletions

File tree

BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2536,6 +2536,8 @@ filegroup(
25362536
"third_party/siphash/halfsiphash.cc",
25372537
"third_party/siphash/halfsiphash.h",
25382538
"third_party/utf8-decoder/utf8-decoder.h",
2539+
"third_party/simdutf/simdutf.cpp",
2540+
"third_party/simdutf/simdutf.h",
25392541
":cppgc_base_files",
25402542
":generated_bytecode_builtins_list",
25412543
":v8_bigint",

BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6181,6 +6181,7 @@ v8_source_set("v8_base_without_compiler") {
61816181
":v8_headers",
61826182
":v8_internal_headers",
61836183
":v8_maybe_icu",
6184+
"//third_party/simdutf:simdutf",
61846185
]
61856186

61866187
if (v8_fuzzilli) {

DEPS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,8 @@ deps = {
249249
'url': Var('chromium_url') + '/chromium/src/third_party/fuchsia-gn-sdk.git' + '@' + '1e538ac2d0583511f489a9f940a0b8a4f2a2f3ab',
250250
'condition': 'checkout_fuchsia',
251251
},
252+
'third_party/simdutf':
253+
Var('chromium_url') + '/chromium/src/third_party/simdutf' + '@' + '5a9a2134b280c1b956ad68a0643797fe26dd1c94',
252254
# Exists for rolling the Fuchsia SDK. Check out of the SDK should always
253255
# rely on the hook running |update_sdk.py| script below.
254256
'third_party/fuchsia-sdk/sdk': {
@@ -504,6 +506,7 @@ include_rules = [
504506
'+third_party/fp16/src/include',
505507
'+third_party/fuzztest',
506508
'+third_party/ittapi/include',
509+
'+third_party/simdutf',
507510
'+third_party/v8/codegen',
508511
'+third_party/vtune',
509512
'+hwy/highway.h',

src/builtins/builtins-definitions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1097,6 +1097,8 @@ namespace internal {
10971097
TFJ(TypedArrayPrototypeToStringTag, kJSArgcReceiverSlots, kReceiver) \
10981098
/* ES6 %TypedArray%.prototype.map */ \
10991099
TFJ(TypedArrayPrototypeMap, kDontAdaptArgumentsSentinel) \
1100+
/* #sec-uint8array.frombase64*/ \
1101+
CPP(Uint8ArrayFromBase64, kDontAdaptArgumentsSentinel) \
11001102
\
11011103
/* Wasm */ \
11021104
IF_WASM_DRUMBRAKE(ASM, WasmInterpreterEntry, WasmDummy) \

src/builtins/builtins-typed-array.cc

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
#include "src/base/logging.h"
56
#include "src/builtins/builtins-utils-inl.h"
67
#include "src/builtins/builtins.h"
8+
#include "src/common/message-template.h"
79
#include "src/logging/counters.h"
810
#include "src/objects/elements.h"
911
#include "src/objects/heap-number-inl.h"
1012
#include "src/objects/js-array-buffer-inl.h"
1113
#include "src/objects/objects-inl.h"
14+
#include "third_party/simdutf/simdutf.h"
1215

1316
namespace v8 {
1417
namespace internal {
@@ -322,5 +325,222 @@ BUILTIN(TypedArrayPrototypeReverse) {
322325
return *array;
323326
}
324327

328+
namespace {
329+
330+
template <typename T>
331+
Maybe<T> MapOptionToEnum(
332+
Isolate* isolate, Handle<String> option_string,
333+
const std::vector<std::tuple<const char*, size_t, T>>& allowed_options) {
334+
option_string = String::Flatten(isolate, option_string);
335+
336+
{
337+
DisallowGarbageCollection no_gc;
338+
String::FlatContent option_content = option_string->GetFlatContent(no_gc);
339+
340+
if (option_content.IsOneByte()) {
341+
const unsigned char* option_string_to_compare =
342+
option_content.ToOneByteVector().data();
343+
size_t length = option_content.ToOneByteVector().size();
344+
345+
for (auto& [str_val, str_size, enum_val] : allowed_options) {
346+
if (str_size == length &&
347+
CompareCharsEqual(option_string_to_compare, str_val, str_size)) {
348+
return Just<T>(enum_val);
349+
}
350+
}
351+
} else {
352+
const base::uc16* option_string_to_compare =
353+
option_content.ToUC16Vector().data();
354+
size_t length = option_content.ToUC16Vector().size();
355+
356+
for (auto& [str_val, str_size, enum_val] : allowed_options) {
357+
if (str_size == length &&
358+
CompareCharsEqual(option_string_to_compare, str_val, str_size)) {
359+
return Just<T>(enum_val);
360+
}
361+
}
362+
}
363+
}
364+
365+
isolate->Throw(*isolate->factory()->NewTypeError(
366+
MessageTemplate::kInvalidOption, option_string));
367+
return Nothing<T>();
368+
}
369+
370+
MessageTemplate ToMessageTemplate(simdutf::error_code error) {
371+
switch (error) {
372+
case simdutf::error_code::INVALID_BASE64_CHARACTER:
373+
return MessageTemplate::kInvalidBase64Character;
374+
case simdutf::error_code::BASE64_INPUT_REMAINDER:
375+
return MessageTemplate::kBase64InputRemainder;
376+
case simdutf::error_code::BASE64_EXTRA_BITS:
377+
return MessageTemplate::kBase64ExtraBits;
378+
default:
379+
UNREACHABLE();
380+
}
381+
}
382+
383+
template <typename T>
384+
Maybe<simdutf::result> ArrayBufferFromBase64(
385+
Isolate* isolate, T input_vector, size_t input_length,
386+
size_t& output_length, simdutf::base64_options alphabet,
387+
simdutf::last_chunk_handling_options last_chunk_handling,
388+
Handle<JSArrayBuffer>& buffer) {
389+
const char method_name[] = "Uint8Array.fromBase64";
390+
391+
output_length = simdutf::maximal_binary_length_from_base64(
392+
reinterpret_cast<const T>(input_vector), input_length);
393+
std::unique_ptr<char[]> output = std::make_unique<char[]>(output_length);
394+
simdutf::result simd_result = simdutf::base64_to_binary_safe(
395+
reinterpret_cast<const T>(input_vector), input_length, output.get(),
396+
output_length, alphabet, last_chunk_handling);
397+
398+
{
399+
AllowGarbageCollection gc;
400+
MaybeHandle<JSArrayBuffer> result_buffer =
401+
isolate->factory()->NewJSArrayBufferAndBackingStore(
402+
output_length, InitializedFlag::kUninitialized);
403+
if (!result_buffer.ToHandle(&buffer)) {
404+
isolate->Throw(*isolate->factory()->NewRangeError(
405+
MessageTemplate::kOutOfMemory,
406+
isolate->factory()->NewStringFromAsciiChecked(method_name)));
407+
return Nothing<simdutf::result>();
408+
}
409+
410+
memcpy(buffer->backing_store(), output.get(), output_length);
411+
}
412+
return Just<simdutf::result>(simd_result);
413+
}
414+
415+
} // namespace
416+
417+
// https://tc39.es/proposal-arraybuffer-base64/spec/#sec-uint8array.frombase64
418+
BUILTIN(Uint8ArrayFromBase64) {
419+
HandleScope scope(isolate);
420+
421+
// 1. If string is not a String, throw a TypeError exception.
422+
Handle<Object> input = args.atOrUndefined(isolate, 1);
423+
if (!IsString(*input)) {
424+
THROW_NEW_ERROR_RETURN_FAILURE(
425+
isolate, NewTypeError(MessageTemplate::kArgumentIsNonString,
426+
isolate->factory()->input_string()));
427+
}
428+
429+
Handle<String> input_string = String::Flatten(isolate, Cast<String>(input));
430+
431+
// 2. Let opts be ? GetOptionsObject(options).
432+
Handle<Object> options = args.atOrUndefined(isolate, 2);
433+
434+
// 3. Let alphabet be ? Get(opts, "alphabet").
435+
Handle<Object> opt_alphabet;
436+
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
437+
isolate, opt_alphabet,
438+
JSObject::ReadFromOptionsBag(
439+
options, isolate->factory()->alphabet_string(), isolate));
440+
441+
// 4. If alphabet is undefined, set alphabet to "base64".
442+
simdutf::base64_options alphabet;
443+
if (IsUndefined(*opt_alphabet)) {
444+
alphabet = simdutf::base64_default;
445+
} else if (!IsString(*opt_alphabet)) {
446+
THROW_NEW_ERROR_RETURN_FAILURE(
447+
isolate, NewTypeError(MessageTemplate::kInvalidOption, opt_alphabet));
448+
} else {
449+
// 5. If alphabet is neither "base64" nor "base64url", throw a TypeError
450+
// exception.
451+
Handle<String> alphabet_string = Cast<String>(opt_alphabet);
452+
453+
std::vector<std::tuple<const char*, size_t, simdutf::base64_options>>
454+
alphabet_vector = {
455+
{"base64", 6, simdutf::base64_options::base64_default},
456+
{"base64url", 9, simdutf::base64_options::base64_url}};
457+
MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
458+
isolate, alphabet,
459+
MapOptionToEnum(isolate, alphabet_string, alphabet_vector));
460+
}
461+
462+
// 6. Let lastChunkHandling be ? Get(opts, "lastChunkHandling").
463+
Handle<Object> opt_last_chunk_handling;
464+
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
465+
isolate, opt_last_chunk_handling,
466+
JSObject::ReadFromOptionsBag(
467+
options, isolate->factory()->last_chunk_handling_string(), isolate));
468+
469+
// 7. If lastChunkHandling is undefined, set lastChunkHandling to "loose".
470+
simdutf::last_chunk_handling_options last_chunk_handling;
471+
if (IsUndefined(*opt_last_chunk_handling)) {
472+
last_chunk_handling = simdutf::last_chunk_handling_options::loose;
473+
} else if (!IsString(*opt_last_chunk_handling)) {
474+
THROW_NEW_ERROR_RETURN_FAILURE(
475+
isolate,
476+
NewTypeError(MessageTemplate::kInvalidOption, opt_last_chunk_handling));
477+
} else {
478+
// 8. If lastChunkHandling is not one of "loose", "strict", or
479+
// "stop-before-partial", throw a TypeError exception.
480+
Handle<String> last_chunk_handling_string =
481+
Cast<String>(opt_last_chunk_handling);
482+
483+
std::vector<
484+
std::tuple<const char*, size_t, simdutf::last_chunk_handling_options>>
485+
last_chunk_handling_vector = {
486+
{"loose", 5, simdutf::last_chunk_handling_options::loose},
487+
{"strict", 6, simdutf::last_chunk_handling_options::strict},
488+
{"stop-before-partial", 19,
489+
simdutf::last_chunk_handling_options::stop_before_partial}};
490+
MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
491+
isolate, last_chunk_handling,
492+
MapOptionToEnum(isolate, last_chunk_handling_string,
493+
last_chunk_handling_vector));
494+
}
495+
496+
// 9. Let result be ? FromBase64(string, alphabet, lastChunkHandling).
497+
size_t input_length;
498+
size_t output_length;
499+
simdutf::result simd_result;
500+
Handle<JSArrayBuffer> buffer;
501+
{
502+
DisallowGarbageCollection no_gc;
503+
String::FlatContent input_content = input_string->GetFlatContent(no_gc);
504+
if (input_content.IsOneByte()) {
505+
const unsigned char* input_vector =
506+
input_content.ToOneByteVector().data();
507+
input_length = input_content.ToOneByteVector().size();
508+
MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
509+
isolate, simd_result,
510+
ArrayBufferFromBase64(isolate,
511+
reinterpret_cast<const char*>(input_vector),
512+
input_length, output_length, alphabet,
513+
last_chunk_handling, buffer));
514+
} else {
515+
const base::uc16* input_vector = input_content.ToUC16Vector().data();
516+
input_length = input_content.ToUC16Vector().size();
517+
MAYBE_ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
518+
isolate, simd_result,
519+
ArrayBufferFromBase64(isolate,
520+
reinterpret_cast<const char16_t*>(input_vector),
521+
input_length, output_length, alphabet,
522+
last_chunk_handling, buffer));
523+
}
524+
}
525+
526+
// 10. If result.[[Error]] is not none, then
527+
// a. Throw result.[[Error]].
528+
if (simd_result.error != simdutf::error_code::SUCCESS) {
529+
THROW_NEW_ERROR_RETURN_FAILURE(
530+
isolate, NewSyntaxError(ToMessageTemplate(simd_result.error)));
531+
}
532+
533+
// 11. Let resultLength be the length of result.[[Bytes]].
534+
// 12. Let ta be ? AllocateTypedArray("Uint8Array", %Uint8Array%,
535+
// "%Uint8Array.prototype%", resultLength).
536+
// 13. Set the value at each index of
537+
// ta.[[ViewedArrayBuffer]].[[ArrayBufferData]] to the value at the
538+
// corresponding index of result.[[Bytes]].
539+
// 14. Return ta.
540+
Handle<JSTypedArray> result_typed_array = isolate->factory()->NewJSTypedArray(
541+
kExternalUint8Array, buffer, 0, output_length);
542+
return *result_typed_array;
543+
}
544+
325545
} // namespace internal
326546
} // namespace v8

src/common/message-template.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ namespace internal {
126126
"Function has non-object prototype '%' in instanceof check") \
127127
T(InvalidArgument, "invalid_argument") \
128128
T(InvalidArgumentForTemporal, "Invalid argument for Temporal %") \
129+
T(InvalidOption, "invalid option %") \
129130
T(InvalidInOperatorUse, "Cannot use 'in' operator to search for '%' in %") \
130131
T(InvalidRawJsonValue, "Invalid value for JSON.rawJSON") \
131132
T(InvalidRegExpExecResult, \
@@ -448,6 +449,11 @@ namespace internal {
448449
"The requested module '%' contains conflicting star exports for name '%'") \
449450
T(BadGetterArity, "Getter must not have any formal parameters.") \
450451
T(BadSetterArity, "Setter must have exactly one formal parameter.") \
452+
T(Base64ExtraBits, \
453+
"The base64 input terminates with non-zero padding bits.") \
454+
T(Base64InputRemainder, \
455+
"The base64 input terminates with a single " \
456+
"character, excluding padding (=).") \
451457
T(BigIntInvalidString, "Invalid BigInt string") \
452458
T(ConstructorIsAccessor, "Class constructor may not be an accessor") \
453459
T(ConstructorIsGenerator, "Class constructor may not be a generator") \
@@ -481,6 +487,8 @@ namespace internal {
481487
"Illegal '%' directive in function with non-simple parameter list") \
482488
T(IllegalReturn, "Illegal return statement") \
483489
T(IntrinsicWithSpread, "Intrinsic calls do not support spread arguments") \
490+
T(InvalidBase64Character, \
491+
"Found a character that cannot be part of a valid base64 string.") \
484492
T(InvalidRestBindingPattern, \
485493
"`...` must be followed by an identifier in declaration contexts") \
486494
T(InvalidPropertyBindingPattern, "Illegal property in declaration context") \

src/flags/flag-definitions.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,9 +277,10 @@ DEFINE_BOOL(js_shipping, true, "enable all shipped JavaScript features")
277277
V(harmony_shadow_realm, "harmony ShadowRealm") \
278278
V(harmony_struct, "harmony structs, shared structs, and shared arrays")
279279

280-
#define JAVASCRIPT_INPROGRESS_FEATURES_BASE(V) \
281-
V(js_decorators, "decorators") \
282-
V(js_source_phase_imports, "source phase imports")
280+
#define JAVASCRIPT_INPROGRESS_FEATURES_BASE(V) \
281+
V(js_decorators, "decorators") \
282+
V(js_source_phase_imports, "source phase imports") \
283+
V(js_base_64, "Uint8Array to/from base64 and hex")
283284

284285
#ifdef V8_INTL_SUPPORT
285286
#define HARMONY_INPROGRESS(V) \

src/init/bootstrapper.cc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6004,6 +6004,17 @@ void Genesis::InitializeGlobal_js_source_phase_imports() {
60046004
Builtin::kAbstractModuleSourceToStringTag, kAdapt);
60056005
}
60066006

6007+
void Genesis::InitializeGlobal_js_base_64() {
6008+
if (!v8_flags.js_base_64) return;
6009+
6010+
Handle<JSGlobalObject> global(native_context()->global_object(), isolate());
6011+
Handle<JSObject> uint8array_function =
6012+
Cast<JSObject>(JSReceiver::GetProperty(isolate(), global, "Uint8Array")
6013+
.ToHandleChecked());
6014+
SimpleInstallFunction(isolate(), uint8array_function, "fromBase64",
6015+
Builtin::kUint8ArrayFromBase64, 1, kDontAdapt);
6016+
}
6017+
60076018
void Genesis::InitializeGlobal_regexp_linear_flag() {
60086019
if (!v8_flags.enable_experimental_regexp_engine) return;
60096020

src/init/heap-symbols.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@
172172
INTERNALIZED_STRING_LIST_GENERATOR_INTL(V, _) \
173173
V(_, add_string, "add") \
174174
V(_, AggregateError_string, "AggregateError") \
175+
V(_, alphabet_string, "alphabet") \
175176
V(_, always_string, "always") \
176177
V(_, anonymous_string, "anonymous") \
177178
V(_, apply_string, "apply") \
@@ -321,6 +322,7 @@
321322
V(_, jsMemoryRange_string, "jsMemoryRange") \
322323
V(_, keys_string, "keys") \
323324
V(_, largestUnit_string, "largestUnit") \
325+
V(_, last_chunk_handling_string, "lastChunkHandling") \
324326
V(_, lastIndex_string, "lastIndex") \
325327
V(_, let_string, "let") \
326328
V(_, line_string, "line") \

0 commit comments

Comments
 (0)