Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common.gypi
Original file line number Diff line number Diff line change
Expand Up @@ -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.45',

##### V8 defaults for Node.js #####

Expand Down
15 changes: 13 additions & 2 deletions deps/v8/src/regexp/regexp-compiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ RegExpCompiler::CompilationResult RegExpCompiler::Assemble(
return CompilationResult::RegExpTooBig();
};

ZoneVector<RegExpNode*> work_list(zone());
ZoneVector<WorkItem> work_list(zone());
work_list_ = &work_list;
Label fail;
macro_assembler_->PushBacktrack(&fail);
Expand All @@ -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();
}
Expand Down Expand Up @@ -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 RegExpFlags flags = compiler->flags();
Label before_non_word;
Label before_word;
if (trace->characters_preloaded() != 1) {
Expand All @@ -2146,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));
Expand Down Expand Up @@ -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.
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));
}
Expand Down Expand Up @@ -3880,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(
Expand Down
9 changes: 7 additions & 2 deletions deps/v8/src/regexp/regexp-compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -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()});
}
}

Expand Down Expand Up @@ -644,7 +649,7 @@ class RegExpCompiler {
int next_register_;
int unicode_lookaround_stack_register_;
int unicode_lookaround_position_register_;
ZoneVector<RegExpNode*>* work_list_;
ZoneVector<WorkItem>* work_list_;
int recursion_depth_;
RegExpFlags flags_;
RegExpMacroAssembler* macro_assembler_;
Expand Down
1 change: 1 addition & 0 deletions deps/v8/src/regexp/regexp-parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -988,6 +988,7 @@ RegExpTree* RegExpParserImpl<CharT>::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
Expand Down
37 changes: 37 additions & 0 deletions deps/v8/test/mjsunit/regexp-modifiers.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,40 @@ 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']);

// 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']);

// 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']);

// 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(/(?<!(?i:x|y))[K]/, ['K'], ['k']);
test(/(?!(?-i:x|y))[K]/i, ['K', 'k']);
5 changes: 5 additions & 0 deletions deps/v8/test/mjsunit/regress/regress-510487690.js
Original file line number Diff line number Diff line change
@@ -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"));