diff --git a/meta-oe/recipes-devtools/nodejs/nodejs-24/0011-v8-handle-supplementary-case-equivalents-of-BMP-characters.patch b/meta-oe/recipes-devtools/nodejs/nodejs-24/0011-v8-handle-supplementary-case-equivalents-of-BMP-characters.patch new file mode 100644 index 0000000000..36703eedd4 --- /dev/null +++ b/meta-oe/recipes-devtools/nodejs/nodejs-24/0011-v8-handle-supplementary-case-equivalents-of-BMP-characters.patch @@ -0,0 +1,235 @@ +From e455d955bd6b7a7549106d1b9d7ae5bb3b9e77b4 Mon Sep 17 00:00:00 2001 +From: Jakob Linke +Date: Fri, 9 Oct 2026 12:41:30 +0200 +Subject: [PATCH] v8: [regexp] Handle supplementary case equivalents of BMP + characters + +Unicode 18 adds U+1DF95, which simple-folds to U+00DF. This creates the +first case-equivalence class spanning BMP and supplementary characters: +{U+00DF, U+1E9E, U+1DF95}. + +Discard supplementary characters from the generator's non-unicode +closures before calling Canonicalize, which requires UTF-16 code units +and otherwise CHECK-fails with ICU 79. Apply the same filtering in +runtime non-unicode closure. + +Generate SupplementaryEquivalentsSet and desugar its BMP characters into +character classes under /iu and /iv, allowing literals to match +surrogate pairs. + +Backreferences with differing UTF-16 character widths remain unsupported +and are tracked in crbug.com/571793821. + +IgnoreSet is unchanged with ICU 78 and with ICU 78.2 patched to upstream +Unicode 18 case data. With the patched data, the generator succeeds, +SupplementaryEquivalentsSet is {U+00DF, U+1E9E}, and regexp tests pass +in release and debug. + +Bug: 571232615, 571668686 +Change-Id: I9eacb273a74c66e17f0cfb1b256e863f2718c130 +Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8548134 +Reviewed-by: Patrick Thier +Commit-Queue: Jakob Linke +Cr-Commit-Position: refs/heads/main@{#110522} + +Fixes the build with system ICU 79.1 (Unicode 18), where +gen-regexp-special-case aborts with "Check failed: ch <= 0xffff". + +This is the V8 change adapted to the RegExpCaseFolding layout of the +V8 13.6 bundled with Node.js 24; the test changes are dropped. + +Upstream-Status: Backport [https://github.com/v8/v8/commit/f607706154eabc4a949628a88b38dbe95cd2274c] +Signed-off-by: Khem Raj +--- + deps/v8/src/regexp/gen-regexp-special-case.cc | 18 ++++++++++ + deps/v8/src/regexp/regexp-parser.cc | 18 +++++++--- + deps/v8/src/regexp/special-case.h | 47 ++++++++++++++++++++++++++- + 3 files changed, 77 insertions(+), 6 deletions(-) + +diff --git a/deps/v8/src/regexp/gen-regexp-special-case.cc b/deps/v8/src/regexp/gen-regexp-special-case.cc +index 86f6b212c93..98f70975be1 100644 +--- a/deps/v8/src/regexp/gen-regexp-special-case.cc ++++ b/deps/v8/src/regexp/gen-regexp-special-case.cc +@@ -9,6 +9,7 @@ + + #include "src/base/strings.h" + #include "src/regexp/special-case.h" ++#include "unicode/uchar.h" + + namespace v8 { + namespace internal { +@@ -17,6 +18,19 @@ static const base::uc32 kSurrogateStart = 0xd800; + static const base::uc32 kSurrogateEnd = 0xdfff; + static const base::uc32 kNonBmpStart = 0x10000; + ++// Removes supplementary code points from |set| and returns whether any were ++// present. Since Unicode 18, the case closure of a BMP character can contain ++// supplementary code points (U+1DF95 LATIN SMALL LIGATURE LONG S WITH ++// DESCENDER S case-folds to U+00DF LATIN SMALL LETTER SHARP S). Non-unicode ++// ignoreCase matching compares UTF-16 code units, so a supplementary code ++// point never matches a BMP character and does not take part in Canonicalize. ++bool RemoveSupplementary(icu::UnicodeSet& set) { ++ constexpr UChar32 kFirstSupplementary = static_cast(kNonBmpStart); ++ if (!set.containsSome(kFirstSupplementary, UCHAR_MAX_VALUE)) return false; ++ set.remove(kFirstSupplementary, UCHAR_MAX_VALUE); ++ return true; ++} ++ + // The following code generates "src/regexp/special-case.cc". + void PrintSet(std::ofstream& out, const char* name, + const icu::UnicodeSet& set) { +@@ -51,6 +65,7 @@ void PrintSpecial(std::ofstream& out) { + icu::UnicodeSet current; + icu::UnicodeSet special_add; + icu::UnicodeSet ignore; ++ icu::UnicodeSet supplementary_equivalents; + UErrorCode status = U_ZERO_ERROR; + icu::UnicodeSet upper("[\\p{Lu}]", status); + CHECK(U_SUCCESS(status)); +@@ -63,6 +78,7 @@ void PrintSpecial(std::ofstream& out) { + } + current.set(i, i); + current.closeOver(USET_CASE_INSENSITIVE); ++ if (RemoveSupplementary(current)) supplementary_equivalents.add(i); + + // Check to see if all characters in the case-folding equivalence + // class as defined by UnicodeSet::closeOver all map to the same +@@ -112,6 +128,7 @@ void PrintSpecial(std::ofstream& out) { + UChar32 canonical = RegExpCaseFolding::Canonicalize(c); + current.set(c, c); + current.closeOver(USET_CASE_INSENSITIVE); ++ RemoveSupplementary(current); + current.removeAll(ignore); + for (int32_t j = 0; j < current.getRangeCount(); j++) { + for (UChar32 c2 = current.getRangeStart(j); +@@ -124,6 +141,7 @@ void PrintSpecial(std::ofstream& out) { + + PrintSet(out, "IgnoreSet", ignore); + PrintSet(out, "SpecialAddSet", special_add); ++ PrintSet(out, "SupplementaryEquivalentsSet", supplementary_equivalents); + } + + void WriteHeader(const char* header_filename) { +diff --git a/deps/v8/src/regexp/regexp-parser.cc b/deps/v8/src/regexp/regexp-parser.cc +index 2792cd0f1e9..dd883c3eddc 100644 +--- a/deps/v8/src/regexp/regexp-parser.cc ++++ b/deps/v8/src/regexp/regexp-parser.cc +@@ -10,6 +10,7 @@ + #include "src/regexp/regexp-ast.h" + #include "src/regexp/regexp-macro-assembler.h" + #include "src/regexp/regexp.h" ++#include "src/regexp/special-case.h" + #include "src/strings/char-predicates-inl.h" + #include "src/utils/ostreams.h" + #include "src/utils/utils.h" +@@ -156,6 +157,10 @@ void RegExpTextBuilder::FlushText() { + + void RegExpTextBuilder::AddCharacter(base::uc16 c) { + FlushPendingSurrogate(); ++ if (NeedsDesugaringForIgnoreCase(c)) { ++ AddClassRangesForDesugaring(c); ++ return; ++ } + if (characters_ == nullptr) { + characters_ = zone()->New>(4, zone()); + } +@@ -238,15 +243,18 @@ bool RegExpTextBuilder::NeedsDesugaringForUnicode(RegExpClassRanges* cc) { + return false; + } + +-// We only use this for characters made of surrogate pairs. All other +-// characters outside of character classes are made case independent in the +-// code generation. ++// Desugar supplementary characters with case equivalents and BMP characters ++// with supplementary equivalents. Other atoms are folded during code ++// generation. + bool RegExpTextBuilder::NeedsDesugaringForIgnoreCase(base::uc32 c) { + #ifdef V8_INTL_SUPPORT + if (IsUnicodeMode() && ignore_case()) { ++ if (c <= unibrow::Utf16::kMaxNonSurrogateCharCode) { ++ return RegExpCaseFolding::HasSupplementaryEquivalents( ++ static_cast(c)); ++ } + icu::UnicodeSet set(c, c); +- set.closeOver(USET_CASE_INSENSITIVE); +- set.removeAllStrings(); ++ RegExpCaseFolding::CloseOver(set, RegExpCaseFolding::Mode::kUnicode); + return set.size() > 1; + } + // In the case where ICU is not included, we act as if the unicode flag is +diff --git a/deps/v8/src/regexp/special-case.h b/deps/v8/src/regexp/special-case.h +index 753c9231ede..80b88a308cc 100644 +--- a/deps/v8/src/regexp/special-case.h ++++ b/deps/v8/src/regexp/special-case.h +@@ -7,8 +7,8 @@ + + #ifdef V8_INTL_SUPPORT + #include "src/base/logging.h" ++#include "src/base/strings.h" + #include "src/common/globals.h" +- + #include "unicode/uchar.h" + #include "unicode/uniset.h" + #include "unicode/unistr.h" +@@ -71,9 +71,17 @@ namespace internal { + // another character. Characters that match no other characters in + // their equivalence class are added to IgnoreSet. Characters that + // match at least one other character are added to SpecialAddSet. ++// Supplementary code points are ignored throughout: non-unicode matching ++// compares UTF-16 code units, so they can never match a BMP character. ++// ++// The generator additionally emits SupplementaryEquivalentsSet, the BMP ++// characters whose Unicode case closure contains supplementary code points. ++// This set is used for unicode-mode matching, see HasSupplementaryEquivalents. + + class RegExpCaseFolding final : public AllStatic { + public: ++ enum class Mode { kNonUnicode, kUnicode }; ++ + static const icu::UnicodeSet& IgnoreSet(); + static const icu::UnicodeSet& SpecialAddSet(); + +@@ -108,6 +116,43 @@ class RegExpCaseFolding final : public AllStatic { + // h. Return cu. + return cu; + } ++ ++ // Close a set of characters under case equivalence, preserving its ++ // original members. Non-unicode inputs must be UTF-16 code units. ++ static void CloseOver(icu::UnicodeSet& set, Mode mode) { ++ if (mode == Mode::kUnicode) { ++ set.closeOver(USET_SIMPLE_CASE_INSENSITIVE); ++ return; ++ } ++ DCHECK(!set.containsSome(0x10000, 0x10ffff)); ++ if (IgnoreSet().containsNone(set)) { ++ CloseOverNonUnicode(set); ++ return; ++ } ++ if (IgnoreSet().containsAll(set)) return; ++ // Ignored characters neither contribute nor acquire equivalents, but ++ // must remain in the result if they were explicitly present. ++ icu::UnicodeSet ignored(set); ++ ignored.retainAll(IgnoreSet()); ++ set.removeAll(IgnoreSet()); ++ CloseOverNonUnicode(set); ++ set.addAll(ignored); ++ } ++ ++ // Whether c has supplementary Unicode case equivalents. ++ static bool HasSupplementaryEquivalents(base::uc16 c) { ++ return SupplementaryEquivalentsSet().contains(c); ++ } ++ ++ private: ++ static const icu::UnicodeSet& SupplementaryEquivalentsSet(); ++ ++ // Requires a set containing no IgnoreSet characters. ++ static void CloseOverNonUnicode(icu::UnicodeSet& set) { ++ set.closeOver(USET_SIMPLE_CASE_INSENSITIVE); ++ set.removeAll(IgnoreSet()); ++ set.remove(0x10000, 0x10ffff); ++ } + }; + + } // namespace internal diff --git a/meta-oe/recipes-devtools/nodejs/nodejs_24.22.1.bb b/meta-oe/recipes-devtools/nodejs/nodejs_24.22.1.bb index 4912136f76..fc5a5e5c09 100644 --- a/meta-oe/recipes-devtools/nodejs/nodejs_24.22.1.bb +++ b/meta-oe/recipes-devtools/nodejs/nodejs_24.22.1.bb @@ -30,6 +30,7 @@ SRC_URI = "https://nodejs.org/dist/v${PV}/node-v${PV}.tar.xz \ file://0007-positional-args.patch \ file://0008-custom-env.patch \ file://0010-v8-fix-Wtemplate-body-error-with-GCC-15-on-ia32.patch \ + file://0011-v8-handle-supplementary-case-equivalents-of-BMP-characters.patch \ file://run-ptest \ " SRC_URI:append:class-target = " \