From ba91609b6c57d00b4e8e3f7e3c25d60b60730ee4 Mon Sep 17 00:00:00 2001 From: Radu Samson Date: Mon, 2 Nov 2026 21:51:54 +0200 Subject: [PATCH 1/5] deps: V8: cherry-pick e540e1161b02 Original commit message: [regexp] Fix modifiers for ChoiceNodes during Analysis During the analysis pass we didn't reset flags after visiting alternatives of a ChoiceNode, which is required as the sub-graph of an alternative might modify the flags. Fixed: 510487690 Change-Id: I6b2660a98b34cd12740538af4ce61685609934a0 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7826630 Commit-Queue: Patrick Thier Auto-Submit: Patrick Thier Reviewed-by: Jakob Linke Commit-Queue: Jakob Linke Cr-Commit-Position: refs/heads/main@{#107120} Adjusted for the V8 version in deps: use RegExpFlags instead of the Flags alias, which does not exist there. Refs: https://github.com/v8/v8/commit/e540e1161b0217ada6d59c8bc5a3a42fd3e27f40 Refs: https://github.com/nodejs/node/issues/66277 Assisted-by: Claude Code Signed-off-by: Radu Samson --- common.gypi | 2 +- deps/v8/src/regexp/regexp-compiler.cc | 5 +++++ deps/v8/test/mjsunit/regress/regress-510487690.js | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 deps/v8/test/mjsunit/regress/regress-510487690.js diff --git a/common.gypi b/common.gypi index 2967d1e63a48..d48884868e1b 100644 --- a/common.gypi +++ b/common.gypi @@ -44,7 +44,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.40', + 'v8_embedder_string': '-node.41', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/regexp/regexp-compiler.cc b/deps/v8/src/regexp/regexp-compiler.cc index a3e7c170d430..eb13b95d5cff 100644 --- a/deps/v8/src/regexp/regexp-compiler.cc +++ b/deps/v8/src/regexp/regexp-compiler.cc @@ -3841,8 +3841,13 @@ class Analysis : public NodeVisitor { } void VisitChoice(ChoiceNode* that) override { + // Flags need to be reset to the state of the ChoiceNode at the beginning + // of each alternative, as flags might be modified when visiting an + // alternative. + RegExpFlags header_flags = flags(); for (int i = 0; i < that->alternatives()->length(); i++) { EnsureAnalyzed(that->alternatives()->at(i).node()); + set_flags(header_flags); if (has_failed()) return; STATIC_FOR_EACH(Propagators::VisitChoice(that, i)); } diff --git a/deps/v8/test/mjsunit/regress/regress-510487690.js b/deps/v8/test/mjsunit/regress/regress-510487690.js new file mode 100644 index 000000000000..856fb25b272d --- /dev/null +++ b/deps/v8/test/mjsunit/regress/regress-510487690.js @@ -0,0 +1,5 @@ +// Copyright 2026 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. + +assertTrue(/(?i:x|[a-f])/.test("F")); From 5fb020b5c8bcab35c52e2c5d97c4748d87507cb7 Mon Sep 17 00:00:00 2001 From: Radu Samson Date: Mon, 2 Nov 2026 21:51:54 +0200 Subject: [PATCH 2/5] deps: V8: cherry-pick 19e7a57bce3e Original commit message: [regexp] Reset flags after resetting state in Parser When restoring the previous state after parsing a closing parenthesis we also need to restore flags explicitly from the restored state. Fixed: 523687962 Change-Id: I63a5bd9fec2f9b7f6fb43b6893027c3c3d0755f0 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7950157 Auto-Submit: Patrick Thier Commit-Queue: Jakob Linke Reviewed-by: Jakob Linke Cr-Commit-Position: refs/heads/main@{#108021} Refs: https://github.com/v8/v8/commit/19e7a57bce3eaae13cfc4f9ddfaaaa2c0fcca78e Refs: https://github.com/nodejs/node/issues/66277 Assisted-by: Claude Code Signed-off-by: Radu Samson --- common.gypi | 2 +- deps/v8/src/regexp/regexp-parser.cc | 1 + deps/v8/test/mjsunit/regexp-modifiers.js | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/common.gypi b/common.gypi index d48884868e1b..2f092e215de3 100644 --- a/common.gypi +++ b/common.gypi @@ -44,7 +44,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.41', + 'v8_embedder_string': '-node.42', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/regexp/regexp-parser.cc b/deps/v8/src/regexp/regexp-parser.cc index 2792cd0f1e91..0c9bbad8497d 100644 --- a/deps/v8/src/regexp/regexp-parser.cc +++ b/deps/v8/src/regexp/regexp-parser.cc @@ -988,6 +988,7 @@ RegExpTree* RegExpParserImpl::ParseDisjunction() { // Restore previous state. state = state->previous_state(); builder = state->builder(); + flags_ = builder->flags(); builder->AddAtom(body); // For compatibility with JSC and ES3, we allow quantifiers after diff --git a/deps/v8/test/mjsunit/regexp-modifiers.js b/deps/v8/test/mjsunit/regexp-modifiers.js index e012dc8d406c..50c891c8ac8c 100644 --- a/deps/v8/test/mjsunit/regexp-modifiers.js +++ b/deps/v8/test/mjsunit/regexp-modifiers.js @@ -73,3 +73,5 @@ test( /(?m:^f(?si:.o)$)/, ['foo', '\nfoO', 'f\no\n', '\nf\rO\n'], ['Foo', 'F\no\n']); test(/(?i:.oo)/, ['Foo', 'FOO', 'fOo', 'foO']); + +test(/(?i:foo)[x-z]/v, ['Foox', 'fOoz'], ['fooX','FooZ']); From 8b86d76aa7acf266208693db809726641da4e96f Mon Sep 17 00:00:00 2001 From: Radu Samson Date: Mon, 2 Nov 2026 21:51:54 +0200 Subject: [PATCH 3/5] deps: V8: cherry-pick b69a1a1d8613 Original commit message: [regexp] Record flags on deferred worklist items ActionNode::Emit for MODIFY_FLAGS was previously changed to restore outer_flags on the way out to avoid leaking inner flags to trailing deferred nodes. However, this caused nodes deferred from within the modifier group to be compiled with the outer flags when processed by Compiler::Assemble. Instead of restoring flags in ActionNode::Emit, record the active flags at the point where a node is deferred in Compiler::AddWork, and restore them from the work-list item before emitting it. This partially reverts https://chromium-review.googlesource.com/c/v8/v8/+/8174589. Change-Id: I3a552f9223b88ba99889e4375b8d1ea761257b94 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8213283 Reviewed-by: Patrick Thier Auto-Submit: Jakob Linke Commit-Queue: Patrick Thier Cr-Commit-Position: refs/heads/main@{#109102} Adjusted for the V8 version in deps: use RegExpNode and RegExpFlags instead of the upstream Node class and Flags alias, and drop the MODIFY_FLAGS hunk, which reverts a workaround that was never part of this V8 version. The new tests are appended at the end of regexp-modifiers.js because the upstream context test does not exist here. Refs: https://github.com/v8/v8/commit/b69a1a1d8613472f7a3f174060356687ba4255fa Refs: https://github.com/nodejs/node/issues/66277 Assisted-by: Claude Code Signed-off-by: Radu Samson --- common.gypi | 2 +- deps/v8/src/regexp/regexp-compiler.cc | 6 ++++-- deps/v8/src/regexp/regexp-compiler.h | 9 +++++++-- deps/v8/test/mjsunit/regexp-modifiers.js | 14 ++++++++++++++ 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/common.gypi b/common.gypi index 2f092e215de3..5b73c404fee6 100644 --- a/common.gypi +++ b/common.gypi @@ -44,7 +44,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.42', + 'v8_embedder_string': '-node.43', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/regexp/regexp-compiler.cc b/deps/v8/src/regexp/regexp-compiler.cc index eb13b95d5cff..23027f93012c 100644 --- a/deps/v8/src/regexp/regexp-compiler.cc +++ b/deps/v8/src/regexp/regexp-compiler.cc @@ -346,7 +346,7 @@ RegExpCompiler::CompilationResult RegExpCompiler::Assemble( return CompilationResult::RegExpTooBig(); }; - ZoneVector work_list(zone()); + ZoneVector work_list(zone()); work_list_ = &work_list; Label fail; macro_assembler_->PushBacktrack(&fail); @@ -357,11 +357,13 @@ RegExpCompiler::CompilationResult RegExpCompiler::Assemble( macro_assembler_->BindJumpTarget(&fail); macro_assembler_->Fail(); while (!work_list.empty()) { - RegExpNode* node = work_list.back(); + WorkItem item = work_list.back(); + RegExpNode* node = item.node; TRACE_WITH_NODE(this, "Popping from worklist ", node); work_list.pop_back(); node->set_on_work_list(false); if (!node->label()->is_bound()) { + set_flags(item.flags); if (node->Emit(this, &new_trace).IsError()) { return ReportError(); } diff --git a/deps/v8/src/regexp/regexp-compiler.h b/deps/v8/src/regexp/regexp-compiler.h index 6e5a8dcb66e2..d90bf720da0d 100644 --- a/deps/v8/src/regexp/regexp-compiler.h +++ b/deps/v8/src/regexp/regexp-compiler.h @@ -570,10 +570,15 @@ class RegExpCompiler { // lead surrogate and start matching from there. RegExpNode* OptionallyStepBackToLeadSurrogate(RegExpNode* on_success); + struct WorkItem { + RegExpNode* node; + RegExpFlags flags; + }; + inline void AddWork(RegExpNode* node) { if (!node->on_work_list() && !node->label()->is_bound()) { node->set_on_work_list(true); - work_list_->push_back(node); + work_list_->push_back({node, flags()}); } } @@ -644,7 +649,7 @@ class RegExpCompiler { int next_register_; int unicode_lookaround_stack_register_; int unicode_lookaround_position_register_; - ZoneVector* work_list_; + ZoneVector* work_list_; int recursion_depth_; RegExpFlags flags_; RegExpMacroAssembler* macro_assembler_; diff --git a/deps/v8/test/mjsunit/regexp-modifiers.js b/deps/v8/test/mjsunit/regexp-modifiers.js index 50c891c8ac8c..72bf88408713 100644 --- a/deps/v8/test/mjsunit/regexp-modifiers.js +++ b/deps/v8/test/mjsunit/regexp-modifiers.js @@ -75,3 +75,17 @@ test( test(/(?i:.oo)/, ['Foo', 'FOO', 'fOo', 'foO']); test(/(?i:foo)[x-z]/v, ['Foox', 'fOoz'], ['fooX','FooZ']); + +// Nodes inside a modifier group deferred to the work list must be compiled with +// the group's inner flags, not the outer flags. +test( + /^(?i:(?:a|b)(?:c|d)(?:e|f)(?:g|h)(?:i|j)(?:k|l)(?:m|n)X)/, + ['acegikmx', 'acegikmX', 'bdfhjlnx', 'bdfhjlnX'], + ['acegikmy', 'bdfhjlny']); + +// Symmetrically, nodes inside a (?-i:...) group under outer /i must compile +// case-sensitively even when deferred. +test( + /^(?-i:(?:a|b)(?:c|d)(?:e|f)(?:g|h)(?:i|j)(?:k|l)(?:m|n)x)/i, + ['acegikmx', 'bdfhjlnx'], + ['acegikmX', 'bdfhjlnX']); From 380afb00dae57aead2efb3da9782bafd894661cf Mon Sep 17 00:00:00 2001 From: Radu Samson Date: Mon, 2 Nov 2026 21:51:54 +0200 Subject: [PATCH 4/5] deps: V8: cherry-pick d315af3d83cf Original commit message: [regexp] Reset compiler flags between word boundary branches When compiling \b and \B word boundary checks in AssertionNode where the succeeding character type is unknown, two code paths (before_non_word and before_word) are generated sequentially. If the boundary assertion is placed inside a modifier group (e.g. (?i:\.|\bNULL)), emitting the first branch traverses through on_success and reaches the ActionNode that restores outer flags at group exit. This mutates flags without restoring it when returning to emit the second branch. Consequently, subsequent nodes in the second branch (before_word) were incorrectly compiled with outer flags instead of the modifier group's flags. This CL saves compiler->flags() and restores them before emitting the before_word branch in AssertionNode::EmitBoundaryCheck. TAG=agy Fixed: 544575761 Change-Id: I7f08bcb99c749d5e22636c8bd6eb44bd1d3e2e39 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8257209 Reviewed-by: Victor Gomes Commit-Queue: Patrick Thier Cr-Commit-Position: refs/heads/main@{#109236} Adjusted for the V8 version in deps: use RegExpFlags instead of the Flags alias, which does not exist there. Refs: https://github.com/v8/v8/commit/d315af3d83cfe3bc1709f5b140db393db42790b3 Refs: https://github.com/nodejs/node/issues/66277 Assisted-by: Claude Code Signed-off-by: Radu Samson --- common.gypi | 2 +- deps/v8/src/regexp/regexp-compiler.cc | 2 ++ deps/v8/test/mjsunit/regexp-modifiers.js | 13 +++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/common.gypi b/common.gypi index 5b73c404fee6..f09d86019552 100644 --- a/common.gypi +++ b/common.gypi @@ -44,7 +44,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.43', + 'v8_embedder_string': '-node.44', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/regexp/regexp-compiler.cc b/deps/v8/src/regexp/regexp-compiler.cc index 23027f93012c..397361031e5a 100644 --- a/deps/v8/src/regexp/regexp-compiler.cc +++ b/deps/v8/src/regexp/regexp-compiler.cc @@ -2134,6 +2134,7 @@ EmitResult AssertionNode::EmitBoundaryCheck(RegExpCompiler* compiler, } bool at_boundary = (assertion_type_ == AssertionNode::AT_BOUNDARY); if (next_is_word_character == Trace::UNKNOWN) { + const RegExpFlags flags = compiler->flags(); Label before_non_word; Label before_word; if (trace->characters_preloaded() != 1) { @@ -2148,6 +2149,7 @@ EmitResult AssertionNode::EmitBoundaryCheck(RegExpCompiler* compiler, at_boundary ? kIsNonWord : kIsWord)); assembler->GoTo(&ok); + compiler->set_flags(flags); assembler->Bind(&before_word); RETURN_IF_ERROR(BacktrackIfPrevious(compiler, trace, at_boundary ? kIsWord : kIsNonWord)); diff --git a/deps/v8/test/mjsunit/regexp-modifiers.js b/deps/v8/test/mjsunit/regexp-modifiers.js index 72bf88408713..a57f266b6067 100644 --- a/deps/v8/test/mjsunit/regexp-modifiers.js +++ b/deps/v8/test/mjsunit/regexp-modifiers.js @@ -89,3 +89,16 @@ test( /^(?-i:(?:a|b)(?:c|d)(?:e|f)(?:g|h)(?:i|j)(?:k|l)(?:m|n)x)/i, ['acegikmx', 'bdfhjlnx'], ['acegikmX', 'bdfhjlnX']); + +// Boundary assertions inside modifier groups with alternations must not leak +// flags between branches during code generation. +test(/(?i:\.|\bNULL)/, ['null', 'NULL', 'Null', '.foo'], ['xyz']); +test(/(?i:\bNULL|\.)/, ['null', 'NULL', '.foo'], ['xyz']); +test(/(?i:\.|\BNULL)/, ['xnull', 'xNULL'], ['null', 'xyz']); +test(/(?i:\W|\bNULL)/, ['null', 'NULL', '!'], ['x']); +test(/(?i:!|\?|\bNULL)/, ['null', 'NULL', '!', '?'], ['x']); +test(/(?i:\.|\bnUll)/, ['null', 'NULL']); +test(/(?i:\.|\bnuLl)/, ['null', 'NULL']); +test(/(?i:\.|\bnulL)/, ['null', 'NULL']); +test(/(?-i:(?i:\.|\bNULL))/, ['null', 'NULL', '.foo'], ['xyz']); +test(/(?i:(?-i:\.|\bNULL))/, ['NULL', '.foo'], ['null', 'xyz']); From 03f0ad52ccae112bd6e831e0b9bef748431ba2c0 Mon Sep 17 00:00:00 2001 From: Radu Samson Date: Tue, 3 Nov 2026 07:36:06 +0200 Subject: [PATCH 5/5] deps: V8: cherry-pick 1e7ee91de775 Original commit message: [regexp] Reset flags between negative lookaround branches e540e1161b0 (bug 510487690) reset the analysis-time flags between the alternatives of a ChoiceNode, since an alternative's sub-graph may contain a MODIFY_FLAGS node. NegativeLookaroundChoiceNode has its own visitor and was left out, so a modifier group inside a negative lookaround still leaks its flags into the continuation. /(?!(?i:x|y))[K]/.exec('kz') // "k", should be null The lookaround body is visited first and leaves the group's flags behind; the continuation's class ranges are then made case independent under those flags rather than the pattern's, so [K] picks up 'k'. Only class ranges are affected: TextNode::MakeCaseIndependent materializes the equivalents during analysis, while atoms get their case closure at emission time from the restored flags. A single- alternative body does not reproduce either, since its restore node is emitted last. Change-Id: I4077076b9dbee6acf75918a2c4f7bf7d86cab059 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8175612 Auto-Submit: Jakob Linke Reviewed-by: Patrick Thier Commit-Queue: Patrick Thier Cr-Commit-Position: refs/heads/main@{#109043} Adjusted for the V8 version in deps: use RegExpFlags instead of the Flags alias, which does not exist there. Refs: https://github.com/v8/v8/commit/1e7ee91de77500f9905ebbc5bed33c2186b1e871 Refs: https://github.com/nodejs/node/issues/66277 Assisted-by: Claude Code Signed-off-by: Radu Samson --- common.gypi | 2 +- deps/v8/src/regexp/regexp-compiler.cc | 2 ++ deps/v8/test/mjsunit/regexp-modifiers.js | 8 ++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/common.gypi b/common.gypi index f09d86019552..e208978fabbf 100644 --- a/common.gypi +++ b/common.gypi @@ -44,7 +44,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.44', + 'v8_embedder_string': '-node.45', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/regexp/regexp-compiler.cc b/deps/v8/src/regexp/regexp-compiler.cc index 397361031e5a..ba1452be9fca 100644 --- a/deps/v8/src/regexp/regexp-compiler.cc +++ b/deps/v8/src/regexp/regexp-compiler.cc @@ -3889,11 +3889,13 @@ class Analysis : public NodeVisitor { NegativeLookaroundChoiceNode* that) override { DCHECK_EQ(that->alternatives()->length(), 2); // Lookaround and continue. + RegExpFlags header_flags = flags(); EnsureAnalyzed(that->lookaround_node()); if (has_failed()) return; STATIC_FOR_EACH( Propagators::VisitNegativeLookaroundChoiceLookaroundNode(that)); + set_flags(header_flags); EnsureAnalyzed(that->continue_node()); if (has_failed()) return; STATIC_FOR_EACH( diff --git a/deps/v8/test/mjsunit/regexp-modifiers.js b/deps/v8/test/mjsunit/regexp-modifiers.js index a57f266b6067..91b1f2364bdc 100644 --- a/deps/v8/test/mjsunit/regexp-modifiers.js +++ b/deps/v8/test/mjsunit/regexp-modifiers.js @@ -102,3 +102,11 @@ test(/(?i:\.|\bnuLl)/, ['null', 'NULL']); test(/(?i:\.|\bnulL)/, ['null', 'NULL']); test(/(?-i:(?i:\.|\bNULL))/, ['null', 'NULL', '.foo'], ['xyz']); test(/(?i:(?-i:\.|\bNULL))/, ['NULL', '.foo'], ['null', 'xyz']); + +// A modifier group inside a negative lookaround must not leak its flags into +// the continuation. Analysis visits the two branches of the lookaround choice +// in sequence, so without a reset the continuation's classes are made case +// independent under the group's flags rather than the pattern's. +test(/(?!(?i:x|y))[K]/, ['K'], ['k']); +test(/(?