Describe the bug
The loops compressor replaces a do...while(false) statement with its body even when that body contains a direct break or continue.
Those abrupt completions target the do loop. Once the loop is removed, they can target an enclosing loop and silently change program behavior. Without an enclosing target, the optimization can instead emit an illegal jump statement.
The bug does not require source labels. In the downstream Kotlin/JS case, SWC first normalizes a labeled jump such as break inner to an unlabeled break while the loop still exists; a later compression pass then removes that target loop. The unlabeled reproduction below demonstrates the same underlying issue directly.
Input code
function run() {
const trace = [];
for (let i = 0; i < 2; i++) {
do {
trace.push("body" + i);
break;
} while (false);
trace.push("tail" + i);
}
return trace.join(",");
}
console.log(run());
Config
// @swc/core minifySync options
{
"compress": {
"dead_code": true,
"evaluate": true,
"loops": true,
"passes": 2
},
"mangle": false
}
Link to the code that reproduces this issue
https://github.com/August-Z/swc/tree/aca52cb768faf2f649e0cc076b6a053cebc4b6b1/crates/swc_ecma_minifier/tests/fixture/issues/do-while-false-control-flow
SWC Info output
Operating System:
Platform: darwin
Arch: arm64
Machine Type: arm64
Version: Darwin Kernel Version 25.0.0
CPU: Apple M3 Pro (12 cores)
Binaries:
Node: 26.3.0
npm: 11.16.0
Yarn: 1.22.22
pnpm: 11.19.0
Relevant Packages:
@swc/core: 1.16.1 (verified in an isolated npm install)
@swc/helpers: N/A
@swc/types: N/A
SWC Config:
API: minifySync
.swcrc path: N/A
Expected behavior
The break must continue to target the inner do...while loop. Executing the transformed code should print:
Actual behavior
The inner loop is removed and its break is retargeted to the outer for loop. With @swc/core@1.16.1, the output is:
function run(){let trace=[];for(let i=0;i<2;i++){trace.push("body"+i);break}return trace.join(",")}console.log(run());
It prints only body0.
Version
@swc/core 1.16.1; also reproduced on swc main at 5b8c98d
Additional context
This was first observed downstream in web-infra-dev/rspack#15335. Kotlin/JS 1.7.20 emits a labeled single-iteration do...while(false) inside a suspend-function state-machine loop. After compression, the inner jump exits the outer state-machine loop, so later code is removed and the function returns undefined.
The behavior is independently reproducible with @swc/core and does not require Rspack, labels, or mangling. A direct continue has the analogous control-target problem. Without an enclosing loop, function f(){do{break;}while(false);return 42} can be reduced to invalid code equivalent to function f(){break}.
On current main (commit 5b8c98d909b4c5db2730877f0be09182ee470a26, swc_ecma_minifier 61.0.3), the affected path is optimize_loops_with_constant_condition in crates/swc_ecma_minifier/src/compress/pure/loops.rs. It calls should_not_inline_loop_body(&stmt.body, true), allowing the direct jump while removing its target.
Disabling the loops compressor option is the narrow reliable workaround.
SWC previously added a conservative guard for the separate simplify transform in #1618. This report concerns the minifier compressor's independent constant-loop optimization.
Describe the bug
The
loopscompressor replaces ado...while(false)statement with its body even when that body contains a directbreakorcontinue.Those abrupt completions target the
doloop. Once the loop is removed, they can target an enclosing loop and silently change program behavior. Without an enclosing target, the optimization can instead emit an illegal jump statement.The bug does not require source labels. In the downstream Kotlin/JS case, SWC first normalizes a labeled jump such as
break innerto an unlabeledbreakwhile the loop still exists; a later compression pass then removes that target loop. The unlabeled reproduction below demonstrates the same underlying issue directly.Input code
Config
Link to the code that reproduces this issue
https://github.com/August-Z/swc/tree/aca52cb768faf2f649e0cc076b6a053cebc4b6b1/crates/swc_ecma_minifier/tests/fixture/issues/do-while-false-control-flow
SWC Info output
Operating System:
Platform: darwin
Arch: arm64
Machine Type: arm64
Version: Darwin Kernel Version 25.0.0
CPU: Apple M3 Pro (12 cores)
Binaries:
Node: 26.3.0
npm: 11.16.0
Yarn: 1.22.22
pnpm: 11.19.0
Relevant Packages:
@swc/core: 1.16.1 (verified in an isolated npm install)
@swc/helpers: N/A
@swc/types: N/A
SWC Config:
API: minifySync
.swcrc path: N/A
Expected behavior
The
breakmust continue to target the innerdo...whileloop. Executing the transformed code should print:Actual behavior
The inner loop is removed and its
breakis retargeted to the outerforloop. With@swc/core@1.16.1, the output is:It prints only
body0.Version
@swc/core 1.16.1; also reproduced on swc main at 5b8c98d
Additional context
This was first observed downstream in web-infra-dev/rspack#15335. Kotlin/JS 1.7.20 emits a labeled single-iteration
do...while(false)inside a suspend-function state-machine loop. After compression, the inner jump exits the outer state-machine loop, so later code is removed and the function returnsundefined.The behavior is independently reproducible with
@swc/coreand does not require Rspack, labels, or mangling. A directcontinuehas the analogous control-target problem. Without an enclosing loop,function f(){do{break;}while(false);return 42}can be reduced to invalid code equivalent tofunction f(){break}.On current
main(commit5b8c98d909b4c5db2730877f0be09182ee470a26,swc_ecma_minifier 61.0.3), the affected path isoptimize_loops_with_constant_conditionincrates/swc_ecma_minifier/src/compress/pure/loops.rs. It callsshould_not_inline_loop_body(&stmt.body, true), allowing the direct jump while removing its target.Disabling the
loopscompressor option is the narrow reliable workaround.SWC previously added a conservative guard for the separate
simplifytransform in #1618. This report concerns the minifier compressor's independent constant-loop optimization.