|
2 | 2 | // Use of this source code is governed by a BSD-style license that can be |
3 | 3 | // found in the LICENSE file. |
4 | 4 |
|
| 5 | +#include "src/base/logging.h" |
5 | 6 | #include "src/builtins/builtins-utils-inl.h" |
6 | 7 | #include "src/builtins/builtins.h" |
| 8 | +#include "src/common/message-template.h" |
7 | 9 | #include "src/logging/counters.h" |
8 | 10 | #include "src/objects/elements.h" |
9 | 11 | #include "src/objects/heap-number-inl.h" |
10 | 12 | #include "src/objects/js-array-buffer-inl.h" |
11 | 13 | #include "src/objects/objects-inl.h" |
| 14 | +#include "third_party/simdutf/simdutf.h" |
12 | 15 |
|
13 | 16 | namespace v8 { |
14 | 17 | namespace internal { |
@@ -322,5 +325,222 @@ BUILTIN(TypedArrayPrototypeReverse) { |
322 | 325 | return *array; |
323 | 326 | } |
324 | 327 |
|
| 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 | + |
325 | 545 | } // namespace internal |
326 | 546 | } // namespace v8 |
0 commit comments