fix(minifier): avoid unsafe return removal in labeled blocks (#25406)

revert #24813 currently there is no way to validate if stmt can be
removed if its inside a block

correct issue with incorrect removal of `return` stmt not at termination
points


```js
(function() {
  label: {
    console.log('before return');
    return;
  }
  console.log('unreachable!');
})();
```

ref: https://github.com/rolldown/rolldown/issues/10555
This commit is contained in:
Armano authored and GitHub committed 2026-08-09 12:59:54 +01:00
1 parent 17037390ed
commit e0fb2d782a
4 files changed
+52 -56

No files matched your search

@@ -374,9 +374,6 @@ impl<'a> PeepholeOptimizations {
Statement::ForOfStatement(for_of_stmt) => {
Self::handle_for_of_statement(for_of_stmt, result, ctx);
}
Statement::LabeledStatement(label_stmt) => {
Self::handle_labeled_statement(label_stmt, result, ctx);
}
Statement::BlockStatement(block_stmt) => Self::handle_block(result, block_stmt, ctx),
stmt => result.push(stmt),
}
@@ -1238,33 +1235,6 @@ impl<'a> PeepholeOptimizations {
result.push(Statement::ForOfStatement(for_of_stmt));
}
fn handle_labeled_statement(
mut labeled_stmt: ArenaBox<'a, LabeledStatement<'a>>,
result: &mut ArenaVec<'a, Statement<'a>>,
ctx: &mut TraverseCtx<'a>,
) {
if let Statement::BlockStatement(block_stmt) = &mut labeled_stmt.body {
Self::minimize_statements(&mut block_stmt.body, ctx);
} else if !Self::statement_cares_about_scope(&labeled_stmt.body) {
let mut stmts = ArenaVec::from_value_in(labeled_stmt.body.take_in(ctx), ctx);
Self::minimize_statements(&mut stmts, ctx);
labeled_stmt.body = match stmts.len() {
0 => Statement::new_empty_statement(labeled_stmt.body.span(), ctx),
1 => stmts[0].take_in(ctx),
_ => {
ctx.notice_change();
Statement::new_block_statement_with_scope_id(
labeled_stmt.span,
stmts,
ctx.create_child_scope_of_current(ScopeFlags::empty()),
ctx,
)
}
};
}
result.push(Statement::LabeledStatement(labeled_stmt));
}
/// `appendIfOrLabelBodyPreservingScope`: <https://github.com/evanw/esbuild/blob/v0.24.2/internal/js_parser/js_parser.go#L9839>
fn handle_block(
result: &mut ArenaVec<'a, Statement<'a>>,
@@ -25,6 +25,8 @@ fn test_break_optimization() {
test("f:g:{if(a()){break f;}else{break f;} break f;}", "f:g:{if(a())break f;break f}"); // f:g:a();
test("function f() { a: break a; }", "function f() {}");
test("function f() { a: { break a; } }", "function f() {}");
test_same("function f() { a: { b(); break a; } c(); }");
test_same("function f() { a: { b(); return; } c(); }");
}
#[test]
@@ -78,11 +80,20 @@ fn test_function_return_optimization() {
"function f(){try{a();return}catch{}}",
); // function f(){try{a()}catch{}}
test("function f(){g:return}", "function f(){}");
test("function f(){g:{return}}", "function f(){}");
test("function f(){g:if(a()){return;}else{return;} return;}", "function f(){g:a()}");
test("function f(){g:{if(a()){return;}else{return;} return;}}", "function f(){g:a()}");
test("function f(){g:{a();if(b()){return;}else{return;} return;}}", "function f(){g:a(),b()}");
test_same("function f(){g:return}"); // function f(){}
test("function f(){g:{return}}", "function f(){g:return}"); // function f(){}
test(
"function f(){g:if(a()){return;}else{return;} return;}",
"function f(){g:if(a())return;else return}",
); // function f(){g:a()}
test(
"function f(){g:{if(a()){return;}else{return;} return;}}",
"function f(){g:return a(),void 0}",
); // function f(){g:a()}
test(
"function f(){g:{a();if(b()){return;}else{return;} return;}}",
"function f(){g:return a(),b(),void 0}",
); // function f(){g:a(),b()}
test(
"function f(){try{g:if(a()){throw 9;} return;}finally{return}}",
"function f(){try{g:if(a())throw 9; return}finally{return}}",
@@ -143,12 +154,21 @@ fn test_while_continue_optimization() {
"for(;;)try{if(a())continue;continue}catch{}",
); // for(;;)try{a()}catch{}
test("while(true){g:continue}", "for(;;);");
test("while(true){g:{continue}}", "for(;;);");
test("while(true){g:continue}", "for(;;) g:continue;"); // for(;;);
test("while(true){g:{continue}}", "for(;;) g:continue;"); // for(;;);
// This case could be improved.
test("while(true){g:if(a()){continue;}else{continue;} continue;}", "for (;;)g:a();");
test("while(true){g:{if(a()){continue;}else{continue;} continue;}}", "for(;;)g:a();");
test("while(true){g:{a();if(b()){continue;}else{continue;} continue;}}", "for(;;)g:a(),b();");
test(
"while(true){g:if(a()){continue;}else{continue;} continue;}",
"for(;;)g:if(a())continue;else continue;",
); // for (;;)g:a();
test(
"while(true){g:{if(a()){continue;}else{continue;} continue;}}",
"for(;;)g:{if(a())continue;continue}",
); // for(;;)g:a();
test(
"while(true){g:{a();if(b()){continue;}else{continue;} continue;}}",
"for(;;)g:{if(a(),b())continue;continue}",
); // for(;;)g:a(),b();
}
#[test]
@@ -184,9 +204,12 @@ fn test_do_continue_optimization() {
"do try{if(a())continue;continue}catch{}while(!0);",
); // do try{a()}catch{}while(!0);
test("do{g:continue}while(true)", "do;while(!0);");
test("do{g:continue}while(true)", "do g:continue;while(!0);"); // do;while(!0);
// This case could be improved.
test("do{g:if(a()){continue;}else{continue;} continue;}while(true)", "do g:a();while(!0);");
test(
"do{g:if(a()){continue;}else{continue;} continue;}while(true)",
"do g:if(a())continue;else continue;while(!0);",
); // do g:a();while(!0);
test("do { foo(); continue; } while(false)", "do foo();while(!1)");
test("do { foo(); break; } while(false)", "do foo();while(!1)");
@@ -267,19 +290,19 @@ fn test_for_continue_optimization() {
"for(x=0;x<y;x++)try{if(a())continue;continue}catch{}",
); // for(x=0;x<y;x++)try{a()}catch{}
test("for(x=0;x<y;x++){g:continue}", "for(x=0;x<y;x++);");
test("for(x=0;x<y;x++){g:{continue}}", "for(x=0;x<y;x++);");
test("for(x=0;x<y;x++){g:continue}", "for(x=0;x<y;x++)g:continue;"); // for(x=0;x<y;x++);
test("for(x=0;x<y;x++){g:{continue}}", "for(x=0;x<y;x++)g:continue;"); // for(x=0;x<y;x++);
test(
"for(x=0;x<y;x++){g:if(a()){continue;}else{continue;} continue;}",
"for(x=0;x<y;x++)g:a();",
);
"for(x=0;x<y;x++)g:if(a())continue;else continue;",
); // for(x=0;x<y;x++)g:a();
test(
"for(x=0;x<y;x++){g:{if(a()){continue;}else{continue;} continue;}}",
"for(x=0;x<y;x++)g:a();",
);
"for(x=0;x<y;x++)g:{if(a())continue;continue;}",
); // for(x=0;x<y;x++)g:a();
test(
"for(x=0;x<y;x++){g:{a();if(b()){continue;}else{continue;} continue;}}",
"for(x=0;x<y;x++)g:a(),b();",
"for(x=0;x<y;x++)g:{if(a(),b())continue;continue}",
);
}
@@ -205,14 +205,17 @@ fn test_handle_switch_statement() {
test("switch (a) { case 1: if(a) {b();}c();}", "a === 1 && (a && b(), c());");
test("switch ('\\v') { case '\\u000B': foo();}", "foo();");
test("x: switch (a) { case 1: break x;}", "x: if (a === 1) break x;");
test("x: switch (a) { case 1: break x;}", "x: switch (a) { case 1: break x; }"); // x: if (a === 1) break x;
test_same("x: switch (a) { case 2: break x; case 1: break x;}"); // x: { a; break x; }
test("x: switch (2) { case 2: f(); break outer; }", "x: { f(); break outer; }");
test("x: switch (2) { case 2: f(); break outer; }", "x:switch(2){case 2:f();break outer}"); // x: { f(); break outer; }
test(
"x: switch (x) { case 2: f(); for (;;){break outer;}}",
"x: if (x === 2) for (f();;) break outer;",
);
test("x: switch (a) { case 2: if(b) { break outer; } }", "x: if (a === 2 && b) break outer;");
"x: switch (x) { case 2: for (f();;)break outer}",
); // x: if (x === 2) for (f();;) break outer;
test(
"x: switch (a) { case 2: if(b) { break outer; } }",
"x: switch (a) { case 2: if(b) break outer; }",
); // x: if (a === 2 && b) break outer;
test(
"switch ('r') { case 'r': a();break; case 'r': var x=0;break;}",
@@ -5,7 +5,7 @@ checker.ts:
sys deallocs: 152
sys alloc bytes: 14996600 # 15.00 MB
sys peak growth: 10169560 # 10.17 MB
arena allocs: 148493
arena allocs: 148428
arena reallocs: 28468
arena size: 19205776 # 19.21 MB
@@ -71,6 +71,6 @@ kitchen-sink.tsx:
sys deallocs: 1858
sys alloc bytes: 5307512 # 5.31 MB
sys peak growth: 3707425 # 3.71 MB
arena allocs: 66806
arena allocs: 66771
arena reallocs: 12252
arena size: 9424360 # 9.42 MB