From dff4a35dbf320d3fc00e061c45cb12e068f90149 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 18 Jun 2024 16:46:54 -0700 Subject: [PATCH 1/4] src, deps: add nbytes library Projects that seek to implement Node.js compatible APIs end up needed to reproduce various bits of functionality internally in order to faithfully replicate the Node.js behaviors. This is particularly true for things like byte manipulation, base64 and hex encoding, and other low-level operations. This change proposes moving much of this low-level byte manipulation code out of nodejs/src and into a new `nbytes` library. Initially this new library will exist in the `deps` directory but the intent is to spin out a new separate repository to be its home in the future. Doing so will allow other projects to use the nbytes library with exactly the same implementation as Node.js. This commit moves only the byte swapping and legacy base64 handling code. Additional commits will move additional byte manipulation logic into the library. --- Makefile | 2 +- deps/nbytes/nbytes.cpp | 151 ++++++++++++++++++++++++++++++++++ deps/nbytes/nbytes.gyp | 17 ++++ deps/nbytes/nbytes.h | 163 +++++++++++++++++++++++++++++++++++++ node.gyp | 7 +- src/base64-inl.h | 126 ---------------------------- src/base64.h | 49 ----------- src/cares_wrap.cc | 16 ++-- src/inspector_socket.cc | 6 +- src/node_buffer.cc | 9 +- src/node_http2.cc | 6 +- src/node_http_common-inl.h | 3 +- src/node_i18n.cc | 7 +- src/node_sockaddr.cc | 4 +- src/spawn_sync.cc | 5 +- src/string_bytes.cc | 41 +++------- src/util-inl.h | 104 ----------------------- src/util.h | 21 ----- test/cctest/test_base64.cc | 7 +- 19 files changed, 381 insertions(+), 363 deletions(-) create mode 100644 deps/nbytes/nbytes.cpp create mode 100644 deps/nbytes/nbytes.gyp create mode 100644 deps/nbytes/nbytes.h delete mode 100644 src/base64-inl.h delete mode 100644 src/base64.h diff --git a/Makefile b/Makefile index c07ffb28c38169..c37e9e190e3c37 100644 --- a/Makefile +++ b/Makefile @@ -174,7 +174,7 @@ with-code-cache test-code-cache: out/Makefile: config.gypi common.gypi node.gyp \ deps/uv/uv.gyp deps/llhttp/llhttp.gyp deps/zlib/zlib.gyp \ - deps/simdutf/simdutf.gyp deps/ada/ada.gyp \ + deps/simdutf/simdutf.gyp deps/ada/ada.gyp deps/nbytes/nbytes.gyp \ tools/v8_gypfiles/toolchain.gypi tools/v8_gypfiles/features.gypi \ tools/v8_gypfiles/inspector.gypi tools/v8_gypfiles/v8.gyp $(PYTHON) tools/gyp_node.py -f make diff --git a/deps/nbytes/nbytes.cpp b/deps/nbytes/nbytes.cpp new file mode 100644 index 00000000000000..d5ad70b08d9e73 --- /dev/null +++ b/deps/nbytes/nbytes.cpp @@ -0,0 +1,151 @@ +#include "nbytes.h" +#include +#include +#include + +namespace nbytes { + +// ============================================================================ +// Byte Swapping + +namespace { +// These are defined by or on some systems. +// To avoid warnings, undefine them before redefining them. +#ifdef BSWAP_2 +# undef BSWAP_2 +#endif +#ifdef BSWAP_4 +# undef BSWAP_4 +#endif +#ifdef BSWAP_8 +# undef BSWAP_8 +#endif + +#if defined(_MSC_VER) +#include +#define BSWAP_2(x) _byteswap_ushort(x) +#define BSWAP_4(x) _byteswap_ulong(x) +#define BSWAP_8(x) _byteswap_uint64(x) +#else +#define BSWAP_2(x) ((x) << 8) | ((x) >> 8) +#define BSWAP_4(x) \ + (((x) & 0xFF) << 24) | \ + (((x) & 0xFF00) << 8) | \ + (((x) >> 8) & 0xFF00) | \ + (((x) >> 24) & 0xFF) +#define BSWAP_8(x) \ + (((x) & 0xFF00000000000000ull) >> 56) | \ + (((x) & 0x00FF000000000000ull) >> 40) | \ + (((x) & 0x0000FF0000000000ull) >> 24) | \ + (((x) & 0x000000FF00000000ull) >> 8) | \ + (((x) & 0x00000000FF000000ull) << 8) | \ + (((x) & 0x0000000000FF0000ull) << 24) | \ + (((x) & 0x000000000000FF00ull) << 40) | \ + (((x) & 0x00000000000000FFull) << 56) +#endif +} // namespace + +bool SwapBytes16(void* data, size_t nbytes) { + if (nbytes % sizeof(uint16_t) != 0) return false; + +#if defined(_MSC_VER) + if (AlignUp(data, sizeof(uint16_t)) == data) { + // MSVC has no strict aliasing, and is able to highly optimize this case. + uint16_t* data16 = reinterpret_cast(data); + size_t len16 = nbytes / sizeof(uint16_t); + for (size_t i = 0; i < len16; i++) { + data16[i] = BSWAP_2(data16[i]); + } + return; + } +#endif + + uint16_t temp; + uint8_t* ptr = reinterpret_cast(data); + for (size_t i = 0; i < nbytes; i += sizeof(uint16_t)) { + memcpy(&temp, &ptr[i], sizeof(uint16_t)); + temp = BSWAP_2(temp); + memcpy(&ptr[i], &temp, sizeof(uint16_t)); + } + + return true; +} + +bool SwapBytes32(void* data, size_t nbytes) { + if (nbytes % sizeof(uint32_t) != 0) return false; + +#if defined(_MSC_VER) + // MSVC has no strict aliasing, and is able to highly optimize this case. + if (AlignUp(data, sizeof(uint32_t)) == data) { + uint32_t* data32 = reinterpret_cast(data); + size_t len32 = nbytes / sizeof(uint32_t); + for (size_t i = 0; i < len32; i++) { + data32[i] = BSWAP_4(data32[i]); + } + return; + } +#endif + + uint32_t temp = 0; + uint8_t* ptr = reinterpret_cast(data); + for (size_t i = 0; i < nbytes; i += sizeof(uint32_t)) { + memcpy(&temp, &ptr[i], sizeof(uint32_t)); + temp = BSWAP_4(temp); + memcpy(&ptr[i], &temp, sizeof(uint32_t)); + } + + return true; +} + +bool SwapBytes64(void* data, size_t nbytes) { + if (nbytes % sizeof(uint64_t) != 0) return false; + +#if defined(_MSC_VER) + if (AlignUp(data, sizeof(uint64_t)) == data) { + // MSVC has no strict aliasing, and is able to highly optimize this case. + uint64_t* data64 = reinterpret_cast(data); + size_t len64 = nbytes / sizeof(uint64_t); + for (size_t i = 0; i < len64; i++) { + data64[i] = BSWAP_8(data64[i]); + } + return; + } +#endif + + uint64_t temp = 0; + uint8_t* ptr = reinterpret_cast(data); + for (size_t i = 0; i < nbytes; i += sizeof(uint64_t)) { + memcpy(&temp, &ptr[i], sizeof(uint64_t)); + temp = BSWAP_8(temp); + memcpy(&ptr[i], &temp, sizeof(uint64_t)); + } + + return true; +} + +// ============================================================================ +// Base64 (legacy) + +// supports regular and URL-safe base64 +const int8_t unbase64_table[256] = + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -1, -1, -2, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + + + +} // namespace nbytes diff --git a/deps/nbytes/nbytes.gyp b/deps/nbytes/nbytes.gyp new file mode 100644 index 00000000000000..1eb91c20a456f5 --- /dev/null +++ b/deps/nbytes/nbytes.gyp @@ -0,0 +1,17 @@ +{ + 'variables': { + 'v8_enable_i18n_support%': 1, + 'nbytes_sources': [ 'nbytes.cpp' ], + }, + 'targets': [ + { + 'target_name': 'nbytes', + 'type': 'static_library', + 'include_dirs': ['.'], + 'direct_dependent_settings': { + 'include_dirs': ['.'], + }, + 'sources': [ '<@(nbytes_sources)' ] + }, + ] +} diff --git a/deps/nbytes/nbytes.h b/deps/nbytes/nbytes.h new file mode 100644 index 00000000000000..364549324edadd --- /dev/null +++ b/deps/nbytes/nbytes.h @@ -0,0 +1,163 @@ +#pragma once + +#include +#include +#include +#include + +namespace nbytes { + +// The nbytes (short for "node bytes") is a set of utility helpers for +// working with bytes that are extracted from Node.js' internals. The +// motivation for extracting these into a separate library is to make it +// easier for other projects to implement functionality that is compatible +// with Node.js' implementation of various byte manipulation functions. + +// Round up a to the next highest multiple of b. +template +constexpr T RoundUp(T a, T b) { + return a % b != 0 ? a + b - (a % b) : a; +} + +// Align ptr to an `alignment`-bytes boundary. +template +constexpr T* AlignUp(T* ptr, U alignment) { + return reinterpret_cast( + RoundUp(reinterpret_cast(ptr), alignment)); +} + +// ============================================================================ +// Byte Swapping + +// Swaps bytes in place. nbytes is the number of bytes to swap and must be a +// multiple of the word size (checked by function). +bool SwapBytes16(void* data, size_t nbytes); +bool SwapBytes32(void* data, size_t nbytes); +bool SwapBytes64(void* data, size_t nbytes); + +// ============================================================================ +// Base64 (legacy) + +#ifdef _MSC_VER +#pragma warning(push) +// MSVC C4003: not enough actual parameters for macro 'identifier' +#pragma warning(disable : 4003) +#endif + +extern const int8_t unbase64_table[256]; + +template +bool Base64DecodeGroupSlow(char* const dst, const size_t dstlen, + const TypeName* const src, const size_t srclen, + size_t* const i, size_t* const k) { + uint8_t hi; + uint8_t lo; +#define V(expr) \ + for (;;) { \ + const uint8_t c = static_cast(src[*i]); \ + lo = unbase64_table[c]; \ + *i += 1; \ + if (lo < 64) break; /* Legal character. */ \ + if (c == '=' || *i >= srclen) return false; /* Stop decoding. */ \ + } \ + expr; \ + if (*i >= srclen) return false; \ + if (*k >= dstlen) return false; \ + hi = lo; + V(/* Nothing. */); + V(dst[(*k)++] = ((hi & 0x3F) << 2) | ((lo & 0x30) >> 4)); + V(dst[(*k)++] = ((hi & 0x0F) << 4) | ((lo & 0x3C) >> 2)); + V(dst[(*k)++] = ((hi & 0x03) << 6) | ((lo & 0x3F) >> 0)); +#undef V + return true; // Continue decoding. +} + +enum class Base64Mode { + NORMAL, + URL +}; + +inline constexpr size_t Base64EncodedSize( + size_t size, + Base64Mode mode = Base64Mode::NORMAL) { + return mode == Base64Mode::NORMAL ? ((size + 2) / 3 * 4) + : static_cast(std::ceil( + static_cast(size * 4) / 3)); +} + +// Doesn't check for padding at the end. Can be 1-2 bytes over. +inline constexpr size_t Base64DecodedSizeFast(size_t size) { + // 1-byte input cannot be decoded + return size > 1 ? (size / 4) * 3 + (size % 4 + 1) / 2 : 0; +} + +inline uint32_t ReadUint32BE(const unsigned char* p) { + return static_cast(p[0] << 24U) | + static_cast(p[1] << 16U) | + static_cast(p[2] << 8U) | + static_cast(p[3]); +} + +template +size_t Base64DecodedSize(const TypeName* src, size_t size) { + // 1-byte input cannot be decoded + if (size < 2) + return 0; + + if (src[size - 1] == '=') { + size--; + if (src[size - 1] == '=') + size--; + } + return Base64DecodedSizeFast(size); +} + +template +size_t Base64DecodeFast(char* const dst, const size_t dstlen, + const TypeName* const src, const size_t srclen, + const size_t decoded_size) { + const size_t available = dstlen < decoded_size ? dstlen : decoded_size; + const size_t max_k = available / 3 * 3; + size_t max_i = srclen / 4 * 4; + size_t i = 0; + size_t k = 0; + while (i < max_i && k < max_k) { + const unsigned char txt[] = { + static_cast(unbase64_table[static_cast(src[i + 0])]), + static_cast(unbase64_table[static_cast(src[i + 1])]), + static_cast(unbase64_table[static_cast(src[i + 2])]), + static_cast(unbase64_table[static_cast(src[i + 3])]), + }; + + const uint32_t v = ReadUint32BE(txt); + // If MSB is set, input contains whitespace or is not valid base64. + if (v & 0x80808080) { + if (!Base64DecodeGroupSlow(dst, dstlen, src, srclen, &i, &k)) + return k; + max_i = i + (srclen - i) / 4 * 4; // Align max_i again. + } else { + dst[k + 0] = ((v >> 22) & 0xFC) | ((v >> 20) & 0x03); + dst[k + 1] = ((v >> 12) & 0xF0) | ((v >> 10) & 0x0F); + dst[k + 2] = ((v >> 2) & 0xC0) | ((v >> 0) & 0x3F); + i += 4; + k += 3; + } + } + if (i < srclen && k < dstlen) { + Base64DecodeGroupSlow(dst, dstlen, src, srclen, &i, &k); + } + return k; +} + +template +size_t Base64Decode(char* const dst, const size_t dstlen, + const TypeName* const src, const size_t srclen) { + const size_t decoded_size = Base64DecodedSize(src, srclen); + return Base64DecodeFast(dst, dstlen, src, srclen, decoded_size); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +} // namespace nbytes diff --git a/node.gyp b/node.gyp index 8ca56d52d73d60..4419bf71dde523 100644 --- a/node.gyp +++ b/node.gyp @@ -187,8 +187,6 @@ 'src/base_object.h', 'src/base_object-inl.h', 'src/base_object_types.h', - 'src/base64.h', - 'src/base64-inl.h', 'src/blob_serializer_deserializer.h', 'src/blob_serializer_deserializer-inl.h', 'src/callback_queue.h', @@ -845,6 +843,7 @@ 'deps/simdjson/simdjson.gyp:simdjson', 'deps/simdutf/simdutf.gyp:simdutf', 'deps/ada/ada.gyp:ada', + 'deps/nbytes/nbytes.gyp:nbytes', 'node_js2c#host', ], @@ -1120,6 +1119,7 @@ 'deps/sqlite/sqlite.gyp:sqlite', 'deps/uvwasi/uvwasi.gyp:uvwasi', 'deps/ada/ada.gyp:ada', + 'deps/nbytes/nbytes.gyp:nbytes', ], 'includes': [ 'node.gypi' @@ -1170,6 +1170,7 @@ 'deps/simdjson/simdjson.gyp:simdjson', 'deps/simdutf/simdutf.gyp:simdutf', 'deps/ada/ada.gyp:ada', + 'deps/nbytes/nbytes.gyp:nbytes', ], 'includes': [ @@ -1246,6 +1247,7 @@ 'deps/histogram/histogram.gyp:histogram', 'deps/sqlite/sqlite.gyp:sqlite', 'deps/ada/ada.gyp:ada', + 'deps/nbytes/nbytes.gyp:nbytes', ], 'includes': [ @@ -1361,6 +1363,7 @@ 'deps/histogram/histogram.gyp:histogram', 'deps/sqlite/sqlite.gyp:sqlite', 'deps/ada/ada.gyp:ada', + 'deps/nbytes/nbytes.gyp:nbytes', 'deps/simdjson/simdjson.gyp:simdjson', 'deps/simdutf/simdutf.gyp:simdutf', ], diff --git a/src/base64-inl.h b/src/base64-inl.h deleted file mode 100644 index 20a2f233ae9f35..00000000000000 --- a/src/base64-inl.h +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef SRC_BASE64_INL_H_ -#define SRC_BASE64_INL_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "base64.h" -#include "util.h" - -namespace node { - -extern const int8_t unbase64_table[256]; - - -inline static int8_t unbase64(uint8_t x) { - return unbase64_table[x]; -} - - -inline uint32_t ReadUint32BE(const unsigned char* p) { - return static_cast(p[0] << 24U) | - static_cast(p[1] << 16U) | - static_cast(p[2] << 8U) | - static_cast(p[3]); -} - -#ifdef _MSC_VER -#pragma warning(push) -// MSVC C4003: not enough actual parameters for macro 'identifier' -#pragma warning(disable : 4003) -#endif - -template -bool base64_decode_group_slow(char* const dst, const size_t dstlen, - const TypeName* const src, const size_t srclen, - size_t* const i, size_t* const k) { - uint8_t hi; - uint8_t lo; -#define V(expr) \ - for (;;) { \ - const uint8_t c = static_cast(src[*i]); \ - lo = unbase64(c); \ - *i += 1; \ - if (lo < 64) break; /* Legal character. */ \ - if (c == '=' || *i >= srclen) return false; /* Stop decoding. */ \ - } \ - expr; \ - if (*i >= srclen) return false; \ - if (*k >= dstlen) return false; \ - hi = lo; - V(/* Nothing. */); - V(dst[(*k)++] = ((hi & 0x3F) << 2) | ((lo & 0x30) >> 4)); - V(dst[(*k)++] = ((hi & 0x0F) << 4) | ((lo & 0x3C) >> 2)); - V(dst[(*k)++] = ((hi & 0x03) << 6) | ((lo & 0x3F) >> 0)); -#undef V - return true; // Continue decoding. -} - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -template -size_t base64_decode_fast(char* const dst, const size_t dstlen, - const TypeName* const src, const size_t srclen, - const size_t decoded_size) { - const size_t available = dstlen < decoded_size ? dstlen : decoded_size; - const size_t max_k = available / 3 * 3; - size_t max_i = srclen / 4 * 4; - size_t i = 0; - size_t k = 0; - while (i < max_i && k < max_k) { - const unsigned char txt[] = { - static_cast(unbase64(static_cast(src[i + 0]))), - static_cast(unbase64(static_cast(src[i + 1]))), - static_cast(unbase64(static_cast(src[i + 2]))), - static_cast(unbase64(static_cast(src[i + 3]))), - }; - - const uint32_t v = ReadUint32BE(txt); - // If MSB is set, input contains whitespace or is not valid base64. - if (v & 0x80808080) { - if (!base64_decode_group_slow(dst, dstlen, src, srclen, &i, &k)) - return k; - max_i = i + (srclen - i) / 4 * 4; // Align max_i again. - } else { - dst[k + 0] = ((v >> 22) & 0xFC) | ((v >> 20) & 0x03); - dst[k + 1] = ((v >> 12) & 0xF0) | ((v >> 10) & 0x0F); - dst[k + 2] = ((v >> 2) & 0xC0) | ((v >> 0) & 0x3F); - i += 4; - k += 3; - } - } - if (i < srclen && k < dstlen) { - base64_decode_group_slow(dst, dstlen, src, srclen, &i, &k); - } - return k; -} - - -template -size_t base64_decoded_size(const TypeName* src, size_t size) { - // 1-byte input cannot be decoded - if (size < 2) - return 0; - - if (src[size - 1] == '=') { - size--; - if (src[size - 1] == '=') - size--; - } - return base64_decoded_size_fast(size); -} - - -template -size_t base64_decode(char* const dst, const size_t dstlen, - const TypeName* const src, const size_t srclen) { - const size_t decoded_size = base64_decoded_size(src, srclen); - return base64_decode_fast(dst, dstlen, src, srclen, decoded_size); -} - -} // namespace node - -#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#endif // SRC_BASE64_INL_H_ diff --git a/src/base64.h b/src/base64.h deleted file mode 100644 index c59a8bb50637c8..00000000000000 --- a/src/base64.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef SRC_BASE64_H_ -#define SRC_BASE64_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "util.h" - -#include -#include -#include - -namespace node { -//// Base 64 //// - -enum class Base64Mode { - NORMAL, - URL -}; - -static inline constexpr size_t base64_encoded_size( - size_t size, - Base64Mode mode = Base64Mode::NORMAL) { - return mode == Base64Mode::NORMAL ? ((size + 2) / 3 * 4) - : static_cast(std::ceil( - static_cast(size * 4) / 3)); -} - -// Doesn't check for padding at the end. Can be 1-2 bytes over. -static inline constexpr size_t base64_decoded_size_fast(size_t size) { - // 1-byte input cannot be decoded - return size > 1 ? (size / 4) * 3 + (size % 4 + 1) / 2 : 0; -} - -inline uint32_t ReadUint32BE(const unsigned char* p); - -template -size_t base64_decoded_size(const TypeName* src, size_t size); - -template -size_t base64_decode(char* const dst, - const size_t dstlen, - const TypeName* const src, - const size_t srclen); -} // namespace node - - -#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#endif // SRC_BASE64_H_ diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 299802238732df..5ac43f3197aaae 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -22,7 +22,6 @@ #include "cares_wrap.h" #include "ada.h" #include "async_wrap-inl.h" -#include "base64-inl.h" #include "base_object-inl.h" #include "env-inl.h" #include "memory_tracker-inl.h" @@ -33,6 +32,7 @@ #include "util-inl.h" #include "uv.h" #include "v8.h" +#include "nbytes.h" #include #include @@ -591,11 +591,11 @@ int ParseSoaReply( return ARES_EBADRESP; } - const unsigned int serial = ReadUint32BE(ptr + 0 * 4); - const unsigned int refresh = ReadUint32BE(ptr + 1 * 4); - const unsigned int retry = ReadUint32BE(ptr + 2 * 4); - const unsigned int expire = ReadUint32BE(ptr + 3 * 4); - const unsigned int minttl = ReadUint32BE(ptr + 4 * 4); + const unsigned int serial = nbytes::ReadUint32BE(ptr + 0 * 4); + const unsigned int refresh = nbytes::ReadUint32BE(ptr + 1 * 4); + const unsigned int retry = nbytes::ReadUint32BE(ptr + 2 * 4); + const unsigned int expire = nbytes::ReadUint32BE(ptr + 3 * 4); + const unsigned int minttl = nbytes::ReadUint32BE(ptr + 4 * 4); Local soa_record = Object::New(env->isolate()); soa_record->Set(env->context(), @@ -1801,7 +1801,7 @@ void SetLocalAddress(const FunctionCallbackInfo& args) { // to 0 (any). if (uv_inet_pton(AF_INET, *ip0, &addr0) == 0) { - ares_set_local_ip4(channel->cares_channel(), ReadUint32BE(addr0)); + ares_set_local_ip4(channel->cares_channel(), nbytes::ReadUint32BE(addr0)); type0 = 4; } else if (uv_inet_pton(AF_INET6, *ip0, &addr0) == 0) { ares_set_local_ip6(channel->cares_channel(), addr0); @@ -1820,7 +1820,7 @@ void SetLocalAddress(const FunctionCallbackInfo& args) { THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv4 addresses."); return; } else { - ares_set_local_ip4(channel->cares_channel(), ReadUint32BE(addr1)); + ares_set_local_ip4(channel->cares_channel(), nbytes::ReadUint32BE(addr1)); } } else if (uv_inet_pton(AF_INET6, *ip1, &addr1) == 0) { if (type0 == 6) { diff --git a/src/inspector_socket.cc b/src/inspector_socket.cc index b6059b49410868..0c77ad38e5e993 100644 --- a/src/inspector_socket.cc +++ b/src/inspector_socket.cc @@ -1,8 +1,8 @@ #include "inspector_socket.h" #include "llhttp.h" -#include "base64.h" #include "simdutf.h" +#include "nbytes.h" #include "util-inl.h" #include "openssl/sha.h" // Sha-1 hash @@ -11,7 +11,7 @@ #include #include -#define ACCEPT_KEY_LENGTH base64_encoded_size(20) +#define ACCEPT_KEY_LENGTH nbytes::Base64EncodedSize(20) #define DUMP_READS 0 #define DUMP_WRITES 0 @@ -149,7 +149,7 @@ static void generate_accept_string(const std::string& client_key, std::string input(client_key + ws_magic); char hash[SHA_DIGEST_LENGTH]; - CHECK(ACCEPT_KEY_LENGTH >= base64_encoded_size(SHA_DIGEST_LENGTH) && + CHECK(ACCEPT_KEY_LENGTH >= nbytes::Base64EncodedSize(SHA_DIGEST_LENGTH) && "not enough space provided for base64 encode"); USE(SHA1(reinterpret_cast(input.data()), input.size(), diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 871324f687617a..e7ef9a7e2262db 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -35,6 +35,7 @@ #include "v8-fast-api-calls.h" #include "v8.h" +#include "nbytes.h" #include #include @@ -667,7 +668,7 @@ void Fill(const FunctionCallbackInfo& args) { str_length = str_obj->Length() * sizeof(uint16_t); node::TwoByteValue str(env->isolate(), args[1]); if constexpr (IsBigEndian()) - SwapBytes16(reinterpret_cast(&str[0]), str_length); + CHECK(nbytes::SwapBytes16(reinterpret_cast(&str[0]), str_length)); memcpy(ts_obj_data + start, *str, std::min(str_length, fill_length)); @@ -1145,7 +1146,7 @@ void Swap16(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]); SPREAD_BUFFER_ARG(args[0], ts_obj); - SwapBytes16(ts_obj_data, ts_obj_length); + CHECK(nbytes::SwapBytes16(ts_obj_data, ts_obj_length)); args.GetReturnValue().Set(args[0]); } @@ -1154,7 +1155,7 @@ void Swap32(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]); SPREAD_BUFFER_ARG(args[0], ts_obj); - SwapBytes32(ts_obj_data, ts_obj_length); + CHECK(nbytes::SwapBytes32(ts_obj_data, ts_obj_length)); args.GetReturnValue().Set(args[0]); } @@ -1163,7 +1164,7 @@ void Swap64(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); THROW_AND_RETURN_UNLESS_BUFFER(env, args[0]); SPREAD_BUFFER_ARG(args[0], ts_obj); - SwapBytes64(ts_obj_data, ts_obj_length); + CHECK(nbytes::SwapBytes64(ts_obj_data, ts_obj_length)); args.GetReturnValue().Set(args[0]); } diff --git a/src/node_http2.cc b/src/node_http2.cc index 56297ba2a3b909..50636d24763078 100644 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -13,6 +13,8 @@ #include "stream_base-inl.h" #include "util-inl.h" +#include "nbytes.h" + #include #include #include @@ -455,8 +457,8 @@ Origins::Origins( } // Make sure the start address is aligned appropriately for an nghttp2_nv*. - char* start = AlignUp(static_cast(bs_->Data()), - alignof(nghttp2_origin_entry)); + char* start = nbytes::AlignUp(static_cast(bs_->Data()), + alignof(nghttp2_origin_entry)); char* origin_contents = start + (count_ * sizeof(nghttp2_origin_entry)); nghttp2_origin_entry* const nva = reinterpret_cast(start); diff --git a/src/node_http_common-inl.h b/src/node_http_common-inl.h index e78a6fdd2f95bd..78037f7e92faa0 100644 --- a/src/node_http_common-inl.h +++ b/src/node_http_common-inl.h @@ -7,6 +7,7 @@ #include "env-inl.h" #include "v8.h" +#include "nbytes.h" #include namespace node { @@ -31,7 +32,7 @@ NgHeaders::NgHeaders(Environment* env, v8::Local headers) { count_ * sizeof(nv_t) + header_string_len); - char* start = AlignUp(buf_.out(), alignof(nv_t)); + char* start = nbytes::AlignUp(buf_.out(), alignof(nv_t)); char* header_contents = start + (count_ * sizeof(nv_t)); nv_t* const nva = reinterpret_cast(start); diff --git a/src/node_i18n.cc b/src/node_i18n.cc index 743ea8c872cef4..14c41660d1a36f 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -54,6 +54,7 @@ #include "util-inl.h" #include "v8.h" +#include "nbytes.h" #include #include #include @@ -113,7 +114,7 @@ MaybeLocal ToBufferEndian(Environment* env, MaybeStackBuffer* buf) { "Currently only one- or two-byte buffers are supported"); if constexpr (sizeof(T) > 1 && IsBigEndian()) { SPREAD_BUFFER_ARG(ret.ToLocalChecked(), retbuf); - SwapBytes16(retbuf_data, retbuf_length); + CHECK(nbytes::SwapBytes16(retbuf_data, retbuf_length)); } return ret; @@ -129,7 +130,7 @@ void CopySourceBuffer(MaybeStackBuffer* dest, char* dst = reinterpret_cast(**dest); memcpy(dst, data, length); if constexpr (IsBigEndian()) { - SwapBytes16(dst, length); + CHECK(nbytes::SwapBytes16(dst, length)); } } @@ -528,7 +529,7 @@ void ConverterObject::Decode(const FunctionCallbackInfo& args) { char* value = reinterpret_cast(output) + beginning; if constexpr (IsBigEndian()) { - SwapBytes16(value, length); + CHECK(nbytes::SwapBytes16(value, length)); } MaybeLocal encoded = diff --git a/src/node_sockaddr.cc b/src/node_sockaddr.cc index 99af64d7259d48..057cf3c056cfba 100644 --- a/src/node_sockaddr.cc +++ b/src/node_sockaddr.cc @@ -1,10 +1,10 @@ #include "node_sockaddr-inl.h" // NOLINT(build/include) #include "env-inl.h" -#include "base64-inl.h" #include "base_object-inl.h" #include "memory_tracker-inl.h" #include "node_errors.h" #include "uv.h" +#include "nbytes.h" #include #include @@ -308,7 +308,7 @@ bool in_network_ipv6_ipv4( return false; ptr += sizeof(mask); - uint32_t check = ReadUint32BE(ptr); + uint32_t check = nbytes::ReadUint32BE(ptr); return (check & m) == (htonl(net_in->sin_addr.s_addr) & m); } diff --git a/src/spawn_sync.cc b/src/spawn_sync.cc index d03803fe3abc5b..bbaf073ee69bdf 100644 --- a/src/spawn_sync.cc +++ b/src/spawn_sync.cc @@ -27,6 +27,7 @@ #include "string_bytes.h" #include "util-inl.h" +#include "nbytes.h" #include @@ -1069,7 +1070,7 @@ Maybe SyncProcessRunner::CopyJsStringArray(Local js_value, Maybe maybe_size = StringBytes::StorageSize(isolate, value, UTF8); if (maybe_size.IsNothing()) return Nothing(); data_size += maybe_size.FromJust() + 1; - data_size = RoundUp(data_size, sizeof(void*)); + data_size = nbytes::RoundUp(data_size, sizeof(void*)); } buffer = new char[list_size + data_size]; @@ -1086,7 +1087,7 @@ Maybe SyncProcessRunner::CopyJsStringArray(Local js_value, value, UTF8); buffer[data_offset++] = '\0'; - data_offset = RoundUp(data_offset, sizeof(void*)); + data_offset = nbytes::RoundUp(data_offset, sizeof(void*)); } list[length] = nullptr; diff --git a/src/string_bytes.cc b/src/string_bytes.cc index 94e6079023284d..d1355de1093692 100644 --- a/src/string_bytes.cc +++ b/src/string_bytes.cc @@ -21,12 +21,12 @@ #include "string_bytes.h" -#include "base64-inl.h" #include "env-inl.h" #include "node_buffer.h" #include "node_errors.h" #include "simdutf.h" #include "util.h" +#include "nbytes.h" #include #include // memcpy @@ -200,27 +200,6 @@ MaybeLocal ExternTwoByteString::NewSimpleFromCopy(Isolate* isolate, } // anonymous namespace -// supports regular and URL-safe base64 -const int8_t unbase64_table[256] = - { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, -1, -1, -2, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, - -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, - -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 - }; - - static const int8_t unhex_table[256] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -270,7 +249,7 @@ size_t StringBytes::WriteUCS2( return 0; } - uint16_t* const aligned_dst = AlignUp(dst, sizeof(*dst)); + uint16_t* const aligned_dst = nbytes::AlignUp(dst, sizeof(*dst)); size_t nchars; if (aligned_dst == dst) { nchars = str->Write(isolate, dst, 0, max_chars, flags); @@ -339,7 +318,7 @@ size_t StringBytes::Write(Isolate* isolate, // the Buffer, so we need to reorder on BE platforms. See // https://nodejs.org/api/buffer.html regarding Node's "ucs2" // encoding specification - if constexpr (IsBigEndian()) SwapBytes16(buf, nbytes); + if constexpr (IsBigEndian()) CHECK(nbytes::SwapBytes16(buf, nbytes)); break; } @@ -356,7 +335,7 @@ size_t StringBytes::Write(Isolate* isolate, // The input does not follow the WHATWG forgiving-base64 specification // adapted for base64url // https://infra.spec.whatwg.org/#forgiving-base64-decode - nbytes = base64_decode(buf, buflen, ext->data(), ext->length()); + nbytes = nbytes::Base64Decode(buf, buflen, ext->data(), ext->length()); } } else if (str->IsOneByte()) { MaybeStackBuffer stack_buf(str->Length()); @@ -378,7 +357,7 @@ size_t StringBytes::Write(Isolate* isolate, // The input does not follow the WHATWG forgiving-base64 specification // (adapted for base64url with + and / replaced by - and _). // https://infra.spec.whatwg.org/#forgiving-base64-decode - nbytes = base64_decode(buf, buflen, *stack_buf, stack_buf.length()); + nbytes = nbytes::Base64Decode(buf, buflen, *stack_buf, stack_buf.length()); } } else { String::Value value(isolate, str); @@ -395,7 +374,7 @@ size_t StringBytes::Write(Isolate* isolate, // The input does not follow the WHATWG forgiving-base64 specification // (adapted for base64url with + and / replaced by - and _). // https://infra.spec.whatwg.org/#forgiving-base64-decode - nbytes = base64_decode(buf, buflen, *value, value.length()); + nbytes = nbytes::Base64Decode(buf, buflen, *value, value.length()); } } break; @@ -411,7 +390,7 @@ size_t StringBytes::Write(Isolate* isolate, } else { // The input does not follow the WHATWG forgiving-base64 specification // https://infra.spec.whatwg.org/#forgiving-base64-decode - nbytes = base64_decode(buf, buflen, ext->data(), ext->length()); + nbytes = nbytes::Base64Decode(buf, buflen, ext->data(), ext->length()); } } else if (str->IsOneByte()) { MaybeStackBuffer stack_buf(str->Length()); @@ -432,7 +411,7 @@ size_t StringBytes::Write(Isolate* isolate, // The input does not follow the WHATWG forgiving-base64 specification // (adapted for base64url with + and / replaced by - and _). // https://infra.spec.whatwg.org/#forgiving-base64-decode - nbytes = base64_decode(buf, buflen, *stack_buf, stack_buf.length()); + nbytes = nbytes::Base64Decode(buf, buflen, *stack_buf, stack_buf.length()); } } else { String::Value value(isolate, str); @@ -447,7 +426,7 @@ size_t StringBytes::Write(Isolate* isolate, } else { // The input does not follow the WHATWG base64 specification // https://infra.spec.whatwg.org/#forgiving-base64-decode - nbytes = base64_decode(buf, buflen, *value, value.length()); + nbytes = nbytes::Base64Decode(buf, buflen, *value, value.length()); } } break; @@ -810,7 +789,7 @@ MaybeLocal StringBytes::Encode(Isolate* isolate, } size_t nbytes = buflen * sizeof(uint16_t); memcpy(dst, buf, nbytes); - SwapBytes16(reinterpret_cast(dst), nbytes); + CHECK(nbytes::SwapBytes16(reinterpret_cast(dst), nbytes)); return ExternTwoByteString::New(isolate, dst, buflen, error); } else { return ExternTwoByteString::NewFromCopy(isolate, buf, buflen, error); diff --git a/src/util-inl.h b/src/util-inl.h index 463f982c91c71b..88d0cfee7f257f 100644 --- a/src/util-inl.h +++ b/src/util-inl.h @@ -30,41 +30,6 @@ #include "node_revert.h" #include "util.h" -// These are defined by or on some systems. -// To avoid warnings, undefine them before redefining them. -#ifdef BSWAP_2 -# undef BSWAP_2 -#endif -#ifdef BSWAP_4 -# undef BSWAP_4 -#endif -#ifdef BSWAP_8 -# undef BSWAP_8 -#endif - -#if defined(_MSC_VER) -#include -#define BSWAP_2(x) _byteswap_ushort(x) -#define BSWAP_4(x) _byteswap_ulong(x) -#define BSWAP_8(x) _byteswap_uint64(x) -#else -#define BSWAP_2(x) ((x) << 8) | ((x) >> 8) -#define BSWAP_4(x) \ - (((x) & 0xFF) << 24) | \ - (((x) & 0xFF00) << 8) | \ - (((x) >> 8) & 0xFF00) | \ - (((x) >> 24) & 0xFF) -#define BSWAP_8(x) \ - (((x) & 0xFF00000000000000ull) >> 56) | \ - (((x) & 0x00FF000000000000ull) >> 40) | \ - (((x) & 0x0000FF0000000000ull) >> 24) | \ - (((x) & 0x000000FF00000000ull) >> 8) | \ - (((x) & 0x00000000FF000000ull) << 8) | \ - (((x) & 0x0000000000FF0000ull) << 24) | \ - (((x) & 0x000000000000FF00ull) << 40) | \ - (((x) & 0x00000000000000FFull) << 56) -#endif - #define CHAR_TEST(bits, name, expr) \ template \ bool name(const T ch) { \ @@ -214,75 +179,6 @@ inline v8::Local OneByteString(v8::Isolate* isolate, .ToLocalChecked(); } -void SwapBytes16(char* data, size_t nbytes) { - CHECK_EQ(nbytes % 2, 0); - -#if defined(_MSC_VER) - if (AlignUp(data, sizeof(uint16_t)) == data) { - // MSVC has no strict aliasing, and is able to highly optimize this case. - uint16_t* data16 = reinterpret_cast(data); - size_t len16 = nbytes / sizeof(*data16); - for (size_t i = 0; i < len16; i++) { - data16[i] = BSWAP_2(data16[i]); - } - return; - } -#endif - - uint16_t temp; - for (size_t i = 0; i < nbytes; i += sizeof(temp)) { - memcpy(&temp, &data[i], sizeof(temp)); - temp = BSWAP_2(temp); - memcpy(&data[i], &temp, sizeof(temp)); - } -} - -void SwapBytes32(char* data, size_t nbytes) { - CHECK_EQ(nbytes % 4, 0); - -#if defined(_MSC_VER) - // MSVC has no strict aliasing, and is able to highly optimize this case. - if (AlignUp(data, sizeof(uint32_t)) == data) { - uint32_t* data32 = reinterpret_cast(data); - size_t len32 = nbytes / sizeof(*data32); - for (size_t i = 0; i < len32; i++) { - data32[i] = BSWAP_4(data32[i]); - } - return; - } -#endif - - uint32_t temp; - for (size_t i = 0; i < nbytes; i += sizeof(temp)) { - memcpy(&temp, &data[i], sizeof(temp)); - temp = BSWAP_4(temp); - memcpy(&data[i], &temp, sizeof(temp)); - } -} - -void SwapBytes64(char* data, size_t nbytes) { - CHECK_EQ(nbytes % 8, 0); - -#if defined(_MSC_VER) - if (AlignUp(data, sizeof(uint64_t)) == data) { - // MSVC has no strict aliasing, and is able to highly optimize this case. - uint64_t* data64 = reinterpret_cast(data); - size_t len64 = nbytes / sizeof(*data64); - for (size_t i = 0; i < len64; i++) { - data64[i] = BSWAP_8(data64[i]); - } - return; - } -#endif - - uint64_t temp; - for (size_t i = 0; i < nbytes; i += sizeof(temp)) { - memcpy(&temp, &data[i], sizeof(temp)); - temp = BSWAP_8(temp); - memcpy(&data[i], &temp, sizeof(temp)); - } -} - char ToLower(char c) { return std::tolower(c, std::locale::classic()); } diff --git a/src/util.h b/src/util.h index a0cee453ee43a7..d3ad830e162a36 100644 --- a/src/util.h +++ b/src/util.h @@ -358,14 +358,6 @@ inline v8::Local FIXED_ONE_BYTE_STRING( return OneByteString(isolate, arr.data(), N - 1); } - - -// Swaps bytes in place. nbytes is the number of bytes to swap and must be a -// multiple of the word size (checked by function). -inline void SwapBytes16(char* data, size_t nbytes); -inline void SwapBytes32(char* data, size_t nbytes); -inline void SwapBytes64(char* data, size_t nbytes); - // tolower() is locale-sensitive. Use ToLower() instead. inline char ToLower(char c); inline std::string ToLower(const std::string& in); @@ -794,19 +786,6 @@ constexpr inline bool IsBigEndian() { static_assert(IsLittleEndian() || IsBigEndian(), "Node.js does not support mixed-endian systems"); -// Round up a to the next highest multiple of b. -template -constexpr T RoundUp(T a, T b) { - return a % b != 0 ? a + b - (a % b) : a; -} - -// Align ptr to an `alignment`-bytes boundary. -template -constexpr T* AlignUp(T* ptr, U alignment) { - return reinterpret_cast( - RoundUp(reinterpret_cast(ptr), alignment)); -} - class SlicedArguments : public MaybeStackBuffer> { public: inline explicit SlicedArguments( diff --git a/test/cctest/test_base64.cc b/test/cctest/test_base64.cc index ce960a6fc8dd56..ac690d29c5c1cc 100644 --- a/test/cctest/test_base64.cc +++ b/test/cctest/test_base64.cc @@ -1,4 +1,5 @@ -#include "base64-inl.h" +#include "nbytes.h" +#include "util-inl.h" #include "simdutf.h" #include @@ -6,8 +7,6 @@ #include "gtest/gtest.h" -using node::base64_decode; - TEST(Base64Test, Encode) { auto test = [](const char* string, const char* base64_string) { const size_t len = strlen(base64_string); @@ -70,7 +69,7 @@ TEST(Base64Test, Decode) { const size_t len = strlen(string); char* const buffer = new char[len + 1]; buffer[len] = 0; - base64_decode(buffer, len, base64_string, strlen(base64_string)); + nbytes::Base64Decode(buffer, len, base64_string, strlen(base64_string)); EXPECT_STREQ(string, buffer); delete[] buffer; }; From 05e5cde9ec1069e638b1f3ef7e0ac403e593ddb9 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 18 Jun 2024 17:20:57 -0700 Subject: [PATCH 2/4] src, deps: move hex_encode/decode/forceAscii to nbytes --- deps/nbytes/nbytes.cpp | 100 ++++++++++++++++++++++++++++ deps/nbytes/nbytes.h | 81 ++++++++++++++++++++++- src/crypto/crypto_common.cc | 9 +-- src/quic/cid.cc | 9 +-- src/quic/tokens.cc | 9 +-- src/string_bytes.cc | 127 ++---------------------------------- src/string_bytes.h | 7 -- 7 files changed, 197 insertions(+), 145 deletions(-) diff --git a/deps/nbytes/nbytes.cpp b/deps/nbytes/nbytes.cpp index d5ad70b08d9e73..565e31646395db 100644 --- a/deps/nbytes/nbytes.cpp +++ b/deps/nbytes/nbytes.cpp @@ -146,6 +146,106 @@ const int8_t unbase64_table[256] = -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; +// ============================================================================ +// Hex + +const int8_t unhex_table[256] = + { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 + }; + +size_t HexEncode( + const char* src, + size_t slen, + char* dst, + size_t dlen) { + // We know how much we'll write, just make sure that there's space. + NBYTES_ASSERT_TRUE( + dlen >= MultiplyWithOverflowCheck(slen, 2u) && + "not enough space provided for hex encode"); + + dlen = slen * 2; + for (size_t i = 0, k = 0; k < dlen; i += 1, k += 2) { + static const char hex[] = "0123456789abcdef"; + uint8_t val = static_cast(src[i]); + dst[k + 0] = hex[val >> 4]; + dst[k + 1] = hex[val & 15]; + } + + return dlen; +} + +std::string HexEncode(const char* src, size_t slen) { + size_t dlen = slen * 2; + std::string dst(dlen, '\0'); + HexEncode(src, slen, dst.data(), dlen); + return dst; +} + +// ============================================================================ + +void ForceAsciiSlow(const char* src, char* dst, size_t len) { + for (size_t i = 0; i < len; ++i) { + dst[i] = src[i] & 0x7f; + } +} + +void ForceAscii(const char* src, char* dst, size_t len) { + if (len < 16) { + ForceAsciiSlow(src, dst, len); + return; + } + + const unsigned bytes_per_word = sizeof(uintptr_t); + const unsigned align_mask = bytes_per_word - 1; + const unsigned src_unalign = reinterpret_cast(src) & align_mask; + const unsigned dst_unalign = reinterpret_cast(dst) & align_mask; + + if (src_unalign > 0) { + if (src_unalign == dst_unalign) { + const unsigned unalign = bytes_per_word - src_unalign; + ForceAsciiSlow(src, dst, unalign); + src += unalign; + dst += unalign; + len -= src_unalign; + } else { + ForceAsciiSlow(src, dst, len); + return; + } + } + +#if defined(_WIN64) || defined(_LP64) + const uintptr_t mask = ~0x8080808080808080ll; +#else + const uintptr_t mask = ~0x80808080l; +#endif + const uintptr_t* srcw = reinterpret_cast(src); + uintptr_t* dstw = reinterpret_cast(dst); + + for (size_t i = 0, n = len / bytes_per_word; i < n; ++i) { + dstw[i] = srcw[i] & mask; + } + + const unsigned remainder = len & align_mask; + if (remainder > 0) { + const size_t offset = len - remainder; + ForceAsciiSlow(src + offset, dst + offset, remainder); + } +} } // namespace nbytes diff --git a/deps/nbytes/nbytes.h b/deps/nbytes/nbytes.h index 364549324edadd..acb0f1bedf1549 100644 --- a/deps/nbytes/nbytes.h +++ b/deps/nbytes/nbytes.h @@ -1,12 +1,44 @@ #pragma once -#include -#include -#include +#include #include +#include +#include namespace nbytes { +#if NBYTES_DEVELOPMENT_CHECKS +#define NBYTES_STR(x) #x +#define NBYTES_REQUIRE(EXPR) \ + { \ + if (!(EXPR) { abort(); }) } + +#define NBYTES_FAIL(MESSAGE) \ + do { \ + std::cerr << "FAIL: " << (MESSAGE) << std::endl; \ + abort(); \ + } while (0); +#define NBYTES_ASSERT_EQUAL(LHS, RHS, MESSAGE) \ + do { \ + if (LHS != RHS) { \ + std::cerr << "Mismatch: '" << LHS << "' - '" << RHS << "'" << std::endl; \ + NBYTES_FAIL(MESSAGE); \ + } \ + } while (0); +#define NBYTES_ASSERT_TRUE(COND) \ + do { \ + if (!(COND)) { \ + std::cerr << "Assert at line " << __LINE__ << " of file " << __FILE__ \ + << std::endl; \ + NBYTES_FAIL(NBYTES_STR(COND)); \ + } \ + } while (0); +#else +#define NBYTES_FAIL(MESSAGE) +#define NBYTES_ASSERT_EQUAL(LHS, RHS, MESSAGE) +#define NBYTES_ASSERT_TRUE(COND) +#endif + // The nbytes (short for "node bytes") is a set of utility helpers for // working with bytes that are extracted from Node.js' internals. The // motivation for extracting these into a separate library is to make it @@ -26,6 +58,19 @@ constexpr T* AlignUp(T* ptr, U alignment) { RoundUp(reinterpret_cast(ptr), alignment)); } +template +inline T MultiplyWithOverflowCheck(T a, T b) { + auto ret = a * b; + if (a != 0) { + NBYTES_ASSERT_TRUE(b == ret / a); + } + + return ret; +} + +void ForceAsciiSlow(const char* src, char* dst, size_t len); +void ForceAscii(const char* src, char* dst, size_t len); + // ============================================================================ // Byte Swapping @@ -160,4 +205,34 @@ size_t Base64Decode(char* const dst, const size_t dstlen, #pragma warning(pop) #endif +// ============================================================================ +// Hex (legacy) + +extern const int8_t unhex_table[256]; + +template +static size_t HexDecode(char* buf, + size_t len, + const TypeName* src, + const size_t srcLen) { + size_t i; + for (i = 0; i < len && i * 2 + 1 < srcLen; ++i) { + unsigned a = unhex_table[static_cast(src[i * 2 + 0])]; + unsigned b = unhex_table[static_cast(src[i * 2 + 1])]; + if (!~a || !~b) + return i; + buf[i] = (a << 4) | b; + } + + return i; +} + +size_t HexEncode( + const char* src, + size_t slen, + char* dst, + size_t dlen); + +std::string HexEncode(const char* src, size_t slen); + } // namespace nbytes diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index 962018583360a1..11e8836fbc450e 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -8,6 +8,7 @@ #include "node_internals.h" #include "string_bytes.h" #include "v8.h" +#include "nbytes.h" #include #include @@ -90,10 +91,10 @@ void LogSecret( } std::string line = name; - line += " " + StringBytes::hex_encode(reinterpret_cast(crandom), - kTlsClientRandomSize); - line += " " + StringBytes::hex_encode( - reinterpret_cast(secret), secretlen); + line += " " + nbytes::HexEncode(reinterpret_cast(crandom), + kTlsClientRandomSize); + line += " " + nbytes::HexEncode(reinterpret_cast(secret), + secretlen); keylog_cb(ssl.get(), line.c_str()); } diff --git a/src/quic/cid.cc b/src/quic/cid.cc index 7c30d0d542aeaf..a017502deeb2ac 100644 --- a/src/quic/cid.cc +++ b/src/quic/cid.cc @@ -5,6 +5,7 @@ #include #include #include "quic/defs.h" +#include "nbytes.h" namespace node { namespace quic { @@ -72,10 +73,10 @@ size_t CID::length() const { std::string CID::ToString() const { char dest[kMaxLength * 2]; size_t written = - StringBytes::hex_encode(reinterpret_cast(ptr_->data), - ptr_->datalen, - dest, - arraysize(dest)); + nbytes::HexEncode(reinterpret_cast(ptr_->data), + ptr_->datalen, + dest, + arraysize(dest)); return std::string(dest, written); } diff --git a/src/quic/tokens.cc b/src/quic/tokens.cc index 9ffdab2575cb42..045c5f9a050d05 100644 --- a/src/quic/tokens.cc +++ b/src/quic/tokens.cc @@ -7,6 +7,7 @@ #include #include #include +#include "nbytes.h" namespace node { namespace quic { @@ -49,7 +50,7 @@ TokenSecret::operator const char*() const { std::string TokenSecret::ToString() const { char dest[QUIC_TOKENSECRET_LEN * 2]; - size_t written = StringBytes::hex_encode( + size_t written = nbytes::HexEncode( *this, QUIC_TOKENSECRET_LEN, dest, arraysize(dest)); DCHECK_EQ(written, arraysize(dest)); return std::string(dest, written); @@ -117,7 +118,7 @@ std::string StatelessResetToken::ToString() const { if (ptr_ == nullptr) return std::string(); char dest[kStatelessTokenLen * 2]; size_t written = - StringBytes::hex_encode(*this, kStatelessTokenLen, dest, arraysize(dest)); + nbytes::HexEncode(*this, kStatelessTokenLen, dest, arraysize(dest)); DCHECK_EQ(written, arraysize(dest)); return std::string(dest, written); } @@ -230,7 +231,7 @@ std::string RetryToken::ToString() const { if (ptr_.base == nullptr) return std::string(); MaybeStackBuffer dest(ptr_.len * 2); size_t written = - StringBytes::hex_encode(*this, ptr_.len, dest.out(), dest.length()); + nbytes::HexEncode(*this, ptr_.len, dest.out(), dest.length()); DCHECK_EQ(written, dest.length()); return std::string(dest.out(), written); } @@ -289,7 +290,7 @@ std::string RegularToken::ToString() const { if (ptr_.base == nullptr) return std::string(); MaybeStackBuffer dest(ptr_.len * 2); size_t written = - StringBytes::hex_encode(*this, ptr_.len, dest.out(), dest.length()); + nbytes::HexEncode(*this, ptr_.len, dest.out(), dest.length()); DCHECK_EQ(written, dest.length()); return std::string(dest.out(), written); } diff --git a/src/string_bytes.cc b/src/string_bytes.cc index d1355de1093692..dda14fe49e7634 100644 --- a/src/string_bytes.cc +++ b/src/string_bytes.cc @@ -200,46 +200,6 @@ MaybeLocal ExternTwoByteString::NewSimpleFromCopy(Isolate* isolate, } // anonymous namespace -static const int8_t unhex_table[256] = - { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 - }; - -static inline unsigned unhex(uint8_t x) { - return unhex_table[x]; -} - -template -static size_t hex_decode(char* buf, - size_t len, - const TypeName* src, - const size_t srcLen) { - size_t i; - for (i = 0; i < len && i * 2 + 1 < srcLen; ++i) { - unsigned a = unhex(static_cast(src[i * 2 + 0])); - unsigned b = unhex(static_cast(src[i * 2 + 1])); - if (!~a || !~b) - return i; - buf[i] = (a << 4) | b; - } - - return i; -} - size_t StringBytes::WriteUCS2( Isolate* isolate, char* buf, size_t buflen, Local str, int flags) { uint16_t* const dst = reinterpret_cast(buf); @@ -434,10 +394,10 @@ size_t StringBytes::Write(Isolate* isolate, case HEX: if (str->IsExternalOneByte()) { auto ext = str->GetExternalOneByteStringResource(); - nbytes = hex_decode(buf, buflen, ext->data(), ext->length()); + nbytes = nbytes::HexDecode(buf, buflen, ext->data(), ext->length()); } else { String::Value value(isolate, str); - nbytes = hex_decode(buf, buflen, *value, value.length()); + nbytes = nbytes::HexDecode(buf, buflen, *value, value.length()); } break; @@ -547,85 +507,6 @@ Maybe StringBytes::Size(Isolate* isolate, UNREACHABLE(); } -static void force_ascii_slow(const char* src, char* dst, size_t len) { - for (size_t i = 0; i < len; ++i) { - dst[i] = src[i] & 0x7f; - } -} - - -static void force_ascii(const char* src, char* dst, size_t len) { - if (len < 16) { - force_ascii_slow(src, dst, len); - return; - } - - const unsigned bytes_per_word = sizeof(uintptr_t); - const unsigned align_mask = bytes_per_word - 1; - const unsigned src_unalign = reinterpret_cast(src) & align_mask; - const unsigned dst_unalign = reinterpret_cast(dst) & align_mask; - - if (src_unalign > 0) { - if (src_unalign == dst_unalign) { - const unsigned unalign = bytes_per_word - src_unalign; - force_ascii_slow(src, dst, unalign); - src += unalign; - dst += unalign; - len -= src_unalign; - } else { - force_ascii_slow(src, dst, len); - return; - } - } - -#if defined(_WIN64) || defined(_LP64) - const uintptr_t mask = ~0x8080808080808080ll; -#else - const uintptr_t mask = ~0x80808080l; -#endif - - const uintptr_t* srcw = reinterpret_cast(src); - uintptr_t* dstw = reinterpret_cast(dst); - - for (size_t i = 0, n = len / bytes_per_word; i < n; ++i) { - dstw[i] = srcw[i] & mask; - } - - const unsigned remainder = len & align_mask; - if (remainder > 0) { - const size_t offset = len - remainder; - force_ascii_slow(src + offset, dst + offset, remainder); - } -} - - -size_t StringBytes::hex_encode( - const char* src, - size_t slen, - char* dst, - size_t dlen) { - // We know how much we'll write, just make sure that there's space. - CHECK(dlen >= MultiplyWithOverflowCheck(slen, 2u) && - "not enough space provided for hex encode"); - - dlen = slen * 2; - for (size_t i = 0, k = 0; k < dlen; i += 1, k += 2) { - static const char hex[] = "0123456789abcdef"; - uint8_t val = static_cast(src[i]); - dst[k + 0] = hex[val >> 4]; - dst[k + 1] = hex[val & 15]; - } - - return dlen; -} - -std::string StringBytes::hex_encode(const char* src, size_t slen) { - size_t dlen = slen * 2; - std::string dst(dlen, '\0'); - hex_encode(src, slen, dst.data(), dlen); - return dst; -} - #define CHECK_BUFLEN_IN_RANGE(len) \ do { \ if ((len) > Buffer::kMaxLength) { \ @@ -667,7 +548,7 @@ MaybeLocal StringBytes::Encode(Isolate* isolate, *error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate); return MaybeLocal(); } - force_ascii(buf, out, buflen); + nbytes::ForceAscii(buf, out, buflen); return ExternOneByteString::New(isolate, out, buflen, error); } else { return ExternOneByteString::NewFromCopy(isolate, buf, buflen, error); @@ -726,7 +607,7 @@ MaybeLocal StringBytes::Encode(Isolate* isolate, *error = node::ERR_MEMORY_ALLOCATION_FAILED(isolate); return MaybeLocal(); } - size_t written = hex_encode(buf, buflen, dst, dlen); + size_t written = nbytes::HexEncode(buf, buflen, dst, dlen); CHECK_EQ(written, dlen); return ExternOneByteString::New(isolate, dst, dlen, error); diff --git a/src/string_bytes.h b/src/string_bytes.h index ad1f15b05704c8..fde5070ffb66a7 100644 --- a/src/string_bytes.h +++ b/src/string_bytes.h @@ -98,13 +98,6 @@ class StringBytes { enum encoding encoding, v8::Local* error); - static size_t hex_encode(const char* src, - size_t slen, - char* dst, - size_t dlen); - - static std::string hex_encode(const char* src, size_t slen); - private: static size_t WriteUCS2(v8::Isolate* isolate, char* buf, From 142021a722257dacaffba03e484b2b9247012229 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 18 Jun 2024 17:28:29 -0700 Subject: [PATCH 3/4] src: update linting/formatting --- src/cares_wrap.cc | 5 +++-- src/crypto/crypto_common.cc | 6 +++--- src/inspector_socket.cc | 2 +- src/node_buffer.cc | 4 ++-- src/node_http_common-inl.h | 2 +- src/node_i18n.cc | 2 +- src/node_sockaddr.cc | 7 ++++--- src/quic/cid.cc | 11 +++++------ src/quic/tokens.cc | 4 ++-- src/spawn_sync.cc | 3 +-- src/string_bytes.cc | 14 +++++++++----- test/cctest/test_base64.cc | 2 +- 12 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 5ac43f3197aaae..26877f3ddd8f69 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -25,6 +25,7 @@ #include "base_object-inl.h" #include "env-inl.h" #include "memory_tracker-inl.h" +#include "nbytes.h" #include "node.h" #include "node_errors.h" #include "node_external_reference.h" @@ -32,7 +33,6 @@ #include "util-inl.h" #include "uv.h" #include "v8.h" -#include "nbytes.h" #include #include @@ -1820,7 +1820,8 @@ void SetLocalAddress(const FunctionCallbackInfo& args) { THROW_ERR_INVALID_ARG_VALUE(env, "Cannot specify two IPv4 addresses."); return; } else { - ares_set_local_ip4(channel->cares_channel(), nbytes::ReadUint32BE(addr1)); + ares_set_local_ip4(channel->cares_channel(), + nbytes::ReadUint32BE(addr1)); } } else if (uv_inet_pton(AF_INET6, *ip1, &addr1) == 0) { if (type0 == 6) { diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index 11e8836fbc450e..4da42cf4147bf6 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -2,13 +2,13 @@ #include "base_object-inl.h" #include "env-inl.h" #include "memory_tracker-inl.h" +#include "nbytes.h" #include "node.h" #include "node_buffer.h" #include "node_crypto.h" #include "node_internals.h" #include "string_bytes.h" #include "v8.h" -#include "nbytes.h" #include #include @@ -93,8 +93,8 @@ void LogSecret( std::string line = name; line += " " + nbytes::HexEncode(reinterpret_cast(crandom), kTlsClientRandomSize); - line += " " + nbytes::HexEncode(reinterpret_cast(secret), - secretlen); + line += + " " + nbytes::HexEncode(reinterpret_cast(secret), secretlen); keylog_cb(ssl.get(), line.c_str()); } diff --git a/src/inspector_socket.cc b/src/inspector_socket.cc index 0c77ad38e5e993..5246b9170a7a78 100644 --- a/src/inspector_socket.cc +++ b/src/inspector_socket.cc @@ -1,8 +1,8 @@ #include "inspector_socket.h" #include "llhttp.h" -#include "simdutf.h" #include "nbytes.h" +#include "simdutf.h" #include "util-inl.h" #include "openssl/sha.h" // Sha-1 hash diff --git a/src/node_buffer.cc b/src/node_buffer.cc index e7ef9a7e2262db..53e4f2888fffdc 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -35,9 +35,9 @@ #include "v8-fast-api-calls.h" #include "v8.h" -#include "nbytes.h" -#include #include +#include +#include "nbytes.h" #define THROW_AND_RETURN_UNLESS_BUFFER(env, obj) \ THROW_AND_RETURN_IF_NOT_BUFFER(env, obj, "argument") \ diff --git a/src/node_http_common-inl.h b/src/node_http_common-inl.h index 78037f7e92faa0..dba1a5e051b3e0 100644 --- a/src/node_http_common-inl.h +++ b/src/node_http_common-inl.h @@ -7,8 +7,8 @@ #include "env-inl.h" #include "v8.h" -#include "nbytes.h" #include +#include "nbytes.h" namespace node { diff --git a/src/node_i18n.cc b/src/node_i18n.cc index 14c41660d1a36f..7a13f35d2f2bcb 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -54,7 +54,6 @@ #include "util-inl.h" #include "v8.h" -#include "nbytes.h" #include #include #include @@ -70,6 +69,7 @@ #include #include #include +#include "nbytes.h" #ifdef NODE_HAVE_SMALL_ICU /* if this is defined, we have a 'secondary' entry point. diff --git a/src/node_sockaddr.cc b/src/node_sockaddr.cc index 057cf3c056cfba..e1572187437f1b 100644 --- a/src/node_sockaddr.cc +++ b/src/node_sockaddr.cc @@ -1,10 +1,11 @@ -#include "node_sockaddr-inl.h" // NOLINT(build/include) -#include "env-inl.h" +#include "node_sockaddr.h" // NOLINT(build/include_inline) #include "base_object-inl.h" +#include "env-inl.h" #include "memory_tracker-inl.h" +#include "nbytes.h" #include "node_errors.h" +#include "node_sockaddr-inl.h" // NOLINT(build/include_inline) #include "uv.h" -#include "nbytes.h" #include #include diff --git a/src/quic/cid.cc b/src/quic/cid.cc index a017502deeb2ac..404b98c47d6c19 100644 --- a/src/quic/cid.cc +++ b/src/quic/cid.cc @@ -4,8 +4,8 @@ #include #include #include -#include "quic/defs.h" #include "nbytes.h" +#include "quic/defs.h" namespace node { namespace quic { @@ -72,11 +72,10 @@ size_t CID::length() const { std::string CID::ToString() const { char dest[kMaxLength * 2]; - size_t written = - nbytes::HexEncode(reinterpret_cast(ptr_->data), - ptr_->datalen, - dest, - arraysize(dest)); + size_t written = nbytes::HexEncode(reinterpret_cast(ptr_->data), + ptr_->datalen, + dest, + arraysize(dest)); return std::string(dest, written); } diff --git a/src/quic/tokens.cc b/src/quic/tokens.cc index 045c5f9a050d05..e2c03d49c1b32f 100644 --- a/src/quic/tokens.cc +++ b/src/quic/tokens.cc @@ -50,8 +50,8 @@ TokenSecret::operator const char*() const { std::string TokenSecret::ToString() const { char dest[QUIC_TOKENSECRET_LEN * 2]; - size_t written = nbytes::HexEncode( - *this, QUIC_TOKENSECRET_LEN, dest, arraysize(dest)); + size_t written = + nbytes::HexEncode(*this, QUIC_TOKENSECRET_LEN, dest, arraysize(dest)); DCHECK_EQ(written, arraysize(dest)); return std::string(dest, written); } diff --git a/src/spawn_sync.cc b/src/spawn_sync.cc index bbaf073ee69bdf..36570d069ad00b 100644 --- a/src/spawn_sync.cc +++ b/src/spawn_sync.cc @@ -27,9 +27,8 @@ #include "string_bytes.h" #include "util-inl.h" -#include "nbytes.h" #include - +#include "nbytes.h" namespace node { diff --git a/src/string_bytes.cc b/src/string_bytes.cc index dda14fe49e7634..3e2b29005a2012 100644 --- a/src/string_bytes.cc +++ b/src/string_bytes.cc @@ -22,11 +22,11 @@ #include "string_bytes.h" #include "env-inl.h" +#include "nbytes.h" #include "node_buffer.h" #include "node_errors.h" #include "simdutf.h" #include "util.h" -#include "nbytes.h" #include #include // memcpy @@ -295,7 +295,8 @@ size_t StringBytes::Write(Isolate* isolate, // The input does not follow the WHATWG forgiving-base64 specification // adapted for base64url // https://infra.spec.whatwg.org/#forgiving-base64-decode - nbytes = nbytes::Base64Decode(buf, buflen, ext->data(), ext->length()); + nbytes = + nbytes::Base64Decode(buf, buflen, ext->data(), ext->length()); } } else if (str->IsOneByte()) { MaybeStackBuffer stack_buf(str->Length()); @@ -317,7 +318,8 @@ size_t StringBytes::Write(Isolate* isolate, // The input does not follow the WHATWG forgiving-base64 specification // (adapted for base64url with + and / replaced by - and _). // https://infra.spec.whatwg.org/#forgiving-base64-decode - nbytes = nbytes::Base64Decode(buf, buflen, *stack_buf, stack_buf.length()); + nbytes = + nbytes::Base64Decode(buf, buflen, *stack_buf, stack_buf.length()); } } else { String::Value value(isolate, str); @@ -350,7 +352,8 @@ size_t StringBytes::Write(Isolate* isolate, } else { // The input does not follow the WHATWG forgiving-base64 specification // https://infra.spec.whatwg.org/#forgiving-base64-decode - nbytes = nbytes::Base64Decode(buf, buflen, ext->data(), ext->length()); + nbytes = + nbytes::Base64Decode(buf, buflen, ext->data(), ext->length()); } } else if (str->IsOneByte()) { MaybeStackBuffer stack_buf(str->Length()); @@ -371,7 +374,8 @@ size_t StringBytes::Write(Isolate* isolate, // The input does not follow the WHATWG forgiving-base64 specification // (adapted for base64url with + and / replaced by - and _). // https://infra.spec.whatwg.org/#forgiving-base64-decode - nbytes = nbytes::Base64Decode(buf, buflen, *stack_buf, stack_buf.length()); + nbytes = + nbytes::Base64Decode(buf, buflen, *stack_buf, stack_buf.length()); } } else { String::Value value(isolate, str); diff --git a/test/cctest/test_base64.cc b/test/cctest/test_base64.cc index ac690d29c5c1cc..4f1bc9173b6818 100644 --- a/test/cctest/test_base64.cc +++ b/test/cctest/test_base64.cc @@ -1,6 +1,6 @@ #include "nbytes.h" -#include "util-inl.h" #include "simdutf.h" +#include "util-inl.h" #include #include From 82011132cbf709fb53d6bf90b4823a85071d41d2 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 19 Jun 2024 07:10:27 -0700 Subject: [PATCH 4/4] src, deps: move string_search.h to nbytes, add version metadata --- deps/nbytes/README.md | 5 + deps/nbytes/nbytes.h | 623 +++++++++++++++++++++++++++++++++++++++++ node.gyp | 1 - src/node_buffer.cc | 80 +++--- src/node_metadata.cc | 2 + src/node_metadata.h | 1 + src/string_search.h | 638 ------------------------------------------ 7 files changed, 671 insertions(+), 679 deletions(-) create mode 100644 deps/nbytes/README.md delete mode 100644 src/string_search.h diff --git a/deps/nbytes/README.md b/deps/nbytes/README.md new file mode 100644 index 00000000000000..9ff412adb30560 --- /dev/null +++ b/deps/nbytes/README.md @@ -0,0 +1,5 @@ +# Node.js bytes (nbytes) library + +The `nbytes` library extracts certain Node.js specific byte manipulation +functions from the core of Node.js itself and makes them available for +use in other projects that need to emulate Node.js' behavior. diff --git a/deps/nbytes/nbytes.h b/deps/nbytes/nbytes.h index acb0f1bedf1549..51aa5d05727678 100644 --- a/deps/nbytes/nbytes.h +++ b/deps/nbytes/nbytes.h @@ -1,8 +1,10 @@ #pragma once +#include #include #include #include +#include #include namespace nbytes { @@ -58,6 +60,12 @@ constexpr T* AlignUp(T* ptr, U alignment) { RoundUp(reinterpret_cast(ptr), alignment)); } +template +inline T AlignDown(T value, U alignment) { + return reinterpret_cast( + (reinterpret_cast(value) & ~(alignment - 1))); +} + template inline T MultiplyWithOverflowCheck(T a, T b) { auto ret = a * b; @@ -235,4 +243,619 @@ size_t HexEncode( std::string HexEncode(const char* src, size_t slen); +// ============================================================================ +// StringSearch + +namespace stringsearch { + +template +class Vector { + public: + Vector(T* data, size_t length, bool isForward) + : start_(data), length_(length), is_forward_(isForward) { + CHECK(length > 0 && data != nullptr); + } + + // Returns the start of the memory range. + // For vector v this is NOT necessarily &v[0], see forward(). + const T* start() const { return start_; } + + // Returns the length of the vector, in characters. + size_t length() const { return length_; } + + // Returns true if the Vector is front-to-back, false if back-to-front. + // In the latter case, v[0] corresponds to the *end* of the memory range. + bool forward() const { return is_forward_; } + + // Access individual vector elements - checks bounds in debug mode. + T& operator[](size_t index) const { + NBYTES_ASSERT_TRUE(index < length_); + return start_[is_forward_ ? index : (length_ - index - 1)]; + } + + private: + T* start_; + size_t length_; + bool is_forward_; +}; + +//--------------------------------------------------------------------- +// String Search object. +//--------------------------------------------------------------------- + +// Class holding constants and methods that apply to all string search variants, +// independently of subject and pattern char size. +class StringSearchBase { + protected: + // Cap on the maximal shift in the Boyer-Moore implementation. By setting a + // limit, we can fix the size of tables. For a needle longer than this limit, + // search will not be optimal, since we only build tables for a suffix + // of the string, but it is a safe approximation. + static const int kBMMaxShift = 250; + + // Reduce alphabet to this size. + // One of the tables used by Boyer-Moore and Boyer-Moore-Horspool has size + // proportional to the input alphabet. We reduce the alphabet size by + // equating input characters modulo a smaller alphabet size. This gives + // a potentially less efficient searching, but is a safe approximation. + // For needles using only characters in the same Unicode 256-code point page, + // there is no search speed degradation. + static const int kLatin1AlphabetSize = 256; + static const int kUC16AlphabetSize = 256; + + // Bad-char shift table stored in the state. It's length is the alphabet size. + // For patterns below this length, the skip length of Boyer-Moore is too short + // to compensate for the algorithmic overhead compared to simple brute force. + static const int kBMMinPatternLength = 8; + + // Store for the BoyerMoore(Horspool) bad char shift table. + int bad_char_shift_table_[kUC16AlphabetSize]; + // Store for the BoyerMoore good suffix shift table. + int good_suffix_shift_table_[kBMMaxShift + 1]; + // Table used temporarily while building the BoyerMoore good suffix + // shift table. + int suffix_table_[kBMMaxShift + 1]; +}; + +template +class StringSearch : private StringSearchBase { + public: + typedef stringsearch::Vector Vector; + + explicit StringSearch(Vector pattern) + : pattern_(pattern), start_(0) { + if (pattern.length() >= kBMMaxShift) { + start_ = pattern.length() - kBMMaxShift; + } + + size_t pattern_length = pattern_.length(); + NBYTES_ASSERT_TRUE(pattern_length > 0); + if (pattern_length < kBMMinPatternLength) { + if (pattern_length == 1) { + strategy_ = SearchStrategy::kSingleChar; + return; + } + strategy_ = SearchStrategy::kLinear; + return; + } + strategy_ = SearchStrategy::kInitial; + } + + size_t Search(Vector subject, size_t index) { + switch (strategy_) { + case kBoyerMooreHorspool: + return BoyerMooreHorspoolSearch(subject, index); + case kBoyerMoore: + return BoyerMooreSearch(subject, index); + case kInitial: + return InitialSearch(subject, index); + case kLinear: + return LinearSearch(subject, index); + case kSingleChar: + return SingleCharSearch(subject, index); + } + __builtin_unreachable(); + } + + static inline int AlphabetSize() { + if (sizeof(Char) == 1) { + // Latin1 needle. + return kLatin1AlphabetSize; + } else { + // UC16 needle. + return kUC16AlphabetSize; + } + + static_assert(sizeof(Char) == sizeof(uint8_t) || + sizeof(Char) == sizeof(uint16_t), + "sizeof(Char) == sizeof(uint16_t) || sizeof(uint8_t)"); + } + + private: + typedef size_t (StringSearch::*SearchFunction)(Vector, size_t); + size_t SingleCharSearch(Vector subject, size_t start_index); + size_t LinearSearch(Vector subject, size_t start_index); + size_t InitialSearch(Vector subject, size_t start_index); + size_t BoyerMooreHorspoolSearch(Vector subject, size_t start_index); + size_t BoyerMooreSearch(Vector subject, size_t start_index); + + void PopulateBoyerMooreHorspoolTable(); + + void PopulateBoyerMooreTable(); + + static inline int CharOccurrence(int* bad_char_occurrence, + Char char_code) { + if (sizeof(Char) == 1) { + return bad_char_occurrence[static_cast(char_code)]; + } + // Both pattern and subject are UC16. Reduce character to equivalence class. + int equiv_class = char_code % kUC16AlphabetSize; + return bad_char_occurrence[equiv_class]; + } + + enum SearchStrategy { + kBoyerMooreHorspool, + kBoyerMoore, + kInitial, + kLinear, + kSingleChar, + }; + + // The pattern to search for. + Vector pattern_; + SearchStrategy strategy_; + // Cache value of Max(0, pattern_length() - kBMMaxShift) + size_t start_; +}; + +inline uint8_t GetHighestValueByte(uint16_t character) { + return std::max(static_cast(character & 0xFF), + static_cast(character >> 8)); +} + +inline uint8_t GetHighestValueByte(uint8_t character) { return character; } + +// Searches for a byte value in a memory buffer, back to front. +// Uses memrchr(3) on systems which support it, for speed. +// Falls back to a vanilla for loop on non-GNU systems such as Windows. +inline const void* MemrchrFill(const void* haystack, uint8_t needle, + size_t haystack_len) { +#ifdef _GNU_SOURCE + return memrchr(haystack, needle, haystack_len); +#else + const uint8_t* haystack8 = static_cast(haystack); + for (size_t i = haystack_len - 1; i != static_cast(-1); i--) { + if (haystack8[i] == needle) { + return haystack8 + i; + } + } + return nullptr; +#endif +} + +// Finds the first occurrence of *two-byte* character pattern[0] in the string +// `subject`. Does not check that the whole pattern matches. +template +inline size_t FindFirstCharacter(Vector pattern, + Vector subject, size_t index) { + const Char pattern_first_char = pattern[0]; + const size_t max_n = (subject.length() - pattern.length() + 1); + + // For speed, search for the more `rare` of the two bytes in pattern[0] + // using memchr / memrchr (which are much faster than a simple for loop). + const uint8_t search_byte = GetHighestValueByte(pattern_first_char); + size_t pos = index; + do { + const size_t bytes_to_search = (max_n - pos) * sizeof(Char); + const void* void_pos; + if (subject.forward()) { + // Assert that bytes_to_search won't overflow + NBYTES_ASSERT_TRUE(pos <= max_n); + NBYTES_ASSERT_TRUE(max_n - pos <= SIZE_MAX / sizeof(Char)); + void_pos = memchr(subject.start() + pos, search_byte, bytes_to_search); + } else { + NBYTES_ASSERT_TRUE(pos <= subject.length()); + NBYTES_ASSERT_TRUE(subject.length() - pos <= SIZE_MAX / sizeof(Char)); + void_pos = MemrchrFill(subject.start() + pattern.length() - 1, + search_byte, + bytes_to_search); + } + const Char* char_pos = static_cast(void_pos); + if (char_pos == nullptr) + return subject.length(); + + // Then, for each match, verify that the full two bytes match pattern[0]. + char_pos = AlignDown(char_pos, sizeof(Char)); + size_t raw_pos = static_cast(char_pos - subject.start()); + pos = subject.forward() ? raw_pos : (subject.length() - raw_pos - 1); + if (subject[pos] == pattern_first_char) { + // Match found, hooray. + return pos; + } + // Search byte matched, but the other byte of pattern[0] didn't. Keep going. + } while (++pos < max_n); + + return subject.length(); +} + +// Finds the first occurrence of the byte pattern[0] in string `subject`. +// Does not verify that the whole pattern matches. +template <> +inline size_t FindFirstCharacter(Vector pattern, + Vector subject, + size_t index) { + const uint8_t pattern_first_char = pattern[0]; + const size_t subj_len = subject.length(); + const size_t max_n = (subject.length() - pattern.length() + 1); + + const void* pos; + if (subject.forward()) { + pos = memchr(subject.start() + index, pattern_first_char, max_n - index); + } else { + pos = MemrchrFill(subject.start() + pattern.length() - 1, + pattern_first_char, + max_n - index); + } + const uint8_t* char_pos = static_cast(pos); + if (char_pos == nullptr) { + return subj_len; + } + + size_t raw_pos = static_cast(char_pos - subject.start()); + return subject.forward() ? raw_pos : (subj_len - raw_pos - 1); +} + +//--------------------------------------------------------------------- +// Single Character Pattern Search Strategy +//--------------------------------------------------------------------- + +template +size_t StringSearch::SingleCharSearch( + Vector subject, + size_t index) { + NBYTES_ASSERT_TRUE(1 == pattern_.length()); + return FindFirstCharacter(pattern_, subject, index); +} + +//--------------------------------------------------------------------- +// Linear Search Strategy +//--------------------------------------------------------------------- + +// Simple linear search for short patterns. Never bails out. +template +size_t StringSearch::LinearSearch( + Vector subject, + size_t index) { + NBYTES_ASSERT_TRUE(pattern_.length() > 1); + const size_t n = subject.length() - pattern_.length(); + for (size_t i = index; i <= n; i++) { + i = FindFirstCharacter(pattern_, subject, i); + if (i == subject.length()) + return subject.length(); + NBYTES_ASSERT_TRUE(i <= n); + + bool matches = true; + for (size_t j = 1; j < pattern_.length(); j++) { + if (pattern_[j] != subject[i + j]) { + matches = false; + break; + } + } + if (matches) { + return i; + } + } + return subject.length(); +} + +//--------------------------------------------------------------------- +// Boyer-Moore string search +//--------------------------------------------------------------------- + +template +size_t StringSearch::BoyerMooreSearch( + Vector subject, + size_t start_index) { + const size_t subject_length = subject.length(); + const size_t pattern_length = pattern_.length(); + // Only preprocess at most kBMMaxShift last characters of pattern. + size_t start = start_; + + int* bad_char_occurrence = bad_char_shift_table_; + int* good_suffix_shift = good_suffix_shift_table_ - start_; + + Char last_char = pattern_[pattern_length - 1]; + size_t index = start_index; + // Continue search from i. + while (index <= subject_length - pattern_length) { + size_t j = pattern_length - 1; + int c; + while (last_char != (c = subject[index + j])) { + int shift = j - CharOccurrence(bad_char_occurrence, c); + index += shift; + if (index > subject_length - pattern_length) { + return subject.length(); + } + } + while (pattern_[j] == (c = subject[index + j])) { + if (j == 0) { + return index; + } + j--; + } + if (j < start) { + // we have matched more than our tables allow us to be smart about. + // Fall back on BMH shift. + index += pattern_length - 1 - + CharOccurrence(bad_char_occurrence, last_char); + } else { + int gs_shift = good_suffix_shift[j + 1]; + int bc_occ = CharOccurrence(bad_char_occurrence, c); + int shift = j - bc_occ; + if (gs_shift > shift) { + shift = gs_shift; + } + index += shift; + } + } + + return subject.length(); +} + +template +void StringSearch::PopulateBoyerMooreTable() { + const size_t pattern_length = pattern_.length(); + // Only look at the last kBMMaxShift characters of pattern (from start_ + // to pattern_length). + const size_t start = start_; + const size_t length = pattern_length - start; + + // Biased tables so that we can use pattern indices as table indices, + // even if we only cover the part of the pattern from offset start. + int* shift_table = good_suffix_shift_table_ - start_; + int* suffix_table = suffix_table_ - start_; + + // Initialize table. + for (size_t i = start; i < pattern_length; i++) { + shift_table[i] = length; + } + shift_table[pattern_length] = 1; + suffix_table[pattern_length] = pattern_length + 1; + + if (pattern_length <= start) { + return; + } + + // Find suffixes. + Char last_char = pattern_[pattern_length - 1]; + size_t suffix = pattern_length + 1; + { + size_t i = pattern_length; + while (i > start) { + Char c = pattern_[i - 1]; + while (suffix <= pattern_length && c != pattern_[suffix - 1]) { + if (static_cast(shift_table[suffix]) == length) { + shift_table[suffix] = suffix - i; + } + suffix = suffix_table[suffix]; + } + suffix_table[--i] = --suffix; + if (suffix == pattern_length) { + // No suffix to extend, so we check against last_char only. + while ((i > start) && (pattern_[i - 1] != last_char)) { + if (static_cast(shift_table[pattern_length]) == length) { + shift_table[pattern_length] = pattern_length - i; + } + suffix_table[--i] = pattern_length; + } + if (i > start) { + suffix_table[--i] = --suffix; + } + } + } + } + // Build shift table using suffixes. + if (suffix < pattern_length) { + for (size_t i = start; i <= pattern_length; i++) { + if (static_cast(shift_table[i]) == length) { + shift_table[i] = suffix - start; + } + if (i == suffix) { + suffix = suffix_table[suffix]; + } + } + } +} + +//--------------------------------------------------------------------- +// Boyer-Moore-Horspool string search. +//--------------------------------------------------------------------- + +template +size_t StringSearch::BoyerMooreHorspoolSearch( + Vector subject, + size_t start_index) { + const size_t subject_length = subject.length(); + const size_t pattern_length = pattern_.length(); + int* char_occurrences = bad_char_shift_table_; + int64_t badness = -static_cast(pattern_length); + + // How bad we are doing without a good-suffix table. + Char last_char = pattern_[pattern_length - 1]; + int last_char_shift = + pattern_length - 1 - + CharOccurrence(char_occurrences, last_char); + + // Perform search + size_t index = start_index; // No matches found prior to this index. + while (index <= subject_length - pattern_length) { + size_t j = pattern_length - 1; + int subject_char; + while (last_char != (subject_char = subject[index + j])) { + int bc_occ = CharOccurrence(char_occurrences, subject_char); + int shift = j - bc_occ; + index += shift; + badness += 1 - shift; // at most zero, so badness cannot increase. + if (index > subject_length - pattern_length) { + return subject_length; + } + } + j--; + while (pattern_[j] == (subject[index + j])) { + if (j == 0) { + return index; + } + j--; + } + index += last_char_shift; + // Badness increases by the number of characters we have + // checked, and decreases by the number of characters we + // can skip by shifting. It's a measure of how we are doing + // compared to reading each character exactly once. + badness += (pattern_length - j) - last_char_shift; + if (badness > 0) { + PopulateBoyerMooreTable(); + strategy_ = SearchStrategy::kBoyerMoore; + return BoyerMooreSearch(subject, index); + } + } + return subject.length(); +} + +template +void StringSearch::PopulateBoyerMooreHorspoolTable() { + const size_t pattern_length = pattern_.length(); + + int* bad_char_occurrence = bad_char_shift_table_; + + // Only preprocess at most kBMMaxShift last characters of pattern. + const size_t start = start_; + // Run forwards to populate bad_char_table, so that *last* instance + // of character equivalence class is the one registered. + // Notice: Doesn't include the last character. + const size_t table_size = AlphabetSize(); + if (start == 0) { + // All patterns less than kBMMaxShift in length. + memset(bad_char_occurrence, -1, table_size * sizeof(*bad_char_occurrence)); + } else { + for (size_t i = 0; i < table_size; i++) { + bad_char_occurrence[i] = start - 1; + } + } + for (size_t i = start; i < pattern_length - 1; i++) { + Char c = pattern_[i]; + int bucket = (sizeof(Char) == 1) ? c : c % AlphabetSize(); + bad_char_occurrence[bucket] = i; + } +} + +//--------------------------------------------------------------------- +// Linear string search with bailout to BMH. +//--------------------------------------------------------------------- + +// Simple linear search for short patterns, which bails out if the string +// isn't found very early in the subject. Upgrades to BoyerMooreHorspool. +template +size_t StringSearch::InitialSearch( + Vector subject, + size_t index) { + const size_t pattern_length = pattern_.length(); + // Badness is a count of how much work we have done. When we have + // done enough work we decide it's probably worth switching to a better + // algorithm. + int64_t badness = -10 - (pattern_length << 2); + + // We know our pattern is at least 2 characters, we cache the first so + // the common case of the first character not matching is faster. + for (size_t i = index, n = subject.length() - pattern_length; i <= n; i++) { + badness++; + if (badness <= 0) { + i = FindFirstCharacter(pattern_, subject, i); + if (i == subject.length()) + return subject.length(); + NBYTES_ASSERT_TRUE(i <= n); + size_t j = 1; + do { + if (pattern_[j] != subject[i + j]) { + break; + } + j++; + } while (j < pattern_length); + if (j == pattern_length) { + return i; + } + badness += j; + } else { + PopulateBoyerMooreHorspoolTable(); + strategy_ = SearchStrategy::kBoyerMooreHorspool; + return BoyerMooreHorspoolSearch(subject, i); + } + } + return subject.length(); +} + +// Perform a single stand-alone search. +// If searching multiple times for the same pattern, a search +// object should be constructed once and the Search function then called +// for each search. +template +size_t SearchString(Vector subject, + Vector pattern, + size_t start_index) { + StringSearch search(pattern); + return search.Search(subject, start_index); +} +} // namespace stringsearch + +template +size_t SearchString(const Char* haystack, + size_t haystack_length, + const Char* needle, + size_t needle_length, + size_t start_index, + bool is_forward) { + if (haystack_length < needle_length) return haystack_length; + // To do a reverse search (lastIndexOf instead of indexOf) without redundant + // code, create two vectors that are reversed views into the input strings. + // For example, v_needle[0] would return the *last* character of the needle. + // So we're searching for the first instance of rev(needle) in rev(haystack) + stringsearch::Vector v_needle(needle, needle_length, is_forward); + stringsearch::Vector v_haystack( + haystack, haystack_length, is_forward); + size_t diff = haystack_length - needle_length; + size_t relative_start_index; + if (is_forward) { + relative_start_index = start_index; + } else if (diff < start_index) { + relative_start_index = 0; + } else { + relative_start_index = diff - start_index; + } + size_t pos = stringsearch::SearchString( + v_haystack, v_needle, relative_start_index); + if (pos == haystack_length) { + // not found + return pos; + } + return is_forward ? pos : (haystack_length - needle_length - pos); +} + +template +size_t SearchString(const char* haystack, size_t haystack_length, + const char (&needle)[N]) { + return SearchString( + reinterpret_cast(haystack), haystack_length, + reinterpret_cast(needle), N - 1, 0, true); +} + +// ============================================================================ +// Version metadata +#define NBYTES_VERSION "0.0.1" + +enum { + NBYTES_VERSION_MAJOR = 0, + NBYTES_VERSION_MINOR = 0, + NBYTES_VERSION_REVISION = 1, +}; + } // namespace nbytes diff --git a/node.gyp b/node.gyp index 4419bf71dde523..0685742e8165fa 100644 --- a/node.gyp +++ b/node.gyp @@ -291,7 +291,6 @@ 'src/string_bytes.h', 'src/string_decoder.h', 'src/string_decoder-inl.h', - 'src/string_search.h', 'src/tcp_wrap.h', 'src/timers.h', 'src/tracing/agent.h', diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 53e4f2888fffdc..02a6a79492cf12 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -30,7 +30,7 @@ #include "env-inl.h" #include "simdutf.h" #include "string_bytes.h" -#include "string_search.h" + #include "util-inl.h" #include "v8-fast-api-calls.h" #include "v8.h" @@ -970,19 +970,20 @@ void IndexOfString(const FunctionCallbackInfo& args) { if (decoded_string == nullptr) return args.GetReturnValue().Set(-1); - result = SearchString(reinterpret_cast(haystack), - haystack_length / 2, - decoded_string, - decoder.size() / 2, - offset / 2, - is_forward); + result = nbytes::SearchString(reinterpret_cast(haystack), + haystack_length / 2, + decoded_string, + decoder.size() / 2, + offset / 2, + is_forward); } else { - result = SearchString(reinterpret_cast(haystack), - haystack_length / 2, - reinterpret_cast(*needle_value), - needle_value.length(), - offset / 2, - is_forward); + result = + nbytes::SearchString(reinterpret_cast(haystack), + haystack_length / 2, + reinterpret_cast(*needle_value), + needle_value.length(), + offset / 2, + is_forward); } result *= 2; } else if (enc == UTF8) { @@ -990,12 +991,13 @@ void IndexOfString(const FunctionCallbackInfo& args) { if (*needle_value == nullptr) return args.GetReturnValue().Set(-1); - result = SearchString(reinterpret_cast(haystack), - haystack_length, - reinterpret_cast(*needle_value), - needle_length, - offset, - is_forward); + result = + nbytes::SearchString(reinterpret_cast(haystack), + haystack_length, + reinterpret_cast(*needle_value), + needle_length, + offset, + is_forward); } else if (enc == LATIN1) { uint8_t* needle_data = node::UncheckedMalloc(needle_length); if (needle_data == nullptr) { @@ -1004,12 +1006,12 @@ void IndexOfString(const FunctionCallbackInfo& args) { needle->WriteOneByte( isolate, needle_data, 0, needle_length, String::NO_NULL_TERMINATION); - result = SearchString(reinterpret_cast(haystack), - haystack_length, - needle_data, - needle_length, - offset, - is_forward); + result = nbytes::SearchString(reinterpret_cast(haystack), + haystack_length, + needle_data, + needle_length, + offset, + is_forward); free(needle_data); } @@ -1068,22 +1070,20 @@ void IndexOfBuffer(const FunctionCallbackInfo& args) { if (haystack_length < 2 || needle_length < 2) { return args.GetReturnValue().Set(-1); } - result = SearchString( - reinterpret_cast(haystack), - haystack_length / 2, - reinterpret_cast(needle), - needle_length / 2, - offset / 2, - is_forward); + result = nbytes::SearchString(reinterpret_cast(haystack), + haystack_length / 2, + reinterpret_cast(needle), + needle_length / 2, + offset / 2, + is_forward); result *= 2; } else { - result = SearchString( - reinterpret_cast(haystack), - haystack_length, - reinterpret_cast(needle), - needle_length, - offset, - is_forward); + result = nbytes::SearchString(reinterpret_cast(haystack), + haystack_length, + reinterpret_cast(needle), + needle_length, + offset, + is_forward); } args.GetReturnValue().Set( @@ -1106,7 +1106,7 @@ int32_t IndexOfNumber(const uint8_t* buffer_data, if (is_forward) { ptr = memchr(buffer_data + offset, needle, buffer_length - offset); } else { - ptr = node::stringsearch::MemrchrFill(buffer_data, needle, offset + 1); + ptr = nbytes::stringsearch::MemrchrFill(buffer_data, needle, offset + 1); } const uint8_t* ptr_uint8 = static_cast(ptr); return ptr != nullptr ? static_cast(ptr_uint8 - buffer_data) : -1; diff --git a/src/node_metadata.cc b/src/node_metadata.cc index 3b7493f82b91b8..937e415eb55857 100644 --- a/src/node_metadata.cc +++ b/src/node_metadata.cc @@ -5,6 +5,7 @@ #include "brotli/encode.h" #include "cjs_module_lexer_version.h" #include "llhttp.h" +#include "nbytes.h" #include "nghttp2/nghttp2ver.h" #include "node.h" #include "simdjson.h" @@ -133,6 +134,7 @@ Metadata::Versions::Versions() { simdutf = SIMDUTF_VERSION; sqlite = SQLITE_VERSION; ada = ADA_VERSION; + nbytes = NBYTES_VERSION; } Metadata::Release::Release() : name(NODE_RELEASE) { diff --git a/src/node_metadata.h b/src/node_metadata.h index 5400220424e8d7..90c7dbf22c8e85 100644 --- a/src/node_metadata.h +++ b/src/node_metadata.h @@ -50,6 +50,7 @@ namespace node { V(simdutf) \ V(sqlite) \ V(ada) \ + V(nbytes) \ NODE_VERSIONS_KEY_UNDICI(V) \ V(cjs_module_lexer) diff --git a/src/string_search.h b/src/string_search.h deleted file mode 100644 index cd9ef320a81112..00000000000000 --- a/src/string_search.h +++ /dev/null @@ -1,638 +0,0 @@ -// Copyright 2011 the V8 project authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef SRC_STRING_SEARCH_H_ -#define SRC_STRING_SEARCH_H_ - -#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#include "util.h" - -#include -#include - -namespace node { -namespace stringsearch { - -template -class Vector { - public: - Vector(T* data, size_t length, bool isForward) - : start_(data), length_(length), is_forward_(isForward) { - CHECK(length > 0 && data != nullptr); - } - - // Returns the start of the memory range. - // For vector v this is NOT necessarily &v[0], see forward(). - const T* start() const { return start_; } - - // Returns the length of the vector, in characters. - size_t length() const { return length_; } - - // Returns true if the Vector is front-to-back, false if back-to-front. - // In the latter case, v[0] corresponds to the *end* of the memory range. - bool forward() const { return is_forward_; } - - // Access individual vector elements - checks bounds in debug mode. - T& operator[](size_t index) const { - DCHECK_LT(index, length_); - return start_[is_forward_ ? index : (length_ - index - 1)]; - } - - private: - T* start_; - size_t length_; - bool is_forward_; -}; - - -//--------------------------------------------------------------------- -// String Search object. -//--------------------------------------------------------------------- - -// Class holding constants and methods that apply to all string search variants, -// independently of subject and pattern char size. -class StringSearchBase { - protected: - // Cap on the maximal shift in the Boyer-Moore implementation. By setting a - // limit, we can fix the size of tables. For a needle longer than this limit, - // search will not be optimal, since we only build tables for a suffix - // of the string, but it is a safe approximation. - static const int kBMMaxShift = 250; - - // Reduce alphabet to this size. - // One of the tables used by Boyer-Moore and Boyer-Moore-Horspool has size - // proportional to the input alphabet. We reduce the alphabet size by - // equating input characters modulo a smaller alphabet size. This gives - // a potentially less efficient searching, but is a safe approximation. - // For needles using only characters in the same Unicode 256-code point page, - // there is no search speed degradation. - static const int kLatin1AlphabetSize = 256; - static const int kUC16AlphabetSize = 256; - - // Bad-char shift table stored in the state. It's length is the alphabet size. - // For patterns below this length, the skip length of Boyer-Moore is too short - // to compensate for the algorithmic overhead compared to simple brute force. - static const int kBMMinPatternLength = 8; - - // Store for the BoyerMoore(Horspool) bad char shift table. - int bad_char_shift_table_[kUC16AlphabetSize]; - // Store for the BoyerMoore good suffix shift table. - int good_suffix_shift_table_[kBMMaxShift + 1]; - // Table used temporarily while building the BoyerMoore good suffix - // shift table. - int suffix_table_[kBMMaxShift + 1]; -}; - -template -class StringSearch : private StringSearchBase { - public: - typedef stringsearch::Vector Vector; - - explicit StringSearch(Vector pattern) - : pattern_(pattern), start_(0) { - if (pattern.length() >= kBMMaxShift) { - start_ = pattern.length() - kBMMaxShift; - } - - size_t pattern_length = pattern_.length(); - CHECK_GT(pattern_length, 0); - if (pattern_length < kBMMinPatternLength) { - if (pattern_length == 1) { - strategy_ = SearchStrategy::kSingleChar; - return; - } - strategy_ = SearchStrategy::kLinear; - return; - } - strategy_ = SearchStrategy::kInitial; - } - - size_t Search(Vector subject, size_t index) { - switch (strategy_) { - case kBoyerMooreHorspool: - return BoyerMooreHorspoolSearch(subject, index); - case kBoyerMoore: - return BoyerMooreSearch(subject, index); - case kInitial: - return InitialSearch(subject, index); - case kLinear: - return LinearSearch(subject, index); - case kSingleChar: - return SingleCharSearch(subject, index); - } - UNREACHABLE(); - } - - static inline int AlphabetSize() { - if (sizeof(Char) == 1) { - // Latin1 needle. - return kLatin1AlphabetSize; - } else { - // UC16 needle. - return kUC16AlphabetSize; - } - - static_assert(sizeof(Char) == sizeof(uint8_t) || - sizeof(Char) == sizeof(uint16_t), - "sizeof(Char) == sizeof(uint16_t) || sizeof(uint8_t)"); - } - - private: - typedef size_t (StringSearch::*SearchFunction)(Vector, size_t); - size_t SingleCharSearch(Vector subject, size_t start_index); - size_t LinearSearch(Vector subject, size_t start_index); - size_t InitialSearch(Vector subject, size_t start_index); - size_t BoyerMooreHorspoolSearch(Vector subject, size_t start_index); - size_t BoyerMooreSearch(Vector subject, size_t start_index); - - void PopulateBoyerMooreHorspoolTable(); - - void PopulateBoyerMooreTable(); - - static inline int CharOccurrence(int* bad_char_occurrence, - Char char_code) { - if (sizeof(Char) == 1) { - return bad_char_occurrence[static_cast(char_code)]; - } - // Both pattern and subject are UC16. Reduce character to equivalence class. - int equiv_class = char_code % kUC16AlphabetSize; - return bad_char_occurrence[equiv_class]; - } - - enum SearchStrategy { - kBoyerMooreHorspool, - kBoyerMoore, - kInitial, - kLinear, - kSingleChar, - }; - - // The pattern to search for. - Vector pattern_; - SearchStrategy strategy_; - // Cache value of Max(0, pattern_length() - kBMMaxShift) - size_t start_; -}; - - -template -inline T AlignDown(T value, U alignment) { - return reinterpret_cast( - (reinterpret_cast(value) & ~(alignment - 1))); -} - - -inline uint8_t GetHighestValueByte(uint16_t character) { - return std::max(static_cast(character & 0xFF), - static_cast(character >> 8)); -} - - -inline uint8_t GetHighestValueByte(uint8_t character) { return character; } - - -// Searches for a byte value in a memory buffer, back to front. -// Uses memrchr(3) on systems which support it, for speed. -// Falls back to a vanilla for loop on non-GNU systems such as Windows. -inline const void* MemrchrFill(const void* haystack, uint8_t needle, - size_t haystack_len) { -#ifdef _GNU_SOURCE - return memrchr(haystack, needle, haystack_len); -#else - const uint8_t* haystack8 = static_cast(haystack); - for (size_t i = haystack_len - 1; i != static_cast(-1); i--) { - if (haystack8[i] == needle) { - return haystack8 + i; - } - } - return nullptr; -#endif -} - - -// Finds the first occurrence of *two-byte* character pattern[0] in the string -// `subject`. Does not check that the whole pattern matches. -template -inline size_t FindFirstCharacter(Vector pattern, - Vector subject, size_t index) { - const Char pattern_first_char = pattern[0]; - const size_t max_n = (subject.length() - pattern.length() + 1); - - // For speed, search for the more `rare` of the two bytes in pattern[0] - // using memchr / memrchr (which are much faster than a simple for loop). - const uint8_t search_byte = GetHighestValueByte(pattern_first_char); - size_t pos = index; - do { - const size_t bytes_to_search = (max_n - pos) * sizeof(Char); - const void* void_pos; - if (subject.forward()) { - // Assert that bytes_to_search won't overflow - CHECK_LE(pos, max_n); - CHECK_LE(max_n - pos, SIZE_MAX / sizeof(Char)); - void_pos = memchr(subject.start() + pos, search_byte, bytes_to_search); - } else { - CHECK_LE(pos, subject.length()); - CHECK_LE(subject.length() - pos, SIZE_MAX / sizeof(Char)); - void_pos = MemrchrFill(subject.start() + pattern.length() - 1, - search_byte, - bytes_to_search); - } - const Char* char_pos = static_cast(void_pos); - if (char_pos == nullptr) - return subject.length(); - - // Then, for each match, verify that the full two bytes match pattern[0]. - char_pos = AlignDown(char_pos, sizeof(Char)); - size_t raw_pos = static_cast(char_pos - subject.start()); - pos = subject.forward() ? raw_pos : (subject.length() - raw_pos - 1); - if (subject[pos] == pattern_first_char) { - // Match found, hooray. - return pos; - } - // Search byte matched, but the other byte of pattern[0] didn't. Keep going. - } while (++pos < max_n); - - return subject.length(); -} - - -// Finds the first occurrence of the byte pattern[0] in string `subject`. -// Does not verify that the whole pattern matches. -template <> -inline size_t FindFirstCharacter(Vector pattern, - Vector subject, - size_t index) { - const uint8_t pattern_first_char = pattern[0]; - const size_t subj_len = subject.length(); - const size_t max_n = (subject.length() - pattern.length() + 1); - - const void* pos; - if (subject.forward()) { - pos = memchr(subject.start() + index, pattern_first_char, max_n - index); - } else { - pos = MemrchrFill(subject.start() + pattern.length() - 1, - pattern_first_char, - max_n - index); - } - const uint8_t* char_pos = static_cast(pos); - if (char_pos == nullptr) { - return subj_len; - } - - size_t raw_pos = static_cast(char_pos - subject.start()); - return subject.forward() ? raw_pos : (subj_len - raw_pos - 1); -} - -//--------------------------------------------------------------------- -// Single Character Pattern Search Strategy -//--------------------------------------------------------------------- - -template -size_t StringSearch::SingleCharSearch( - Vector subject, - size_t index) { - CHECK_EQ(1, pattern_.length()); - return FindFirstCharacter(pattern_, subject, index); -} - -//--------------------------------------------------------------------- -// Linear Search Strategy -//--------------------------------------------------------------------- - -// Simple linear search for short patterns. Never bails out. -template -size_t StringSearch::LinearSearch( - Vector subject, - size_t index) { - CHECK_GT(pattern_.length(), 1); - const size_t n = subject.length() - pattern_.length(); - for (size_t i = index; i <= n; i++) { - i = FindFirstCharacter(pattern_, subject, i); - if (i == subject.length()) - return subject.length(); - CHECK_LE(i, n); - - bool matches = true; - for (size_t j = 1; j < pattern_.length(); j++) { - if (pattern_[j] != subject[i + j]) { - matches = false; - break; - } - } - if (matches) { - return i; - } - } - return subject.length(); -} - -//--------------------------------------------------------------------- -// Boyer-Moore string search -//--------------------------------------------------------------------- - -template -size_t StringSearch::BoyerMooreSearch( - Vector subject, - size_t start_index) { - const size_t subject_length = subject.length(); - const size_t pattern_length = pattern_.length(); - // Only preprocess at most kBMMaxShift last characters of pattern. - size_t start = start_; - - int* bad_char_occurrence = bad_char_shift_table_; - int* good_suffix_shift = good_suffix_shift_table_ - start_; - - Char last_char = pattern_[pattern_length - 1]; - size_t index = start_index; - // Continue search from i. - while (index <= subject_length - pattern_length) { - size_t j = pattern_length - 1; - int c; - while (last_char != (c = subject[index + j])) { - int shift = j - CharOccurrence(bad_char_occurrence, c); - index += shift; - if (index > subject_length - pattern_length) { - return subject.length(); - } - } - while (pattern_[j] == (c = subject[index + j])) { - if (j == 0) { - return index; - } - j--; - } - if (j < start) { - // we have matched more than our tables allow us to be smart about. - // Fall back on BMH shift. - index += pattern_length - 1 - - CharOccurrence(bad_char_occurrence, last_char); - } else { - int gs_shift = good_suffix_shift[j + 1]; - int bc_occ = CharOccurrence(bad_char_occurrence, c); - int shift = j - bc_occ; - if (gs_shift > shift) { - shift = gs_shift; - } - index += shift; - } - } - - return subject.length(); -} - -template -void StringSearch::PopulateBoyerMooreTable() { - const size_t pattern_length = pattern_.length(); - // Only look at the last kBMMaxShift characters of pattern (from start_ - // to pattern_length). - const size_t start = start_; - const size_t length = pattern_length - start; - - // Biased tables so that we can use pattern indices as table indices, - // even if we only cover the part of the pattern from offset start. - int* shift_table = good_suffix_shift_table_ - start_; - int* suffix_table = suffix_table_ - start_; - - // Initialize table. - for (size_t i = start; i < pattern_length; i++) { - shift_table[i] = length; - } - shift_table[pattern_length] = 1; - suffix_table[pattern_length] = pattern_length + 1; - - if (pattern_length <= start) { - return; - } - - // Find suffixes. - Char last_char = pattern_[pattern_length - 1]; - size_t suffix = pattern_length + 1; - { - size_t i = pattern_length; - while (i > start) { - Char c = pattern_[i - 1]; - while (suffix <= pattern_length && c != pattern_[suffix - 1]) { - if (static_cast(shift_table[suffix]) == length) { - shift_table[suffix] = suffix - i; - } - suffix = suffix_table[suffix]; - } - suffix_table[--i] = --suffix; - if (suffix == pattern_length) { - // No suffix to extend, so we check against last_char only. - while ((i > start) && (pattern_[i - 1] != last_char)) { - if (static_cast(shift_table[pattern_length]) == length) { - shift_table[pattern_length] = pattern_length - i; - } - suffix_table[--i] = pattern_length; - } - if (i > start) { - suffix_table[--i] = --suffix; - } - } - } - } - // Build shift table using suffixes. - if (suffix < pattern_length) { - for (size_t i = start; i <= pattern_length; i++) { - if (static_cast(shift_table[i]) == length) { - shift_table[i] = suffix - start; - } - if (i == suffix) { - suffix = suffix_table[suffix]; - } - } - } -} - -//--------------------------------------------------------------------- -// Boyer-Moore-Horspool string search. -//--------------------------------------------------------------------- - -template -size_t StringSearch::BoyerMooreHorspoolSearch( - Vector subject, - size_t start_index) { - const size_t subject_length = subject.length(); - const size_t pattern_length = pattern_.length(); - int* char_occurrences = bad_char_shift_table_; - int64_t badness = -static_cast(pattern_length); - - // How bad we are doing without a good-suffix table. - Char last_char = pattern_[pattern_length - 1]; - int last_char_shift = - pattern_length - 1 - - CharOccurrence(char_occurrences, last_char); - - // Perform search - size_t index = start_index; // No matches found prior to this index. - while (index <= subject_length - pattern_length) { - size_t j = pattern_length - 1; - int subject_char; - while (last_char != (subject_char = subject[index + j])) { - int bc_occ = CharOccurrence(char_occurrences, subject_char); - int shift = j - bc_occ; - index += shift; - badness += 1 - shift; // at most zero, so badness cannot increase. - if (index > subject_length - pattern_length) { - return subject_length; - } - } - j--; - while (pattern_[j] == (subject[index + j])) { - if (j == 0) { - return index; - } - j--; - } - index += last_char_shift; - // Badness increases by the number of characters we have - // checked, and decreases by the number of characters we - // can skip by shifting. It's a measure of how we are doing - // compared to reading each character exactly once. - badness += (pattern_length - j) - last_char_shift; - if (badness > 0) { - PopulateBoyerMooreTable(); - strategy_ = SearchStrategy::kBoyerMoore; - return BoyerMooreSearch(subject, index); - } - } - return subject.length(); -} - -template -void StringSearch::PopulateBoyerMooreHorspoolTable() { - const size_t pattern_length = pattern_.length(); - - int* bad_char_occurrence = bad_char_shift_table_; - - // Only preprocess at most kBMMaxShift last characters of pattern. - const size_t start = start_; - // Run forwards to populate bad_char_table, so that *last* instance - // of character equivalence class is the one registered. - // Notice: Doesn't include the last character. - const size_t table_size = AlphabetSize(); - if (start == 0) { - // All patterns less than kBMMaxShift in length. - memset(bad_char_occurrence, -1, table_size * sizeof(*bad_char_occurrence)); - } else { - for (size_t i = 0; i < table_size; i++) { - bad_char_occurrence[i] = start - 1; - } - } - for (size_t i = start; i < pattern_length - 1; i++) { - Char c = pattern_[i]; - int bucket = (sizeof(Char) == 1) ? c : c % AlphabetSize(); - bad_char_occurrence[bucket] = i; - } -} - -//--------------------------------------------------------------------- -// Linear string search with bailout to BMH. -//--------------------------------------------------------------------- - -// Simple linear search for short patterns, which bails out if the string -// isn't found very early in the subject. Upgrades to BoyerMooreHorspool. -template -size_t StringSearch::InitialSearch( - Vector subject, - size_t index) { - const size_t pattern_length = pattern_.length(); - // Badness is a count of how much work we have done. When we have - // done enough work we decide it's probably worth switching to a better - // algorithm. - int64_t badness = -10 - (pattern_length << 2); - - // We know our pattern is at least 2 characters, we cache the first so - // the common case of the first character not matching is faster. - for (size_t i = index, n = subject.length() - pattern_length; i <= n; i++) { - badness++; - if (badness <= 0) { - i = FindFirstCharacter(pattern_, subject, i); - if (i == subject.length()) - return subject.length(); - CHECK_LE(i, n); - size_t j = 1; - do { - if (pattern_[j] != subject[i + j]) { - break; - } - j++; - } while (j < pattern_length); - if (j == pattern_length) { - return i; - } - badness += j; - } else { - PopulateBoyerMooreHorspoolTable(); - strategy_ = SearchStrategy::kBoyerMooreHorspool; - return BoyerMooreHorspoolSearch(subject, i); - } - } - return subject.length(); -} - -// Perform a single stand-alone search. -// If searching multiple times for the same pattern, a search -// object should be constructed once and the Search function then called -// for each search. -template -size_t SearchString(Vector subject, - Vector pattern, - size_t start_index) { - StringSearch search(pattern); - return search.Search(subject, start_index); -} -} // namespace stringsearch -} // namespace node - -namespace node { - -template -size_t SearchString(const Char* haystack, - size_t haystack_length, - const Char* needle, - size_t needle_length, - size_t start_index, - bool is_forward) { - if (haystack_length < needle_length) return haystack_length; - // To do a reverse search (lastIndexOf instead of indexOf) without redundant - // code, create two vectors that are reversed views into the input strings. - // For example, v_needle[0] would return the *last* character of the needle. - // So we're searching for the first instance of rev(needle) in rev(haystack) - stringsearch::Vector v_needle(needle, needle_length, is_forward); - stringsearch::Vector v_haystack( - haystack, haystack_length, is_forward); - size_t diff = haystack_length - needle_length; - size_t relative_start_index; - if (is_forward) { - relative_start_index = start_index; - } else if (diff < start_index) { - relative_start_index = 0; - } else { - relative_start_index = diff - start_index; - } - size_t pos = node::stringsearch::SearchString( - v_haystack, v_needle, relative_start_index); - if (pos == haystack_length) { - // not found - return pos; - } - return is_forward ? pos : (haystack_length - needle_length - pos); -} - -template -size_t SearchString(const char* haystack, size_t haystack_length, - const char (&needle)[N]) { - return SearchString( - reinterpret_cast(haystack), haystack_length, - reinterpret_cast(needle), N - 1, 0, true); -} - -} // namespace node - -#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS - -#endif // SRC_STRING_SEARCH_H_