From 02e2bcc89d95aadb50a08d7db34b71a0cbec3d26 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Thu, 30 Apr 2026 06:44:07 +0200 Subject: [PATCH] fix(live-debugger): exclude params from locals in instrumented code The locals helper passed to $dd_return included function parameters alongside actual local variables, causing the snapshot to duplicate argument values in both the arguments and locals sections. Change exitVars to only collect local variable declarations (not params), and omit the args/locals helpers entirely when they would be empty to reduce instrumented bundle size. --- .../live-debugger/src/transform/index.test.ts | 77 +++++++++++++++---- .../live-debugger/src/transform/index.ts | 31 +++++--- 2 files changed, 80 insertions(+), 28 deletions(-) diff --git a/packages/plugins/live-debugger/src/transform/index.test.ts b/packages/plugins/live-debugger/src/transform/index.test.ts index 670d5535d..d2fa18b3d 100644 --- a/packages/plugins/live-debugger/src/transform/index.test.ts +++ b/packages/plugins/live-debugger/src/transform/index.test.ts @@ -257,27 +257,33 @@ describe('transformCode', () => { }); describe('hoisted variable capture', () => { - it('should generate entry and exit helper functions', () => { + it('should generate args and locals helpers', () => { const result = transformCode({ ...BASE_OPTIONS, code: 'function f(a, b) { const c = 1; return a + b + c; }', }); - // Entry helper captures params only expect(result.code).toMatch(/\$dd_e\d+ = \(\) => \(\{a, b\}\)/); - // Exit helper captures params + locals - expect(result.code).toMatch(/\$dd_l\d+ = \(\) => \(\{a, b, c\}\)/); + expect(result.code).toMatch(/\$dd_l\d+ = \(\) => \(\{c\}\)/); }); - it('should emit a single shared helper when entry and exit vars are identical', () => { + it('should omit the locals helper when there are no locals', () => { const result = transformCode({ ...BASE_OPTIONS, code: 'function f(a, b) { return a + b; }', }); - // Only the entry helper should be emitted expect(result.code).toMatch(/\$dd_e\d+ = \(\) => \(\{a, b\}\)/); - // No exit helper should be present + expect(result.code).not.toMatch(/\$dd_l\d+/); + }); + + it('should omit both helpers when there are no params and no locals', () => { + const result = transformCode({ + ...BASE_OPTIONS, + code: 'function f() { return 1; }', + }); + + expect(result.code).not.toMatch(/\$dd_e\d+/); expect(result.code).not.toMatch(/\$dd_l\d+/); }); }); @@ -668,6 +674,43 @@ describe('transformCode', () => { return lines.map((s) => s.trimStart()).join('\n'); } + it('should produce the expected output for a function with no arguments', () => { + const result = transformCode({ + ...BASE_OPTIONS, + code: 'function getTime() { return Date.now(); }', + }); + + expect(normalizeCode(result.code)).toBe( + normalizeCode( + "function getTime() {const $dd_p0 = $dd_probes('src/utils.ts;getTime');", + ' try {', + ' let $dd_rv0;', + ' if ($dd_p0) $dd_entry($dd_p0, this); return ($dd_rv0 = Date.now(), $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this) : $dd_rv0); ', + ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this); throw e; }', + '}', + ), + ); + }); + + it('should produce the expected output for a function with no arguments but with locals', () => { + const result = transformCode({ + ...BASE_OPTIONS, + code: 'function getTime() { const now = Date.now(); return now; }', + }); + + expect(normalizeCode(result.code)).toBe( + normalizeCode( + "function getTime() {const $dd_p0 = $dd_probes('src/utils.ts;getTime');", + ' try {', + ' const $dd_l0 = () => ({now});', + ' let $dd_rv0;', + ' if ($dd_p0) $dd_entry($dd_p0, this); const now = Date.now(); return ($dd_rv0 = now, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, undefined, $dd_l0()) : $dd_rv0); ', + ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this); throw e; }', + '}', + ), + ); + }); + it('should produce the expected output for a function with a single return', () => { const result = transformCode({ ...BASE_OPTIONS, @@ -680,7 +723,7 @@ describe('transformCode', () => { ' 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_e0()) : $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; }', '}', ), @@ -698,7 +741,7 @@ describe('transformCode', () => { "function add(a, b) {const $dd_p0 = $dd_probes('src/utils.ts;add');", ' const $dd_e0 = () => ({a, b});', ' try {', - ' const $dd_l0 = () => ({a, b, sum});', + ' const $dd_l0 = () => ({sum});', ' 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(), $dd_l0()) : $dd_rv0); ', ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; }', @@ -721,7 +764,7 @@ describe('transformCode', () => { ' try {', ' if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0());', ' const $dd_rv0 = x * 2;', - ' if ($dd_p0) $dd_return($dd_p0, $dd_rv0, this, $dd_e0(), $dd_e0());', + ' if ($dd_p0) $dd_return($dd_p0, $dd_rv0, this, $dd_e0());', ' return $dd_rv0;', ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; }', '};', @@ -743,7 +786,7 @@ describe('transformCode', () => { ' try {', ' if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0());', ' const $dd_rv0 = {key: x};', - ' if ($dd_p0) $dd_return($dd_p0, $dd_rv0, this, $dd_e0(), $dd_e0());', + ' if ($dd_p0) $dd_return($dd_p0, $dd_rv0, this, $dd_e0());', ' return $dd_rv0;', ' } catch(e) { if ($dd_p0) $dd_throw($dd_p0, e, this, $dd_e0()); throw e; }', '};', @@ -764,7 +807,7 @@ describe('transformCode', () => { ' 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(), $dd_e0());', + ' 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; }', '}', ), @@ -783,7 +826,7 @@ describe('transformCode', () => { ' 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_e0()) : $dd_rv0); } return ($dd_rv0 = x, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0(), $dd_e0()) : $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; }', '}', ), @@ -802,8 +845,8 @@ describe('transformCode', () => { ' 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(), $dd_e0()); return; } console.log(x); ', - ' if ($dd_p0) $dd_return($dd_p0, undefined, this, $dd_e0(), $dd_e0());', + ' 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; }', '}', ), @@ -832,9 +875,9 @@ describe('transformCode', () => { ' let $dd_rv0;', ' if ($dd_p0) $dd_entry($dd_p0, this, $dd_e0());', ' if (x > 0) {', - ' return ($dd_rv0 = 1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0(), $dd_e0()) : $dd_rv0);', + ' return ($dd_rv0 = 1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0()) : $dd_rv0);', ' } else {', - ' return ($dd_rv0 = -1, $dd_p0 ? $dd_return($dd_p0, $dd_rv0, this, $dd_e0(), $dd_e0()) : $dd_rv0);', + ' return ($dd_rv0 = -1, $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; }', diff --git a/packages/plugins/live-debugger/src/transform/index.ts b/packages/plugins/live-debugger/src/transform/index.ts index 7aa934669..82dd11596 100644 --- a/packages/plugins/live-debugger/src/transform/index.ts +++ b/packages/plugins/live-debugger/src/transform/index.ts @@ -282,7 +282,7 @@ export function transformCode(options: TransformOptions): TransformResult { const idx = probeVarCounter++; const probeVarName = `$dd_p${idx}`; const entryVars = getVariableNames(node, true, false, babelTypes); - const exitVars = getVariableNames(node, true, true, babelTypes); + const exitVars = getVariableNames(node, false, true, babelTypes); const isExpressionBody = babelTypes.isArrowFunctionExpression(node) && @@ -399,16 +399,25 @@ function injectInstrumentation(s: MagicStringType, code: string, target: Functio const entryVarsList = entryVars.join(', '); const exitVarsList = exitVars.join(', '); - const shared = entryVarsList === exitVarsList; - const snapshotHelper = shared ? entryHelper : exitHelper; + const hasParams = entryVarsList !== ''; + const hasLocals = exitVarsList !== ''; + + const argsArg = hasParams ? `, ${entryHelper}()` : ''; + const returnArgsAndLocals = hasParams + ? hasLocals + ? `, ${entryHelper}(), ${exitHelper}()` + : `, ${entryHelper}()` + : hasLocals + ? `, undefined, ${exitHelper}()` + : ''; // TODO: functionId is not escaped — if it contains a single quote (e.g. quoted method names), // the generated code will be invalid. Escaping is not currently supported. const probeDecl = `const ${probeVarName} = $dd_probes('${functionId}');`; - const entryHelperDecl = `const ${entryHelper} = () => ({${entryVarsList}});`; - const exitHelperDecl = shared ? '' : `const ${exitHelper} = () => ({${exitVarsList}});`; - const entryCall = `if (${probeVarName}) $dd_entry(${probeVarName}, this, ${entryHelper}());`; - const catchBlock = `catch(e) { if (${probeVarName}) $dd_throw(${probeVarName}, e, this, ${entryHelper}()); throw e; }`; + const entryHelperDecl = hasParams ? `const ${entryHelper} = () => ({${entryVarsList}});` : ''; + const exitHelperDecl = hasLocals ? `const ${exitHelper} = () => ({${exitVarsList}});` : ''; + const entryCall = `if (${probeVarName}) $dd_entry(${probeVarName}, this${argsArg});`; + const catchBlock = `catch(e) { if (${probeVarName}) $dd_throw(${probeVarName}, e, this${argsArg}); throw e; }`; if (isExpressionBody) { // Arrow expression body: (a) => expr @@ -447,7 +456,7 @@ function injectInstrumentation(s: MagicStringType, code: string, target: Functio const suffix = [ ';', - `if (${probeVarName}) $dd_return(${probeVarName}, ${rvVarName}, this, ${entryHelper}(), ${snapshotHelper}());`, + `if (${probeVarName}) $dd_return(${probeVarName}, ${rvVarName}, this${returnArgsAndLocals});`, `return ${rvVarName};`, `} ${catchBlock}`, '}', @@ -472,7 +481,7 @@ function injectInstrumentation(s: MagicStringType, code: string, target: Functio const postambleParts = ['']; if (target.needsTrailingReturn) { postambleParts.push( - `if (${probeVarName}) $dd_return(${probeVarName}, undefined, this, ${entryHelper}(), ${snapshotHelper}());`, + `if (${probeVarName}) $dd_return(${probeVarName}, undefined, this${returnArgsAndLocals});`, ); } postambleParts.push(`} ${catchBlock}`, ''); @@ -490,13 +499,13 @@ function injectInstrumentation(s: MagicStringType, code: string, target: Functio s.appendLeft(ret.argStart, `(${rvVarName} = `); s.appendLeft( ret.argEnd, - `, ${probeVarName} ? $dd_return(${probeVarName}, ${rvVarName}, this, ${entryHelper}(), ${snapshotHelper}()) : ${rvVarName})`, + `, ${probeVarName} ? $dd_return(${probeVarName}, ${rvVarName}, this${returnArgsAndLocals}) : ${rvVarName})`, ); } else { // return; → if (probe) $dd_return(...); return; s.appendLeft( ret.start, - `if (${probeVarName}) $dd_return(${probeVarName}, undefined, this, ${entryHelper}(), ${snapshotHelper}()); `, + `if (${probeVarName}) $dd_return(${probeVarName}, undefined, this${returnArgsAndLocals}); `, ); } }