From 937be829978b72f20deb2d6464e911a18dd56f2f Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Wed, 24 Jun 2026 20:37:19 +0200 Subject: [PATCH] Fix Safari dormant overhead in Live Debugger instrumentation The transform hoisted each instrumented function's captured parameters into a per-call arrow closure (`const $dd_eN = () => ({...})`). A nested closure that captures parameters forces JavaScriptCore (Safari and all iOS browsers) to heap-allocate the function's scope on every invocation -- even when probes are dormant and the closure is never created or called. V8 and SpiderMonkey sink that allocation; JavaScriptCore does not, so it dominated the dormant runtime overhead. The runtime benchmark measured ~8.9 ns/call of dormant overhead on Safari for a tiny function (vs ~0 on Chrome and ~1.6 on Firefox). Gating the closure behind the probe check does not help, because the captured scope is still allocated on every call. Instead, inline the captured-arguments object literal (`{a, b}`) directly at each probe-guarded call site. The object now lives inside the `if (probe)` / `probe ? ` guards, so dormant calls allocate nothing. The active path is unchanged: the same arguments object is built the same number of times when a probe fires. Safari per-call dormant overhead drops ~73% (8.9 -> 2.4 ns); the Hot workload drops from ~10 to ~7 ns. Chrome and Firefox are unchanged. Transform snapshots and the README/EXAMPLES output catalog are updated to match the new shape. --- packages/plugins/live-debugger/EXAMPLES.md | 86 ++++++++----------- packages/plugins/live-debugger/README.md | 7 +- .../live-debugger/src/transform/index.test.ts | 84 ++++++++---------- .../live-debugger/src/transform/index.ts | 20 ++--- 4 files changed, 87 insertions(+), 110 deletions(-) diff --git a/packages/plugins/live-debugger/EXAMPLES.md b/packages/plugins/live-debugger/EXAMPLES.md index 683310bff..5b0e183fe 100644 --- a/packages/plugins/live-debugger/EXAMPLES.md +++ b/packages/plugins/live-debugger/EXAMPLES.md @@ -7,8 +7,8 @@ plugin's [How it works](./README.md#how-it-works) section. A few conventions used throughout: - The injected globals (`$dd_probes`, `$dd_entry`, `$dd_return`, `$dd_throw`) are provided by the Browser Debugger SDK at runtime — see [Runtime requirements](./README.md#runtime-requirements). -- The per-function local helpers carry a numeric suffix (`$dd_p0`, `$dd_e0`, `$dd_rv0`); the index increments for each instrumented function in a file. -- The `$dd_e` parameter-capture helper is only emitted when the function has parameters. +- The per-function locals carry a numeric suffix (`$dd_p0`, `$dd_rv0`); the index increments for each instrumented function in a file. +- The captured-arguments object literal (e.g. `{a, b}`) is inlined directly at each probe-guarded call site and is only emitted when the function has parameters. It is built lazily — inside the `if (probe)` / `probe ? ` guards — so dormant calls allocate nothing. - Indentation below is reformatted for readability; the real output preserves the original source layout. ## Table of content @@ -48,13 +48,12 @@ function add(a, b) { // After function add(a, b) { const $dd_p0 = $dd_probes('src/utils.js;add'); - const $dd_e0 = () => ({a, b}); try { let $dd_rv0; - if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); + if ($dd_p0) $dd_entry($dd_p0, this, {a, b}); const sum = a + b; - return ($dd_rv0 = sum, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0(), {sum}) : $dd_rv0); - } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; } + return ($dd_rv0 = sum, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {a, b}, {sum}) : $dd_rv0); + } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {a, b}); throw e; } } ``` @@ -70,13 +69,12 @@ const double = (x) => x * 2; // After const double = (x) => { const $dd_p0 = $dd_probes('src/utils.js;double'); - const $dd_e0 = () => ({x}); try { - if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); + if ($dd_p0) $dd_entry($dd_p0, this, {x}); const $dd_rv0 = x * 2; - if ($dd_p0) $dd_return($dd_p0, $dd_rv0, this, $dd_e0()); + if ($dd_p0) $dd_return($dd_p0, $dd_rv0, this, {x}); return $dd_rv0; - } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; } + } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {x}); throw e; } }; ``` @@ -92,20 +90,19 @@ const getObj = (x) => ({key: x}); // After const getObj = (x) => { const $dd_p0 = $dd_probes('src/utils.js;getObj'); - const $dd_e0 = () => ({x}); try { - if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); + if ($dd_p0) $dd_entry($dd_p0, this, {x}); const $dd_rv0 = {key: x}; - if ($dd_p0) $dd_return($dd_p0, $dd_rv0, this, $dd_e0()); + if ($dd_p0) $dd_return($dd_p0, $dd_rv0, this, {x}); return $dd_rv0; - } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; } + } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {x}); throw e; } }; ``` ## Function with no parameters -With no parameters, no `$dd_e0` helper is generated and the receiver (`this`) is passed without -an arguments object. +With no parameters, no arguments object is generated and the receiver (`this`) is passed without +one. ```js // Before @@ -162,13 +159,12 @@ function log(msg) { // After function log(msg) { const $dd_p0 = $dd_probes('src/utils.js;log'); - const $dd_e0 = () => ({msg}); try { let $dd_rv0; - if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); + if ($dd_p0) $dd_entry($dd_p0, this, {msg}); console.log(msg); - if ($dd_p0) $dd_return($dd_p0, undefined, this, $dd_e0()); - } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; } + if ($dd_p0) $dd_return($dd_p0, undefined, this, {msg}); + } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {msg}); throw e; } } ``` @@ -189,17 +185,16 @@ function earlyExit(x) { // After function earlyExit(x) { const $dd_p0 = $dd_probes('src/utils.js;earlyExit'); - const $dd_e0 = () => ({x}); try { let $dd_rv0; - if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); + if ($dd_p0) $dd_entry($dd_p0, this, {x}); if (!x) { - if ($dd_p0) $dd_return($dd_p0, undefined, this, $dd_e0()); + if ($dd_p0) $dd_return($dd_p0, undefined, this, {x}); return; } console.log(x); - if ($dd_p0) $dd_return($dd_p0, undefined, this, $dd_e0()); - } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; } + if ($dd_p0) $dd_return($dd_p0, undefined, this, {x}); + } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {x}); throw e; } } ``` @@ -219,15 +214,14 @@ function abs(x) { // After function abs(x) { const $dd_p0 = $dd_probes('src/utils.js;abs'); - const $dd_e0 = () => ({x}); try { let $dd_rv0; - if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); + if ($dd_p0) $dd_entry($dd_p0, this, {x}); if (x < 0) { - return ($dd_rv0 = -x, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0()) : $dd_rv0); + return ($dd_rv0 = -x, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {x}) : $dd_rv0); } - return ($dd_rv0 = x, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0()) : $dd_rv0); - } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; } + return ($dd_rv0 = x, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {x}) : $dd_rv0); + } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {x}); throw e; } } ``` @@ -248,16 +242,15 @@ function sign(x) { // After function sign(x) { const $dd_p0 = $dd_probes('src/utils.js;sign'); - const $dd_e0 = () => ({x}); try { let $dd_rv0; - if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); + if ($dd_p0) $dd_entry($dd_p0, this, {x}); if (x > 0) { - return ($dd_rv0 = 1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0()) : $dd_rv0); + return ($dd_rv0 = 1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {x}) : $dd_rv0); } else { - return ($dd_rv0 = -1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0()) : $dd_rv0); + return ($dd_rv0 = -1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {x}) : $dd_rv0); } - } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; } + } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {x}); throw e; } } ``` @@ -280,16 +273,15 @@ function f(flag) { // After function f(flag) { const $dd_p0 = $dd_probes('src/utils.js;f'); - const $dd_e0 = () => ({flag}); try { let $dd_rv0; - if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); + if ($dd_p0) $dd_entry($dd_p0, this, {flag}); if (flag) { - return ($dd_rv0 = 1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0()) : $dd_rv0); + return ($dd_rv0 = 1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {flag}) : $dd_rv0); } const later = 2; - return ($dd_rv0 = later, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0(), {later}) : $dd_rv0); - } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; } + return ($dd_rv0 = later, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {flag}, {later}) : $dd_rv0); + } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {flag}); throw e; } } ``` @@ -355,13 +347,12 @@ class Widget extends Base { let $dd_t; ($dd_t = super(items.map((x) => { const $dd_p0 = $dd_probes('src/widget.js;@4:16:0'); - const $dd_e0 = () => ({x}); try { - if ($dd_p0) $dd_entry($dd_p0, $dd_t, $dd_e0()); + if ($dd_p0) $dd_entry($dd_p0, $dd_t, {x}); const $dd_rv0 = x * 2; - if ($dd_p0) $dd_return($dd_p0, $dd_rv0, $dd_t, $dd_e0()); + if ($dd_p0) $dd_return($dd_p0, $dd_rv0, $dd_t, {x}); return $dd_rv0; - } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, $dd_t, $dd_e0()); throw e; } + } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, $dd_t, {x}); throw e; } }))); } } @@ -419,12 +410,11 @@ class Widget extends Base { constructor(items) { const double = function(x) { const $dd_p0 = $dd_probes('src/widget.js;double'); - const $dd_e0 = () => ({x}); try { let $dd_rv0; - if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); - return ($dd_rv0 = x * 2, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0()) : $dd_rv0); - } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; } + if ($dd_p0) $dd_entry($dd_p0, this, {x}); + return ($dd_rv0 = x * 2, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {x}) : $dd_rv0); + } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {x}); throw e; } }; super(items.map(double)); } diff --git a/packages/plugins/live-debugger/README.md b/packages/plugins/live-debugger/README.md index 8f975a30e..c367cdee3 100644 --- a/packages/plugins/live-debugger/README.md +++ b/packages/plugins/live-debugger/README.md @@ -89,13 +89,12 @@ function add(a, b) { // After function add(a, b) { const $dd_p0 = $dd_probes('src/utils.js;add'); - const $dd_e0 = () => ({a, b}); try { let $dd_rv0; - if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); + if ($dd_p0) $dd_entry($dd_p0, this, {a, b}); const sum = a + b; - return ($dd_rv0 = sum, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0(), {sum}) : $dd_rv0); - } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; } + return ($dd_rv0 = sum, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {a, b}, {sum}) : $dd_rv0); + } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {a, b}); throw e; } } ``` diff --git a/packages/plugins/live-debugger/src/transform/index.test.ts b/packages/plugins/live-debugger/src/transform/index.test.ts index 5fd8341bf..48cf0bf92 100644 --- a/packages/plugins/live-debugger/src/transform/index.test.ts +++ b/packages/plugins/live-debugger/src/transform/index.test.ts @@ -232,9 +232,9 @@ describe('transformCode', () => { expect(validateSyntax(result.code, '/src/utils.ts')).toBeNull(); expect(result.code).toContain('constructor(items) {let $dd_t;'); expect(result.code).toContain('super(items.map((x) => {'); - expect(result.code).toContain('$dd_entry($dd_p0, $dd_t, $dd_e0())'); - expect(result.code).toContain('$dd_return($dd_p0, $dd_rv0, $dd_t, $dd_e0())'); - expect(result.code).toContain('$dd_throw($dd_p0, e, $dd_t, $dd_e0())'); + expect(result.code).toContain('$dd_entry($dd_p0, $dd_t, {x})'); + expect(result.code).toContain('$dd_return($dd_p0, $dd_rv0, $dd_t, {x})'); + expect(result.code).toContain('$dd_throw($dd_p0, e, $dd_t, {x})'); expect(result.code).not.toContain('$dd_entry($dd_p0, this'); expect(result.code).not.toContain('$dd_return($dd_p0, $dd_rv0, this'); expect(result.code).not.toContain('$dd_throw($dd_p0, e, this'); @@ -268,7 +268,7 @@ describe('transformCode', () => { }); expect(result.instrumentedCount).toBe(1); - expect(result.code).toContain('$dd_entry($dd_p0, this, $dd_e0())'); + expect(result.code).toContain('$dd_entry($dd_p0, this, {x})'); expect(result.code).not.toContain('let $dd_t'); }); @@ -288,7 +288,7 @@ describe('transformCode', () => { expect(result.instrumentedCount).toBe(1); expect(result.skippedUnsupportedCount).toBe(1); - expect(result.code).toContain('$dd_entry($dd_p0, this, $dd_e0())'); + expect(result.code).toContain('$dd_entry($dd_p0, this, {x})'); expect(result.code).not.toContain('let $dd_t'); }); }); @@ -398,14 +398,14 @@ describe('transformCode', () => { }); describe('local variable capture', () => { - it('should generate args helpers and inline local captures', () => { + it('should inline args and local captures', () => { const result = transformCode({ ...BASE_OPTIONS, code: 'function f(a, b) { const c = 1; return a + b + c; }', }); - expect(result.code).toMatch(/\$dd_e\d+ = \(\) => \(\{a, b\}\)/); - expect(result.code).toContain('$dd_return($dd_p0, $dd_rv0, this, $dd_e0(), {c})'); + expect(result.code).toContain('$dd_entry($dd_p0, this, {a, b})'); + expect(result.code).toContain('$dd_return($dd_p0, $dd_rv0, this, {a, b}, {c})'); }); it('should omit local captures when there are no locals', () => { @@ -414,8 +414,8 @@ describe('transformCode', () => { code: 'function f(a, b) { return a + b; }', }); - expect(result.code).toMatch(/\$dd_e\d+ = \(\) => \(\{a, b\}\)/); - expect(result.code).toContain('$dd_return($dd_p0, $dd_rv0, this, $dd_e0())'); + expect(result.code).toContain('$dd_entry($dd_p0, this, {a, b})'); + expect(result.code).toContain('$dd_return($dd_p0, $dd_rv0, this, {a, b})'); }); it('should omit both helpers when there are no params and no locals', () => { @@ -892,11 +892,10 @@ describe('transformCode', () => { expect(normalizeCode(result.code)).toBe( normalizeCode( "function add(a, b) {const $dd_p0 = $dd_probes('src/utils.ts;add');", - ' const $dd_e0 = () => ({a, b});', ' try {', ' let $dd_rv0;', - ' if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); return ($dd_rv0 = a + b, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0()) : $dd_rv0); ', - ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; }', + ' if ($dd_p0) $dd_entry($dd_p0, this, {a, b}); return ($dd_rv0 = a + b, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {a, b}) : $dd_rv0); ', + ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {a, b}); throw e; }', '}', ), ); @@ -911,11 +910,10 @@ describe('transformCode', () => { expect(normalizeCode(result.code)).toBe( normalizeCode( "function add(a, b) {const $dd_p0 = $dd_probes('src/utils.ts;add');", - ' const $dd_e0 = () => ({a, b});', ' try {', ' let $dd_rv0;', - ' if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); const sum = a + b; return ($dd_rv0 = sum, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0(), {sum}) : $dd_rv0); ', - ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; }', + ' if ($dd_p0) $dd_entry($dd_p0, this, {a, b}); const sum = a + b; return ($dd_rv0 = sum, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {a, b}, {sum}) : $dd_rv0); ', + ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {a, b}); throw e; }', '}', ), ); @@ -930,11 +928,10 @@ describe('transformCode', () => { expect(normalizeCode(result.code)).toBe( normalizeCode( "function f(flag) {const $dd_p0 = $dd_probes('src/utils.ts;f');", - ' const $dd_e0 = () => ({flag});', ' try {', ' let $dd_rv0;', - ' if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); if (flag) { return ($dd_rv0 = 1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0()) : $dd_rv0); } const later = 2; return ($dd_rv0 = later, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0(), {later}) : $dd_rv0); ', - ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; }', + ' if ($dd_p0) $dd_entry($dd_p0, this, {flag}); if (flag) { return ($dd_rv0 = 1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {flag}) : $dd_rv0); } const later = 2; return ($dd_rv0 = later, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {flag}, {later}) : $dd_rv0); ', + ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {flag}); throw e; }', '}', ), ); @@ -969,13 +966,12 @@ describe('transformCode', () => { normalizeCode( 'const double = (x) => {', " const $dd_p0 = $dd_probes('src/utils.ts;double');", - ' const $dd_e0 = () => ({x});', ' try {', - ' if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0());', + ' if ($dd_p0) $dd_entry($dd_p0, this, {x});', ' const $dd_rv0 = x * 2;', - ' if ($dd_p0) $dd_return($dd_p0, $dd_rv0, this, $dd_e0());', + ' if ($dd_p0) $dd_return($dd_p0, $dd_rv0, this, {x});', ' return $dd_rv0;', - ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; }', + ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {x}); throw e; }', '};', ), ); @@ -991,13 +987,12 @@ describe('transformCode', () => { normalizeCode( 'const getObj = (x) => {', " const $dd_p0 = $dd_probes('src/utils.ts;getObj');", - ' const $dd_e0 = () => ({x});', ' try {', - ' if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0());', + ' if ($dd_p0) $dd_entry($dd_p0, this, {x});', ' const $dd_rv0 = {key: x};', - ' if ($dd_p0) $dd_return($dd_p0, $dd_rv0, this, $dd_e0());', + ' if ($dd_p0) $dd_return($dd_p0, $dd_rv0, this, {x});', ' return $dd_rv0;', - ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; }', + ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {x}); throw e; }', '};', ), ); @@ -1023,13 +1018,12 @@ describe('transformCode', () => { ' constructor(items) {let $dd_t;', ' ($dd_t = super(items.map((x) => {', " const $dd_p0 = $dd_probes('src/utils.ts;@4:16:0');", - ' const $dd_e0 = () => ({x});', ' try {', - ' if ($dd_p0) $dd_entry($dd_p0, $dd_t, $dd_e0());', + ' if ($dd_p0) $dd_entry($dd_p0, $dd_t, {x});', ' const $dd_rv0 = x * 2;', - ' if ($dd_p0) $dd_return($dd_p0, $dd_rv0, $dd_t, $dd_e0());', + ' if ($dd_p0) $dd_return($dd_p0, $dd_rv0, $dd_t, {x});', ' return $dd_rv0;', - ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, $dd_t, $dd_e0()); throw e; }', + ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, $dd_t, {x}); throw e; }', ' })));', ' }', '}', @@ -1081,12 +1075,11 @@ describe('transformCode', () => { expect(normalizeCode(result.code)).toBe( normalizeCode( "function log(msg) {const $dd_p0 = $dd_probes('src/utils.ts;log');", - ' const $dd_e0 = () => ({msg});', ' try {', ' let $dd_rv0;', - ' if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); console.log(msg); ', - ' if ($dd_p0) $dd_return($dd_p0, undefined, this, $dd_e0());', - ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; }', + ' if ($dd_p0) $dd_entry($dd_p0, this, {msg}); console.log(msg); ', + ' if ($dd_p0) $dd_return($dd_p0, undefined, this, {msg});', + ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {msg}); throw e; }', '}', ), ); @@ -1101,11 +1094,10 @@ describe('transformCode', () => { expect(normalizeCode(result.code)).toBe( normalizeCode( "function abs(x) {const $dd_p0 = $dd_probes('src/utils.ts;abs');", - ' const $dd_e0 = () => ({x});', ' try {', ' let $dd_rv0;', - ' if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); if (x < 0) { return ($dd_rv0 = -x, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0()) : $dd_rv0); } return ($dd_rv0 = x, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0()) : $dd_rv0); ', - ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; }', + ' if ($dd_p0) $dd_entry($dd_p0, this, {x}); if (x < 0) { return ($dd_rv0 = -x, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {x}) : $dd_rv0); } return ($dd_rv0 = x, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {x}) : $dd_rv0); ', + ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {x}); throw e; }', '}', ), ); @@ -1120,12 +1112,11 @@ describe('transformCode', () => { expect(normalizeCode(result.code)).toBe( normalizeCode( "function earlyExit(x) {const $dd_p0 = $dd_probes('src/utils.ts;earlyExit');", - ' const $dd_e0 = () => ({x});', ' try {', ' let $dd_rv0;', - ' if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0()); if (!x) { if ($dd_p0) $dd_return($dd_p0, undefined, this, $dd_e0()); return; } console.log(x); ', - ' if ($dd_p0) $dd_return($dd_p0, undefined, this, $dd_e0());', - ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; }', + ' if ($dd_p0) $dd_entry($dd_p0, this, {x}); if (!x) { if ($dd_p0) $dd_return($dd_p0, undefined, this, {x}); return; } console.log(x); ', + ' if ($dd_p0) $dd_return($dd_p0, undefined, this, {x});', + ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {x}); throw e; }', '}', ), ); @@ -1148,17 +1139,16 @@ describe('transformCode', () => { expect(normalizeCode(result.code)).toBe( normalizeCode( "function sign(x) {const $dd_p0 = $dd_probes('src/utils.ts;sign');", - ' const $dd_e0 = () => ({x});', ' try {', ' let $dd_rv0;', - ' if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0());', + ' if ($dd_p0) $dd_entry($dd_p0, this, {x});', ' if (x > 0) {', - ' return ($dd_rv0 = 1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0()) : $dd_rv0);', + ' return ($dd_rv0 = 1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {x}) : $dd_rv0);', ' } else {', - ' return ($dd_rv0 = -1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0()) : $dd_rv0);', + ' return ($dd_rv0 = -1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, {x}) : $dd_rv0);', ' }', '', - ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; }', + ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, {x}); throw e; }', '}', ), ); diff --git a/packages/plugins/live-debugger/src/transform/index.ts b/packages/plugins/live-debugger/src/transform/index.ts index 79ed9ffe6..65ed1551f 100644 --- a/packages/plugins/live-debugger/src/transform/index.ts +++ b/packages/plugins/live-debugger/src/transform/index.ts @@ -463,7 +463,6 @@ function injectInstrumentation(s: MagicStringType, code: string, target: Functio directivesEnd, } = target; - const entryHelper = `$dd_e${probeIdx}`; const rvVarName = `$dd_rv${probeIdx}`; const receiverArg = useThisAlias ? '$dd_t' : 'this'; @@ -471,11 +470,11 @@ function injectInstrumentation(s: MagicStringType, code: string, target: Functio const hasParams = entryVarsList !== ''; - const argsArg = hasParams ? `, ${entryHelper}()` : ''; + const entryArgs = `{${entryVarsList}}`; + const argsArg = hasParams ? `, ${entryArgs}` : ''; const escapedFunctionId = escapeSingleQuotedJavaScriptString(functionId); const probeDecl = `const ${probeVarName} = $dd_probes('${escapedFunctionId}');`; - const entryHelperDecl = hasParams ? `const ${entryHelper} = () => ({${entryVarsList}});` : ''; const entryCall = `if (${probeVarName}) $dd_entry(${probeVarName}, ${receiverArg}${argsArg});`; const catchBlock = `catch(e) { if (${probeVarName}) $dd_throw(${probeVarName}, e, ${receiverArg}${argsArg}); throw e; }`; @@ -504,7 +503,6 @@ function injectInstrumentation(s: MagicStringType, code: string, target: Functio const prefix = [ '{', probeDecl, - entryHelperDecl, 'try {', entryCall, aliasesExpressionBodySuperCall @@ -514,7 +512,7 @@ function injectInstrumentation(s: MagicStringType, code: string, target: Functio .filter(Boolean) .join('\n'); - const returnCaptureArgs = getReturnCaptureArgs(entryHelper, hasParams, localVars, bodyEnd); + const returnCaptureArgs = getReturnCaptureArgs(entryArgs, hasParams, localVars, bodyEnd); let expressionSuffix = hasSequenceExpressionBody ? ');' : ';'; if (aliasesExpressionBodySuperCall) { expressionSuffix = hasSequenceExpressionBody ? '));' : ');'; @@ -543,7 +541,7 @@ function injectInstrumentation(s: MagicStringType, code: string, target: Functio } } else { // Block body function - const preamble = [probeDecl, entryHelperDecl, 'try {', `let ${rvVarName};`, entryCall] + const preamble = [probeDecl, 'try {', `let ${rvVarName};`, entryCall] .filter(Boolean) .join('\n'); @@ -553,7 +551,7 @@ function injectInstrumentation(s: MagicStringType, code: string, target: Functio // (its outro) and ends up before the postamble in the generated code. for (const ret of returns) { const returnCaptureArgs = getReturnCaptureArgs( - entryHelper, + entryArgs, hasParams, localVars, ret.start, @@ -602,7 +600,7 @@ function injectInstrumentation(s: MagicStringType, code: string, target: Functio // single boundary update for `}` covers it; this keeps the trailing // helper's source-map segment anchored to the closing brace too. const trailingReturnCaptureArgs = getReturnCaptureArgs( - entryHelper, + entryArgs, hasParams, localVars, bodyEnd, @@ -795,17 +793,17 @@ function isDerivedClassMethod(path: BabelPath, typesModule: BabelTypesModule): b } function getReturnCaptureArgs( - entryHelper: string, + entryArgs: string, hasParams: boolean, localVars: LocalVariableDeclaration[], exitPosition: number, ): string { const localsArg = getLocalCaptureArg(localVars, exitPosition); if (hasParams && localsArg) { - return `, ${entryHelper}(), ${localsArg}`; + return `, ${entryArgs}, ${localsArg}`; } if (hasParams) { - return `, ${entryHelper}()`; + return `, ${entryArgs}`; } if (localsArg) { return `, undefined, ${localsArg}`;