CVE-2026-5279
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
Processorsrc/parsing/rewriter.cc |
modified |
Files Changed
src/parsing/rewriter.cc
Patch
From c29f676270548a5b042a7b203b7c61e2808f0f09 Mon Sep 17 00:00:00 2001
From: Toon Verwaest <verwaest@chromium.org>
Date: Fri, 13 Mar 2026 10:48:08 +0100
Subject: [PATCH] [parser] Fix stack overflow in the rewriter
Bug: 490642836
Change-Id: Iaab7be8083e14c44867a68eb5d52c9dcb5b78e50
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7666017
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Auto-Submit: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#105783}
---
diff --git a/src/parsing/rewriter.cc b/src/parsing/rewriter.cc
index 3ed2e76..905eac4 100644
--- a/src/parsing/rewriter.cc
+++ b/src/parsing/rewriter.cc
@@ -20,6 +20,12 @@
Visit(param); \
if (CheckStackOverflow()) return;
+// Use this macro when a recursive Process() call may mutate state that would be
+// invalid to consume once stack overflow has been reported.
+#define PROCESS_AND_RETURN_IF_STACK_OVERFLOW(param) \
+ Process(param); \
+ if (CheckStackOverflow()) return;
+
namespace v8::internal {
class Processor final : public AstVisitor<Processor> {
@@ -124,7 +130,7 @@
// early.
for (int i = statements->length() - 1; i >= 0 && (breakable_ || !is_set_);
--i) {
- Visit(statements->at(i));
+ VISIT_AND_RETURN_IF_STACK_OVERFLOW(statements->at(i));
statements->Set(i, replacement_);
}
}
@@ -141,7 +147,7 @@
// to prevent rewriting in that case.
if (!node->ignore_completion_value()) {
BreakableScope scope(this, node->is_breakable());
- Process(node->statements());
+ PROCESS_AND_RETURN_IF_STACK_OVERFLOW(node->statements());
}
replacement_ = node;
}
@@ -161,12 +167,12 @@
// Rewrite both branches.
bool set_after = is_set_;
- Visit(node->then_statement());
+ VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->then_statement());
node->set_then_statement(replacement_);
bool set_in_then = is_set_;
is_set_ = set_after;
- Visit(node->else_statement());
+ VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->else_statement());
node->set_else_statement(replacement_);
replacement_ = set_in_then && is_set_ ? node : AssignUndefinedBefore(node);
@@ -182,7 +188,7 @@
DCHECK(breakable_ || !is_set_);
BreakableScope scope(this);
- Visit(node->body());
+ VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->body());
node->set_body(replacement_);
replacement_ = AssignUndefinedBefore(node);
@@ -297,7 +303,7 @@
ZonePtrList<CaseClause>* clauses = node->cases();
for (int i = clauses->length() - 1; i >= 0; --i) {
CaseClause* clause = clauses->at(i);
- Process(clause->statements());
+ PROCESS_AND_RETURN_IF_STACK_OVERFLOW(clause->statements());
}
replacement_ = AssignUndefinedBefore(node);
@@ -318,7 +324,7 @@
void Processor::VisitWithStatement(WithStatement* node) {
- Visit(node->statement());
+ VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->statement());
node->set_statement(replacement_);
replacement_ = is_set_ ? node : AssignUndefinedBefore(node);
@@ -328,7 +334,7 @@
void Processor::VisitSloppyBlockFunctionStatement(
SloppyBlockFunctionStatement* node) {
- Visit(node->statement());
+ VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->statement());
node->set_statement(replacement_);
replacement_ = node;
}
@@ -417,6 +423,11 @@
result, info->ast_value_factory(), info->zone());
processor.Process(body);
+ if (processor.HasStackOverflow()) {
+ *out_has_stack_overflow = true;
+ return std::nullopt;
+ }
+
if (processor.result_assigned()) {
int pos = kNoSourcePosition;
VariableProxy* result_value =
@@ -429,15 +440,11 @@
}
return result_value;
}
-
- if (processor.HasStackOverflow()) {
- *out_has_stack_overflow = true;
- return std::nullopt;
- }
}
return nullptr;
}
+#undef PROCESS_AND_RETURN_IF_STACK_OVERFLOW
#undef VISIT_AND_RETURN_IF_STACK_OVERFLOW
} // namespace v8::internal
Original Bug Report
Rewriter stack overflow can corrupt AST and can cause wrong-scope bytecode generation
VULNERABILITY DETAILS
Summary
Processor::Process trusts replacement_ from visits even when stack overflow occurred, then it can lead to corrupted AST and bytecode generator consuming it.
Root cause analysis
// src/ast/ast.h
#define DEFINE_AST_VISITOR_SUBCLASS_MEMBERS() \
public: \
void VisitNoStackOverflowCheck(AstNode* node) { \
GENERATE_AST_VISITOR_SWITCH() \
} \
\
void Visit(AstNode* node) { \
if (CheckStackOverflow()) return; \
VisitNoStackOverflowCheck(node); \
} \
\
void SetStackOverflow() { stack_overflow_ = true; } \
void ClearStackOverflow() { stack_overflow_ = false; } \
bool HasStackOverflow() const { return stack_overflow_; } \
\
bool CheckStackOverflow() { \
if (stack_overflow_) return true; \
if (GetCurrentStackPosition() < stack_limit_) { \
stack_overflow_ = true; \
return true; \
} \
return false; \
} \
...
The generic visitor sets a sticky flag and silently returns when stack overflow occurs.
// src/parsing/rewriter.cc
void Processor::VisitBlock(Block* node) {
if (!node->ignore_completion_value()) {
BreakableScope scope(this, node->is_breakable());
Process(node->statements());
}
replacement_ = node;
}
Processor::VisitBlock unconditionally publishes the current block as replacement_ even if an inner Process(node->statements()); can overflow.
// src/parsing/rewriter.cc
void Processor::Process(ZonePtrList<Statement>* statements) {
for (int i = statements->length() - 1; i >= 0 && (breakable_ || !is_set_);
--i) {
Visit(statements->at(i));
statements->Set(i, replacement_);
}
}
Processor::Process trusts replacement_ from Visit.
// src/parsing/rewriter.cc
// Assumes code has been parsed. Mutates the AST, so the AST should not
// continue to be used in the case of failure.
bool Rewriter::Rewrite(ParseInfo* info, bool* out_has_stack_overflow) {
RCS_SCOPE(info->runtime_call_stats(),
RuntimeCallCounterId::kCompileRewriteReturnResult,
RuntimeCallStats::kThreadSpecific);
FunctionLiteral* function = info->literal();
DCHECK_NOT_NULL(function);
Scope* scope = function->scope();
DCHECK_NOT_NULL(scope);
DCHECK_EQ(scope, scope->GetClosureScope());
if (scope->is_repl_mode_scope() ||
!(scope->is_script_scope() || scope->is_eval_scope())) {
return true;
}
ZonePtrList<Statement>* body = function->body();
return RewriteBody(info, scope, body, out_has_stack_overflow).has_value();
}
std::optional<VariableProxy*> Rewriter::RewriteBody(
ParseInfo* info, Scope* scope, ZonePtrList<Statement>* body,
bool* out_has_stack_overflow) {
DisallowGarbageCollection no_gc;
DisallowHandleAllocation no_handles;
DisallowHandleDereference no_deref;
if (!body->is_empty()) {
Variable* result = scope->AsDeclarationScope()->NewTemporary(
info->ast_value_factory()->dot_result_string());
Processor processor(info->stack_limit(), scope->AsDeclarationScope(),
result, info->ast_value_factory(), info->zone());
processor.Process(body);
if (processor.result_assigned()) {
int pos = kNoSourcePosition;
VariableProxy* result_value =
processor.factory()->NewVariableProxy(result, pos);
if (!info->flags().is_repl_mode()) {
Statement* result_statement;
result_statement =
processor.factory()->NewReturnStatement(result_value, pos);
body->Add(result_statement, info->zone());
}
return result_value;
}
if (processor.HasStackOverflow()) {
*out_has_stack_overflow = true;
return std::nullopt;
}
}
return nullptr;
}
Rewriter::RewriteBody trusts that stack overflow did not happen if processor.result_assigned() returns true.
// src/parsing/rewriter.cc
void Processor::VisitIfStatement(IfStatement* node) {
// Rewrite both branches.
bool set_after = is_set_;
Visit(node->then_statement()); // successful
node->set_then_statement(replacement_);
bool set_in_then = is_set_;
is_set_ = set_after;
Visit(node->else_statement()); // later overflows
node->set_else_statement(replacement_);
replacement_ = set_in_then && is_set_ ? node : AssignUndefinedBefore(node);
is_set_ = true;
}
But result_assigned_ can be true when Processor::VisitIfStatement visits then branch successfully and stack overflow occurs in else branch.
And AstVisitor::Visit does not roll back result_assigned_.
So by the time it gets back to Rewriter::RewriteBody, both can be true at once:
processor.result_assigned()== trueprocessor.HasStackOverflow()== true
Eventually Rewriter::Rewrite can return safely with corrupted AST.
// src/interpreter/bytecode-generator.cc
int BytecodeGenerator::GetNewClosureSlot(FunctionLiteral* literal) {
DCHECK_EQ(feedback_slot_cache()->Get(
FeedbackSlotCache::SlotKind::kClosureFeedbackCell, literal),
-1); // dcheck failure
...
#ifdef DEBUG
feedback_slot_cache()->Put(
FeedbackSlotCache::SlotKind::kClosureFeedbackCell, literal, index);
#endif
return index;
}
Then previous sate causes the same FunctionLiteral* to become reachable twice during bytecode generation.
Here’s the exact execution of repro_carrier.js (used in reproduction):
Parser::PostProcessParseResult()callsRewriter::Rewrite.Rewriter::RewriteBody()callsprocessor.Process(body).- Rewriter handles the outer block’s statements from last to first.
VisitBlock()enters the block that containsfunction g() {}and theif (1) 0; else <deep switch chain>.- Inside that block,
VisitIfStatement()descends into theelseside and reaches nestedVisitSwitchStatement()/Process(clause->statements())recursion. AstVisitor::Visit()hits the native stack limit and flips stickystack_overflow_.- Execution returns to
VisitBlock(), which still doesreplacement_ = node. Process()then moves to the0;statement, butVisit(0;)immediately returns because the overflow flag is already set.- So
statements->Set(i, replacement_)replaces that earlier0;statement with the old block still stored inreplacement_. RewriteBody()can still return success, and later bytecode generation revisits the sameFunctionLiteral*.
Impact
The malformed AST may cause miscompilation, where bytecode is emitted for a node under the wrong lexical scope, leading to wrong context-slot reads/writes and potentially type confusion.
VERSION
tested v8 git commit : f9e2258a2c6c7aa50372c496f75db8ddcecff25f
v8 git commit that introduces this bug:
commit af33cccfc4b8b806cbc54a1c72a6b5682312df8d
Author: neis <neis@chromium.org>
Date: Thu Oct 1 02:06:06 2015 -0700
Enable visitor in rewriter to replace statements.
This is in preparation of implementing ES6 completion semantics and
depends on #1362333002.
R=rossberg
BUG=
Review URL: https://codereview.chromium.org/1362363002
Cr-Commit-Position: refs/heads/master@{#31041}
Release tags containing the root-cause commit (from git -C /home/slave/v8-bug-bounty/v8_latest/v8 tag --contains af33cccfc4b8b806cbc54a1c72a6b5682312df8d):
- Earliest local numeric release tag containing the root-cause commit: 4.7.84
- Latest local numeric release tag containing the root-cause commit: 14.7.168
REPRODUCTION CASE
gn args out/x64.release_asan_ubsan:
is_debug = false
target_cpu = "x64"
v8_enable_sandbox = true
v8_enable_backtrace = true
dcheck_always_on = true
symbol_level = 2
is_clang = true
is_asan = true
is_ubsan = true
is_ubsan_no_recover = false
Execute:
./out/x64.release_asan_ubsan/d8 --stack-size=128 repro_carrier.js -- outer_dup_donor_decl_switch 158 158 1
Result:
TRY outer_dup_donor_decl_switch n=158 len=4823
#
# Fatal error in ../../src/interpreter/bytecode-generator.cc, line 9047
# Debug check failed: feedback_slot_cache()->Get( FeedbackSlotCache::SlotKind::kClosureFeedbackCell, literal) == -1 (0 vs. -1).
#
#
#
#FailureMessage Object: 0x7bfff5fdc060
==== C stack trace ===============================
out/x64.release_asan_ubsan/d8(__interceptor_backtrace+0x46) [0x55555cf729f6]
out/x64.release_asan_ubsan/d8(v8::base::debug::StackTrace::StackTrace()+0x34) [0x555566dc00b4]
out/x64.release_asan_ubsan/d8(+0x11866ef3) [0x555566dbaef3]
out/x64.release_asan_ubsan/d8(V8_Fatal(char const*, int, char const*, ...)+0x2ce) [0x555566d6d636]
out/x64.release_asan_ubsan/d8(+0x118180ec) [0x555566d6c0ec]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::GetNewClosureSlot(v8::internal::FunctionLiteral*)+0x313) [0x55555ed5bda3]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::VisitFunctionLiteral(v8::internal::FunctionLiteral*)+0x2b0) [0x55555ed55c40]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::VisitFunctionDeclaration(v8::internal::FunctionDeclaration*)+0x202) [0x55555ed55102]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::VisitDeclarations(v8::base::ThreadedListBase<v8::internal::Declaration, v8::base::EmptyBase, v8::base::ThreadedListTraits<v8::internal::Declaration>, false>*)+0x16b) [0x55555ed4d66b]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::VisitBlockDeclarationsAndStatements(v8::internal::Block*)+0x22c) [0x55555ed50d2c]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::VisitBlockMaybeDispose(v8::internal::Block*)+0x2bc) [0x55555ed52c3c]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::VisitBlock(v8::internal::Block*)+0x1fd) [0x55555ed521ad]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::VisitStatements(v8::internal::ZoneList<v8::internal::Statement*> const*, int)+0x568) [0x55555ed51808]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::VisitBlockDeclarationsAndStatements(v8::internal::Block*)+0x3cf) [0x55555ed50ecf]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::VisitBlockMaybeDispose(v8::internal::Block*)+0x2bc) [0x55555ed52c3c]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::VisitBlock(v8::internal::Block*)+0x1fd) [0x55555ed521ad]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::VisitStatements(v8::internal::ZoneList<v8::internal::Statement*> const*, int)+0x568) [0x55555ed51808]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::GenerateBodyStatementsWithoutImplicitFinalReturn(int)+0x312) [0x55555ed4ee02]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::GenerateBodyStatements(int)+0x16) [0x55555ed4b3d6]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::BytecodeGenerator::GenerateBytecode(unsigned long)+0x5c8) [0x55555ed43c88]
out/x64.release_asan_ubsan/d8(v8::internal::interpreter::InterpreterCompilationJob::ExecuteJobImpl()+0xa1c) [0x55555ee1f24c]
out/x64.release_asan_ubsan/d8(v8::internal::UnoptimizedCompilationJob::ExecuteJob()+0x288) [0x55555d9970c8]
out/x64.release_asan_ubsan/d8(+0x84b3eae) [0x55555da07eae]
out/x64.release_asan_ubsan/d8(+0x8469d65) [0x55555d9bdd65]
out/x64.release_asan_ubsan/d8(+0x846eefb) [0x55555d9c2efb]
out/x64.release_asan_ubsan/d8(v8::internal::Compiler::GetFunctionFromEval(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::String>, v8::internal::DirectHandle<v8::internal::SharedFunctionInfo>, v8::internal::DirectHandle<v8::internal::Context>, v8::internal::LanguageMode, v8::internal::ParseRestriction, int, int, v8::internal::ParsingWhileDebugging)+0x15c8) [0x55555d9c9510]
out/x64.release_asan_ubsan/d8(+0xa9aad50) [0x55555fefed50]
out/x64.release_asan_ubsan/d8(v8::internal::Runtime_ResolvePossiblyDirectEval(int, unsigned long*, v8::internal::Isolate*)+0x1b6) [0x55555fefdc4e]
out/x64.release_asan_ubsan/d8(+0x11334a78) [0x555566888a78]
[1] 1452834 trace trap out/x64.release_asan_ubsan/d8 --stack-size=128 -- outer_dup_donor_decl_switc
another example of reproducible binary:
gn args out/x64.release_asan_no_inline:
is_debug = false
target_cpu = "x64"
v8_enable_sandbox = true
v8_enable_backtrace = true
dcheck_always_on = true
symbol_level = 2
is_clang = true
is_asan = true
v8_no_inline = true
Execute:
./out/x64.release_asan_no_inline/d8 --stack-size=128 repro_carrier.js -- outer_dup_donor_decl_switch 188 188 1
Result:
TRY outer_dup_donor_decl_switch n=188 len=5723
#
# Fatal error in ../../src/interpreter/bytecode-generator.cc, line 9047
# Debug check failed: feedback_slot_cache()->Get( FeedbackSlotCache::SlotKind::kClosureFeedbackCell, literal) == -1 (0 vs. -1).
#
#
#
#FailureMessage Object: 0x7bfff5dabc60
==== C stack trace ===============================
./out/x64.release_asan_no_inline/d8(__interceptor_backtrace+0x46) [0x5555608239f6]
./out/x64.release_asan_no_inline/d8(v8::base::debug::StackTrace::StackTrace()+0x34) [0x555568257554]
./out/x64.release_asan_no_inline/d8(+0x12cfcbdb) [0x555568250bdb]
./out/x64.release_asan_no_inline/d8(V8_Fatal(char const*, int, char const*, ...)+0x271) [0x555568218671]
./out/x64.release_asan_no_inline/d8(+0x12cc3e3f) [0x555568217e3f]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::GetNewClosureSlot(v8::internal::FunctionLiteral*)+0x5c) [0x555561ef06dc]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::VisitFunctionLiteral(v8::internal::FunctionLiteral*)+0x1e2) [0x555561eed922]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::VisitFunctionDeclaration(v8::internal::FunctionDeclaration*)+0x11b) [0x555561eed48b]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::VisitDeclarations(v8::base::ThreadedListBase<v8::internal::Declaration, v8::base::EmptyBase, v8::base::ThreadedListTraits<v8::internal::Declaration>, false>*)+0x153) [0x555561ee9ce3]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::VisitBlockDeclarationsAndStatements(v8::internal::Block*)+0x157) [0x555561eeb677]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::VisitBlockMaybeDispose(v8::internal::Block*)+0x122) [0x555561eeca82]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::VisitBlock(v8::internal::Block*)+0x184) [0x555561eec644]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::VisitStatements(v8::internal::ZoneList<v8::internal::Statement*> const*, int)+0x292) [0x555561eec0e2]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::VisitBlockDeclarationsAndStatements(v8::internal::Block*)+0x1cd) [0x555561eeb6ed]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::VisitBlockMaybeDispose(v8::internal::Block*)+0x122) [0x555561eeca82]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::VisitBlock(v8::internal::Block*)+0x184) [0x555561eec644]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::VisitStatements(v8::internal::ZoneList<v8::internal::Statement*> const*, int)+0x292) [0x555561eec0e2]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::GenerateBodyStatementsWithoutImplicitFinalReturn(int)+0x1ac) [0x555561eeac6c]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::GenerateBodyStatements(int)+0xf) [0x555561ee900f]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::BytecodeGenerator::GenerateBytecode(unsigned long)+0x333) [0x555561ee6b63]
./out/x64.release_asan_no_inline/d8(+0xca12fc7) [0x555561f66fc7]
./out/x64.release_asan_no_inline/d8(v8::internal::interpreter::InterpreterCompilationJob::ExecuteJobImpl()+0x2b5) [0x555561f669a5]
./out/x64.release_asan_no_inline/d8(v8::internal::UnoptimizedCompilationJob::ExecuteJob()+0x167) [0x55556105b7e7]
./out/x64.release_asan_no_inline/d8(+0xbb684e1) [0x5555610bc4e1]
./out/x64.release_asan_no_inline/d8(+0xbb1febb) [0x555561073ebb]
./out/x64.release_asan_no_inline/d8(+0xbb231dc) [0x5555610771dc]
./out/x64.release_asan_no_inline/d8(v8::internal::Compiler::GetFunctionFromEval(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::String>, v8::internal::DirectHandle<v8::internal::SharedFunctionInfo>, v8::internal::DirectHandle<v8::internal::Context>, v8::internal::LanguageMode, v8::internal::ParseRestriction, int, int, v8::internal::ParsingWhileDebugging)+0x12e3) [0x55556107b133]
./out/x64.release_asan_no_inline/d8(+0xd68d446) [0x555562be1446]
./out/x64.release_asan_no_inline/d8(+0xd6879aa) [0x555562bdb9aa]
./out/x64.release_asan_no_inline/d8(v8::internal::Runtime_ResolvePossiblyDirectEval(int, unsigned long*, v8::internal::Isolate*)+0x19d) [0x555562bdb28d]
./out/x64.release_asan_no_inline/d8(+0x127dfa78) [0x555567d33a78]
[1] 893216 trace trap ./out/x64.release_asan_no_inline/d8 --stack-size=128 -- 188 188 1
Type of crash: dcheck failure
Suggesting Fix
suggest.patch:
diff --git a/src/parsing/rewriter.cc b/src/parsing/rewriter.cc
index 3ed2e764bfa..905eac437cc 100644
--- a/src/parsing/rewriter.cc
+++ b/src/parsing/rewriter.cc
@@ -20,6 +20,12 @@
Visit(param); \
if (CheckStackOverflow()) return;
+// Use this macro when a recursive Process() call may mutate state that would be
+// invalid to consume once stack overflow has been reported.
+#define PROCESS_AND_RETURN_IF_STACK_OVERFLOW(param) \
+ Process(param); \
+ if (CheckStackOverflow()) return;
+
namespace v8::internal {
class Processor final : public AstVisitor<Processor> {
@@ -124,7 +130,7 @@ void Processor::Process(ZonePtrList<Statement>* statements) {
// early.
for (int i = statements->length() - 1; i >= 0 && (breakable_ || !is_set_);
--i) {
- Visit(statements->at(i));
+ VISIT_AND_RETURN_IF_STACK_OVERFLOW(statements->at(i));
statements->Set(i, replacement_);
}
}
@@ -141,7 +147,7 @@ void Processor::VisitBlock(Block* node) {
// to prevent rewriting in that case.
if (!node->ignore_completion_value()) {
BreakableScope scope(this, node->is_breakable());
- Process(node->statements());
+ PROCESS_AND_RETURN_IF_STACK_OVERFLOW(node->statements());
}
replacement_ = node;
}
@@ -161,12 +167,12 @@ void Processor::VisitIfStatement(IfStatement* node) {
// Rewrite both branches.
bool set_after = is_set_;
- Visit(node->then_statement());
+ VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->then_statement());
node->set_then_statement(replacement_);
bool set_in_then = is_set_;
is_set_ = set_after;
- Visit(node->else_statement());
+ VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->else_statement());
node->set_else_statement(replacement_);
replacement_ = set_in_then && is_set_ ? node : AssignUndefinedBefore(node);
@@ -182,7 +188,7 @@ void Processor::VisitIterationStatement(IterationStatement* node) {
DCHECK(breakable_ || !is_set_);
BreakableScope scope(this);
- Visit(node->body());
+ VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->body());
node->set_body(replacement_);
replacement_ = AssignUndefinedBefore(node);
@@ -297,7 +303,7 @@ void Processor::VisitSwitchStatement(SwitchStatement* node) {
ZonePtrList<CaseClause>* clauses = node->cases();
for (int i = clauses->length() - 1; i >= 0; --i) {
CaseClause* clause = clauses->at(i);
- Process(clause->statements());
+ PROCESS_AND_RETURN_IF_STACK_OVERFLOW(clause->statements());
}
replacement_ = AssignUndefinedBefore(node);
@@ -318,7 +324,7 @@ void Processor::VisitBreakStatement(BreakStatement* node) {
void Processor::VisitWithStatement(WithStatement* node) {
- Visit(node->statement());
+ VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->statement());
node->set_statement(replacement_);
replacement_ = is_set_ ? node : AssignUndefinedBefore(node);
@@ -328,7 +334,7 @@ void Processor::VisitWithStatement(WithStatement* node) {
void Processor::VisitSloppyBlockFunctionStatement(
SloppyBlockFunctionStatement* node) {
- Visit(node->statement());
+ VISIT_AND_RETURN_IF_STACK_OVERFLOW(node->statement());
node->set_statement(replacement_);
replacement_ = node;
}
@@ -417,6 +423,11 @@ std::optional<VariableProxy*> Rewriter::RewriteBody(
result, info->ast_value_factory(), info->zone());
processor.Process(body);
+ if (processor.HasStackOverflow()) {
+ *out_has_stack_overflow = true;
+ return std::nullopt;
+ }
+
if (processor.result_assigned()) {
int pos = kNoSourcePosition;
VariableProxy* result_value =
@@ -429,15 +440,11 @@ std::optional<VariableProxy*> Rewriter::RewriteBody(
}
return result_value;
}
-
- if (processor.HasStackOverflow()) {
- *out_has_stack_overflow = true;
- return std::nullopt;
- }
}
return nullptr;
}
+#undef PROCESS_AND_RETURN_IF_STACK_OVERFLOW
#undef VISIT_AND_RETURN_IF_STACK_OVERFLOW
} // namespace v8::internal
Run after patch:
➜ v8 git:(main) ✗ ./out/x64.release_asan_ubsan/d8 --stack-size=128 repro_carrier.js -- outer_dup_donor_decl_switch 158 158 1
TRY outer_dup_donor_decl_switch n=158 len=4823
STACK outer_dup_donor_decl_switch n=158: RangeError: Maximum call stack size exceeded
DONE outer_dup_donor_decl_switch
CREDIT INFORMATION
Reporter credit: Hyeonjun Ahn (@_deayzl)