From 1ed98eb5ef84ff8eba02f2070d681457ef1d39e0 Mon Sep 17 00:00:00 2001 From: Samson radu Date: Sun, 1 Nov 2026 22:36:34 +0200 Subject: [PATCH] deps: v8: cherrypick e540e1161b02 b69a1a1d8613 19e7a57bce3e d315af3d83cf Fixes #66277 Original commits: [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} ----------------------------------------------------------------------- [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} ----------------------------------------------------------------------- [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} ----------------------------------------------------------------------- [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} ----------------------------------------------------------------------- --- deps/v8/src/regexp/regexp-compiler.cc | 15 +++++++++--- deps/v8/src/regexp/regexp-compiler.h | 9 +++++-- deps/v8/src/regexp/regexp-parser.cc | 2 +- deps/v8/test/mjsunit/regexp-modifiers.js | 30 ++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/deps/v8/src/regexp/regexp-compiler.cc b/deps/v8/src/regexp/regexp-compiler.cc index a3e7c170d430..313f733e4ed8 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(); } @@ -2132,6 +2134,7 @@ EmitResult AssertionNode::EmitBoundaryCheck(RegExpCompiler* compiler, } bool at_boundary = (assertion_type_ == AssertionNode::AT_BOUNDARY); if (next_is_word_character == Trace::UNKNOWN) { + const Flags flags = compiler->flags(); Label before_non_word; Label before_word; if (trace->characters_preloaded() != 1) { @@ -2145,7 +2148,8 @@ EmitResult AssertionNode::EmitBoundaryCheck(RegExpCompiler* compiler, RETURN_IF_ERROR(BacktrackIfPrevious(compiler, trace, 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)); @@ -3841,8 +3845,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. + Flags 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/src/regexp/regexp-compiler.h b/deps/v8/src/regexp/regexp-compiler.h index 6e5a8dcb66e2..14bbc62211b5 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; + Flags 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/src/regexp/regexp-parser.cc b/deps/v8/src/regexp/regexp-parser.cc index 2792cd0f1e91..5a5d7bb7d3c7 100644 --- a/deps/v8/src/regexp/regexp-parser.cc +++ b/deps/v8/src/regexp/regexp-parser.cc @@ -988,7 +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 // lookaheads, and break in all cases. diff --git a/deps/v8/test/mjsunit/regexp-modifiers.js b/deps/v8/test/mjsunit/regexp-modifiers.js index e012dc8d406c..0d6592cf7cd2 100644 --- a/deps/v8/test/mjsunit/regexp-modifiers.js +++ b/deps/v8/test/mjsunit/regexp-modifiers.js @@ -73,3 +73,33 @@ test( /(?m:^f(?si:.o)$)/, ['foo', '\nfoO', 'f\no\n', '\nf\rO\n'], ['Foo', 'F\no\n']); test(/(?i:.oo)/, ['Foo', 'FOO', 'fOo', 'foO']); + +// 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']); + +test(/(?i:x|[a-f])/, ['F']); +test(/(?i:foo)[x-z]/v, ['Foox', 'fOoz'], ['fooX','FooZ']); + +// 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']); \ No newline at end of file