High patch-now ✔︎ verified CVSS 8.8 chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactObject corruption in V8
DescriptionObject corruption in V8
ComponentV8
Sub-componentParser
Bug ClassLogic Error
Action tierpatch-now — corruption primitive, web-reachable in default config (confirmed) — upheld by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIConfirmed
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker490642836
Fix commitc29f67627054 (v8/v8) +20/-13
CWECWE-120 (Buffer overflow)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedHyeonjun Ahn (@_deayzl)
Reported2026-03-08 (per vendor advisory)
Fix landed2026-03-13 (public git)
Disclosed2026-03-31
Patch gap18 days — fix public in source before the advisory

Background

`Processor`
The V8 AST rewriter class (AstVisitor<Processor>) in src/parsing/rewriter.cc that walks parsed statements and rewrites them so the last evaluated expression’s completion value is captured.
`CheckStackOverflow()`
A recursion guard that reports whether the interpreter’s C++ stack limit has been exceeded during the deep Visit()/Process() recursion over the AST.
`replacement_` / `is_set_`
Mutable Processor member state where each recursive visit records its rewritten node and whether a completion-value assignment was set, which the caller consumes immediately after the recursive call returns.
`VISIT_AND_RETURN_IF_STACK_OVERFLOW`
A macro that runs a recursive Visit() and then bails out of the current frame if CheckStackOverflow() is true, preventing consumption of half-computed state.

Root Cause Analysis

The recursive rewriting path in Processor (VisitBlock, VisitIfStatement, VisitDoWhileStatement, VisitSwitchStatement, VisitWithStatement, VisitSloppyBlockFunctionStatement, and the loop in Process) called Visit() or Process() on child nodes and then unconditionally consumed the resulting replacement_ and is_set_ members (e.g. statements->Set(i, replacement_) or node->set_then_statement(replacement_)). When a deeply nested AST triggered a stack overflow mid-recursion, the recursive call returned early without producing valid output, yet the caller still read and stored the stale or partially-computed replacement_, violating the invariant that state is only consumed after a fully-completed recursive visit.

The fix introduces PROCESS_AND_RETURN_IF_STACK_OVERFLOW and replaces the bare Visit()/Process() calls with the *_AND_RETURN_IF_STACK_OVERFLOW macros so each frame immediately returns after a detected overflow instead of consuming results. It also reorders the check in the rewriter entry point so processor.HasStackOverflow() is tested before processor.result_assigned() is consulted, returning std::nullopt before any assigned result is used. Together these ensure that once overflow is reported, no frame builds a rewritten AST node out of invalid intermediate state.

Key insight
The single mistake was consuming a recursive visit’s mutated replacement_/is_set_ state without first checking whether that visit had bailed out on stack overflow; the fix makes every recursive Visit()/Process() call return immediately on overflow so corrupted intermediate state is never woven into the rewritten AST.

Attack Path

  1. Craft deeply nested script An attacker supplies JavaScript with sufficiently deep nesting of blocks, if/switch/with/do-while statements to drive the Processor recursion toward the C++ stack limit.
  2. Trigger overflow mid-rewrite During completion-value rewriting a recursive Visit()/Process() call hits CheckStackOverflow(), returning without having produced a valid replacement_.
  3. Consume stale state The parent frame unconditionally stores the stale/partial replacement_ (e.g. via statements->Set or set_then_statement), splicing an inconsistent node into the AST.
  4. Produce corrupted AST The rewriter returns a result built from half-unwound recursion state, yielding a malformed AST that downstream compilation consumes.
  5. Achieve object corruption Later execution of the corrupted program state produces object corruption in V8, giving the attacker a memory-safety primitive from a single malicious script.

Impact Assessment

An attacker who can run arbitrary JavaScript in the renderer (e.g. a malicious web page) can produce object corruption in the V8 engine by forcing a stack overflow during AST rewriting. The only precondition is delivering a script with deeply nested statements to reach the recursion limit inside Processor, and the corruption occurs in the sandboxed renderer process. Such object corruption is a strong primitive that can be leveraged toward memory disclosure or control-flow hijacking within the renderer.

Changed Functions

FunctionChangeNotes
Processor
src/parsing/rewriter.cc
modified

Files Changed

  • src/parsing/rewriter.cc

Audit Directions

  • Recursive AST visitors
    Audit other AstVisitor subclasses that recurse and then read shared mutable state (like replacement_) after the recursive call, ensuring they bail out when CheckStackOverflow() or HasStackOverflow() is set.
  • Check-after-consume ordering
    Look for code that consumes a recursive result before testing its overflow/error flag; the error or overflow check must precede any use of the produced state.
  • Bare Visit/Process calls
    Grep for direct Visit()/Process() calls in parser and compiler passes that are not guarded by the *_AND_RETURN_IF_STACK_OVERFLOW macros and could splice partial state into an output structure.
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