Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactObject corruption in V8
DescriptionObject corruption in V8
ComponentV8
Bug ClassLogic Error
Tracker490642836
Fix commitc29f67627054 (v8/v8) +20/-13
CISA KEVNot listed
CreditedHyeonjun Ahn (@_deayzl)
Disclosed2026-03-31

Changed Functions

FunctionChangeNotes
Processor
src/parsing/rewriter.cc
modified

Files Changed

  • src/parsing/rewriter.cc
From c29f676270548a5b042a7b203b7c61e2808f0f09 Mon Sep 17 00:00:00 2001
From: Toon Verwaest <[email protected]>
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 <[email protected]>
Auto-Submit: Toon Verwaest <[email protected]>
Commit-Queue: Toon Verwaest <[email protected]>
Commit-Queue: Igor Sheludko <[email protected]>
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
Loading diff…

Original Bug Report

reported by [email protected]

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() == true
  • processor.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):

  1. Parser::PostProcessParseResult() calls Rewriter::Rewrite.
  2. Rewriter::RewriteBody() calls processor.Process(body).
  3. Rewriter handles the outer block’s statements from last to first.
  4. VisitBlock() enters the block that contains function g() {} and the if (1) 0; else <deep switch chain>.
  5. Inside that block, VisitIfStatement() descends into the else side and reaches nested VisitSwitchStatement() / Process(clause->statements()) recursion.
  6. AstVisitor::Visit() hits the native stack limit and flips sticky stack_overflow_.
  7. Execution returns to VisitBlock(), which still does replacement_ = node.
  8. Process() then moves to the 0; statement, but Visit(0;) immediately returns because the overflow flag is already set.
  9. So statements->Set(i, replacement_) replaces that earlier 0; statement with the old block still stored in replacement_.
  10. RewriteBody() can still return success, and later bytecode generation revisits the same FunctionLiteral*.

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 <[email protected]>
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)

View on issue tracker