Files
v8/test/debugger/debug/compiler/loop-interrupt-check.js
Darius Mercadier 73b4712d0f [debug] Deoptimize topmost optimized frame on debugger break
When the debugger pauses, it can execute arbitrary JS (e.g. via
evaluation or inspector listeners). This JS can modify heap state,
such as detaching ArrayBuffers.

If the execution stack contains optimized frames, they might be using
load-eliminated values (like backing store pointers or array lengths)
that are now invalidated by the heap modifications.

To prevent use-after-free or out-of-bounds accesses when resuming,
deoptimize the topmost optimized JS frame when handling a debug break.
We only need to deoptimize the topmost frame because any caller frames
are at a call site, which acts as a memory serialization barrier,
forcing them to reload all heap state upon return anyway.

TAG=agy
CONV=be1ec857-0c4b-479c-ba1b-e84d8741da83

Bug: 531297707
Change-Id: I5e46963660eaa228a68422490e7495ec6f0d9f8a
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8131283
Auto-Submit: Darius Mercadier <dmercadier@chromium.org>
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#108890}
2026-07-27 05:13:33 -07:00

64 lines
2.0 KiB
JavaScript

// Copyright 2024 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.
// Flags: --allow-natives-syntax --turbolev --turbofan
// Flags: --no-maglev --no-stress-concurrent-inlining
// Flags: --invocation-count-for-turbofan=3000
// TODO(dmercadier): Investigate why loop peeling is breaking this test.
// Flags: --no-turbolev-non-eager-loop-peeling
// This test checks that loop interrupt stack checks are properly detected are
// processed.
// Note that interrupt requests are not handled at the same time in Maglev and
// Turbofan: in Maglev, they are not directly checked but we instead rely on the
// fact budget interrupt will perform stack check interrupt, which in Turbofan,
// they are checked at every loop iteration.
var Debug = debug.Debug;
var trigger_count = 0;
var called_from;
Debug.setListener(function (event, exec_state, event_data, data) {
if (event == Debug.DebugEvent.Break) {
called_from = exec_state.frames[0].functionName;
trigger_count += 1;
}
});
function g(x) {
if (x == 0) {
%ScheduleBreak();
}
return 7;
}
function loop_interrupt_check_f(x) {
let r = 0;
for (let i = 0; i < 10; i++) {
if (i == 0) {
// Schedules an interrupt if {x} is 0.
r += g(x);
} else {
r += g(1);
}
}
return r;
}
%PrepareFunctionForOptimization(loop_interrupt_check_f);
assertEquals(70, loop_interrupt_check_f(1));
%OptimizeFunctionOnNextCall(loop_interrupt_check_f);
assertEquals(70, loop_interrupt_check_f(1));
assertOptimized(loop_interrupt_check_f);
assertEquals(0, trigger_count);
assertEquals(70, loop_interrupt_check_f(0));
assertEquals(1, trigger_count);
assertEquals("loop_interrupt_check_f", called_from);
// We expect the function to be deoptimized because the debugger pause
// deoptimizes the topmost frame to prevent it from using stale load-eliminated
// values in case the debugger executes JS that modifies heap state.
assertUnoptimized(loop_interrupt_check_f);