Skip to content
Draft
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 completions/bun-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -4207,7 +4207,7 @@
},
{
"name": "inspect-brk",
"description": "Activate Bun's debugger, set breakpoint on first line of code and wait",
"description": "Activate Bun's debugger, wait for a connection and pause before the first line of code",
"hasValue": true,
"valueType": "val",
"required": false,
Expand Down
4 changes: 2 additions & 2 deletions completions/bun.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ _bun_run_completion() {
'--target[The intended execution environment for the bundle. "browser", "bun" or "node"]: :->target' \
'--inspect[Activate Bun'"'"'s Debugger]:inspect' \
'--inspect-wait[Activate Bun'"'"'s Debugger, wait for a connection before executing]:inspect-wait' \
'--inspect-brk[Activate Bun'"'"'s Debugger, set breakpoint on first line of code and wait]:inspect-brk' \
'--inspect-brk[Activate Bun'"'"'s Debugger, wait for a connection and pause before the first line of code]:inspect-brk' \
'--hot[Enable auto reload in bun'"'"'s JavaScript runtime]' \
'--watch[Automatically restart bun'"'"'s JavaScript runtime on file change]' \
'--no-install[Disable auto install in bun'"'"'s JavaScript runtime]' \
Expand Down Expand Up @@ -832,7 +832,7 @@ _bun_test_completion() {
'--target[The intended execution environment for the bundle. "browser", "bun" or "node"]: :->target' \
'--inspect[Activate Bun'"'"'s Debugger]:inspect' \
'--inspect-wait[Activate Bun'"'"'s Debugger, wait for a connection before executing]:inspect-wait' \
'--inspect-brk[Activate Bun'"'"'s Debugger, set breakpoint on first line of code and wait]:inspect-brk' \
'--inspect-brk[Activate Bun'"'"'s Debugger, wait for a connection and pause before the first line of code]:inspect-brk' \
'--watch[Automatically restart bun'"'"'s JavaScript runtime on file change]' \
'--timeout[Set the per-test timeout in milliseconds, default is 5000.]:timeout' \
'--update-snapshots[Update snapshot files]' \
Expand Down
4 changes: 3 additions & 1 deletion docs/runtime/debugger.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ Inspect in browser:

### `--inspect-brk`

The `--inspect-brk` flag behaves identically to `--inspect`, except it injects a breakpoint at the first line of the executed script. Use it to debug scripts that run quickly and exit immediately.
The `--inspect-brk` flag behaves identically to `--inspect`, except the code does not execute until a debugger attaches, and the debugger then pauses before the first statement of the executed script. Use it to debug scripts that run quickly and exit immediately.

The pause needs a debugger that has enabled breakpoints. A client of the protocol sends `Debugger.enable` and `Debugger.setBreakpointsActive` with `active: true` before `Inspector.initialized`. `debug.bun.sh` and the VS Code extension do this.

### `--inspect-wait`

Expand Down
2 changes: 1 addition & 1 deletion docs/snippets/cli/run.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ bun run <file or script>
</ParamField>

<ParamField path="--inspect-brk" type="string">
Activate Bun's debugger, set breakpoint on first line of code and wait
Activate Bun's debugger, wait for a connection and pause before the first line of code
</ParamField>

### Dependency &amp; Module Resolution
Expand Down
2 changes: 1 addition & 1 deletion scripts/build/deps/webkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* for local mode. Override via `--webkit-version=<hash>` to test a branch.
* From https://github.com/oven-sh/WebKit releases.
*/
export const WEBKIT_VERSION = "0c06faadf65bf8e8c8ad3a5a8aca83e1e9ed653f";
export const WEBKIT_VERSION = "autobuild-preview-pr-776-1fbd9966";

/**
* WebKit (JavaScriptCore) — the JS engine.
Expand Down
4 changes: 0 additions & 4 deletions src/bundler/transpiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,6 @@ pub struct ParseOptions<'a, 'b> {
pub virtual_source: Option<&'b bun_ast::Source>,
pub replace_exports: bun_collections::StringArrayHashMap<bun_ast::runtime::ReplaceableExport>,
pub inject_jest_globals: bool,
pub set_breakpoint_on_first_line: bool,
pub emit_decorator_metadata: bool,
pub experimental_decorators: bool,
pub use_define_for_class_fields: bool,
Expand Down Expand Up @@ -1496,8 +1495,6 @@ impl<'a> Transpiler<'a> {
opts.features.standard_decorators = !loader.is_typescript()
|| !(this_parse.experimental_decorators || this_parse.emit_decorator_metadata);
opts.features.allow_runtime = self.options.allow_runtime;
opts.features.set_breakpoint_on_first_line =
this_parse.set_breakpoint_on_first_line;
opts.features.trim_unused_imports = self
.options
.trim_unused_imports
Expand Down Expand Up @@ -2861,7 +2858,6 @@ impl<'a> Transpiler<'a> {
virtual_source: None,
replace_exports: Default::default(),
inject_jest_globals: false,
set_breakpoint_on_first_line: false,
remove_cjs_module_wrapper: false,
dont_bundle_twice: false,
allow_commonjs: false,
Expand Down
13 changes: 0 additions & 13 deletions src/js_parser/parse/parse_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,6 @@ impl<'a> Options<'a> {
minify_keep_names: f.minify_keep_names,
minify_whitespace: f.minify_whitespace,
dead_code_elimination: f.dead_code_elimination,
set_breakpoint_on_first_line: f.set_breakpoint_on_first_line,
trim_unused_imports: f.trim_unused_imports,
auto_polyfill_require: f.auto_polyfill_require,
replace_exports: Default::default(),
Expand Down Expand Up @@ -1180,18 +1179,6 @@ impl<'a> Parser<'a> {
before.push(js_ast::Part::default());
}

// --inspect-brk
if p.options.features.set_breakpoint_on_first_line {
let debugger_stmts = p.arena.alloc_slice_fill_with(1, |_| Stmt {
data: js_ast::StmtData::SDebugger(Default::default()),
loc: bun_ast::Loc::EMPTY,
});
before.push(js_ast::Part {
stmts: debugger_stmts.into(),
..Default::default()
});
}

// When "using" declarations appear at the top level, we change all TDZ
// variables in the top-level scope into "var" so that they aren't harmed
// when they are moved into the try/catch statement that lowering will
Expand Down
6 changes: 1 addition & 5 deletions src/js_parser/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,6 @@ pub mod Runtime {
pub minify_whitespace: bool,
pub dead_code_elimination: bool,

pub set_breakpoint_on_first_line: bool,

pub trim_unused_imports: bool,

/// Allow runtime usage of require(), converting `require` into `__require`
Expand Down Expand Up @@ -293,7 +291,6 @@ pub mod Runtime {
minify_keep_names: false,
minify_whitespace: false,
dead_code_elimination: true,
set_breakpoint_on_first_line: false,
trim_unused_imports: false,
auto_polyfill_require: false,
replace_exports: ReplaceableExportMap::default(),
Expand Down Expand Up @@ -359,7 +356,7 @@ pub mod Runtime {
pub(crate) fn hash_for_runtime_transpiler(&self, hasher: &mut Wyhash) {
debug_assert!(self.runtime_transpiler_cache.is_some());

let bools: [bool; 17] = [
let bools: [bool; 16] = [
self.top_level_await,
self.auto_import_jsx,
self.allow_runtime,
Expand All @@ -369,7 +366,6 @@ pub mod Runtime {
self.minify_identifiers,
self.minify_keep_names,
self.dead_code_elimination,
self.set_breakpoint_on_first_line,
self.trim_unused_imports,
self.dont_bundle_twice,
self.commonjs_at_runtime,
Expand Down
37 changes: 37 additions & 0 deletions src/jsc/Debugger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,10 @@ pub struct Debugger {
pub poll_ref: KeepAlive,
pub wait_for_connection: Wait,
// wait_for_connection: bool = false,
/// Taken by the first entry load of the process (`schedule_pause_at_entry`).
pub set_breakpoint_on_first_line: bool,
/// JSC holds a request to pause when the entry starts to run.
pub pause_at_entry_scheduled: bool,
pub mode: Mode,
pub protocol: Protocol,

Expand All @@ -144,6 +147,7 @@ impl Default for Debugger {
poll_ref: KeepAlive::default(),
wait_for_connection: Wait::Off,
set_breakpoint_on_first_line: false,
pause_at_entry_scheduled: false,
mode: Mode::Listen,
protocol: Protocol::Jsc,
test_reporter_agent: TestReporterAgent::default(),
Expand All @@ -169,6 +173,12 @@ unsafe extern "C" {
is_connect: bool,
is_node_inspector: bool,
);
safe fn Debugger__schedulePauseAtEntry(
global: &JSGlobalObject,
url: &BunString,
program_code: bool,
) -> bool;
safe fn Debugger__cancelPauseAtEntry(global: &JSGlobalObject);
}

static FUTEX_ATOMIC: AtomicU32 = AtomicU32::new(0);
Expand All @@ -186,6 +196,33 @@ struct DebuggerThreadInit {
}

impl Debugger {
/// Asks JSC to pause when the top-level code of `vm.main()` starts to run.
pub(crate) fn schedule_pause_at_entry(vm: &VirtualMachine) {
let asked = vm
.debugger_mut()
.is_some_and(|dbg| core::mem::take(&mut dbg.set_breakpoint_on_first_line));
if !asked {
return;
}
let url = BunString::from_bytes(vm.main());
// The code of `-e` and stdin runs as a program, not as a module or a CommonJS wrapper.
let program_code = vm.module_loader.eval_source.is_some();
let scheduled = Debugger__schedulePauseAtEntry(vm.global(), &url, program_code);
if let Some(dbg) = vm.debugger_mut() {
dbg.pause_at_entry_scheduled = scheduled;
}
}

/// Takes back the request of an earlier load whose entry did not run.
pub(crate) fn cancel_pause_at_entry(vm: &VirtualMachine) {
let scheduled = vm
.debugger_mut()
.is_some_and(|dbg| core::mem::take(&mut dbg.pause_at_entry_scheduled));
if scheduled {
Debugger__cancelPauseAtEntry(vm.global());
}
}

/// `Debugger.waitForDebuggerIfNecessary(vm)` — block on the futex until
/// `start()` (debugger thread) signals, then run the wait-loop until a
/// frontend connects (`Debugger__didConnect`) or the deadline elapses.
Expand Down
3 changes: 2 additions & 1 deletion src/jsc/RuntimeTranspilerCache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ bun_core::declare_scope!(cache, visible);
/// `onResolve` rewrote (`namespace:path`). Older entries request the bare path, and
/// the cache-HIT path reinstates #33904 for them.
/// Version 34: An import that a plugin `onResolve` answers is printed as it is written.
const EXPECTED_VERSION: u32 = 34;
/// Version 35: The features hash has no slot for the `--inspect-brk` statement, which the parser no longer adds.
const EXPECTED_VERSION: u32 = 35;

/// Source files smaller than this are not written to / read from the on-disk
/// transpiler cache. Originally 50 KiB, which excluded almost every file in a
Expand Down
18 changes: 1 addition & 17 deletions src/jsc/RuntimeTranspilerStore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use core::cell::Cell;
use core::ffi::c_void;
use core::ptr::{self, NonNull};
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use core::sync::atomic::{AtomicU32, Ordering};

use bun_alloc::Arena;
use bun_ast::Loader;
Expand Down Expand Up @@ -182,15 +182,6 @@ fn dump_source_string_failiable(
Ok(())
}

// `pub`: also consumed cross-crate by bun_runtime's ModuleLoader transpile path
// (runtime/jsc_hooks.rs). The one-shot AtomicBool
// must stay shared between that path and this store — whichever transpiles the
// main module first consumes it.
pub fn set_break_point_on_first_line() -> bool {
static SET_BREAK_POINT: AtomicBool = AtomicBool::new(true);
SET_BREAK_POINT.swap(false, Ordering::SeqCst)
}

// ──────────────────────────────────────────────────────────────────────────
// RuntimeTranspilerStore
// ──────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -823,13 +814,6 @@ impl TranspilerJob {
dont_bundle_twice: true,
allow_commonjs: true,
inject_jest_globals: transpiler.options.rewrite_jest_for_tests,
// SAFETY: leaf-field `&` borrow on `*vm.debugger`; see `vm` note above.
set_breakpoint_on_first_line: unsafe { &(*vm).debugger }
.as_ref()
.map(|d| d.set_breakpoint_on_first_line)
.unwrap_or(false)
&& is_main
&& set_break_point_on_first_line(),
runtime_transpiler_cache: if !JscRuntimeTranspilerCache::is_disabled() {
// SAFETY: `cache` is a stack local declared above and outlives
// `parse_options`; `addr_of_mut!` avoids an intermediate `&mut`
Expand Down
6 changes: 6 additions & 0 deletions src/jsc/VirtualMachine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3409,6 +3409,9 @@ impl VirtualMachine {

let hooks = runtime_hooks();
let _ = self.ensure_debugger(true);
// Before the preloads: one of them can import the entry.
crate::debugger::Debugger::cancel_pause_at_entry(self);
crate::debugger::Debugger::schedule_pause_at_entry(self);

// Node.js `--trace-*` and `--stack-trace-limit` flags need
// `internal/process/pre_execution` to run before any user code.
Expand Down Expand Up @@ -5627,6 +5630,9 @@ impl VirtualMachine {
self.event_loop_mut().ensure_waker();

let _ = self.ensure_debugger(true);
// As in `reload_entry_point`.
crate::debugger::Debugger::cancel_pause_at_entry(self);
crate::debugger::Debugger::schedule_pause_at_entry(self);

if !self.transpiler.options.disable_transpilation {
if let Some(hooks) = runtime_hooks() {
Expand Down
13 changes: 13 additions & 0 deletions src/jsc/bindings/BunDebugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,19 @@ extern "C" void Debugger__willDispatchAsyncCall(JSGlobalObject* globalObject, As
agent->willDispatchAsyncCall(getCallType(callType), callbackId);
}

// False when no frontend enabled the debugger with breakpoints active: nothing can pause then.
extern "C" bool Debugger__schedulePauseAtEntry(JSGlobalObject* globalObject, const BunString* url, bool programCode)
{
auto* agent = debuggerAgent(globalObject);
return agent && agent->schedulePauseAtEntry(url->toWTFString(), programCode);
}

extern "C" void Debugger__cancelPauseAtEntry(JSGlobalObject* globalObject)
{
if (auto* agent = debuggerAgent(globalObject))
agent->cancelPauseAtEntry();
}

extern "C" void Bun__InspectorConnection__disconnectAllOnExit(Zig::GlobalObject* globalObject)
{
// Snapshot under the lock, release before calling into the inspector —
Expand Down
2 changes: 0 additions & 2 deletions src/runtime/api/JSTranspiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -783,7 +783,6 @@ impl TransformTask {
macro_js_ctx: MacroJSCtx::ZERO,
file_fd_ptr: None,
inject_jest_globals: false,
set_breakpoint_on_first_line: false,
remove_cjs_module_wrapper: false,
dont_bundle_twice: false,
allow_commonjs: false,
Expand Down Expand Up @@ -1246,7 +1245,6 @@ impl JSTranspiler {
.unwrap_or(true),
file_fd_ptr: None,
inject_jest_globals: false,
set_breakpoint_on_first_line: false,
remove_cjs_module_wrapper: false,
dont_bundle_twice: false,
allow_commonjs: false,
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/cli/Arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ const RUNTIME_PARAMS_: &[ParamType] = &[
"--inspect-wait <STR>? Activate Bun's debugger, wait for a connection before executing"
),
parse_param!(
"--inspect-brk <STR>? Activate Bun's debugger, set breakpoint on first line of code and wait"
"--inspect-brk <STR>? Activate Bun's debugger, wait for a connection and pause before the first line of code"
),
parse_param!(
"--cpu-prof Start CPU profiler and write profile to disk on exit"
Expand Down
13 changes: 0 additions & 13 deletions src/runtime/jsc_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2475,18 +2475,6 @@ fn transpile_source_code_inner(
use bun_bundler::transpiler::{AlreadyBundled, ParseOptions, ParseResult};
use bun_jsc::resolved_source::Tag as ResolvedSourceTag;

// Keep the one-shot
// `set_break_point_on_first_line()` last so it is only
// consumed when the other conditions hold.
// SAFETY: per fn contract — leaf-field `&` borrow on
// `(*jsc_vm).debugger` (same shape as RuntimeTranspilerStore).
let set_breakpoint_on_first_line = is_main
&& unsafe { &(*jsc_vm).debugger }
.as_ref()
.map(|d| d.set_breakpoint_on_first_line)
.unwrap_or(false)
&& bun_jsc::runtime_transpiler_store::set_break_point_on_first_line();

// `ParseOptions::path` is `bun_paths::fs::Path<'static>`
// (the `'static`-slice flavour used by `bun_ast::Source`), but
// `path` here is `bun_resolver::fs::Path<'_>`. The two structs
Expand Down Expand Up @@ -2582,7 +2570,6 @@ fn transpile_source_code_inner(
},
keep_json_and_toml_as_one_statement: true,
allow_bytecode_cache: true,
set_breakpoint_on_first_line,
runtime_transpiler_cache: if !disable_transpilying
&& !<RuntimeTranspilerCache as bun_bundler::RuntimeTranspilerCacheExt>::disabled()
{
Expand Down
Loading
Loading