Skip to content

Commit c46d498

Browse files
committed
fix #4353: remove empty try/finally clauses
1 parent 7a72735 commit c46d498

File tree

3 files changed

+83
-28
lines changed

3 files changed

+83
-28
lines changed

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,28 @@
2727
a:e:try{for(;;)break a}catch{break e}
2828
```
2929

30+
* The minifier now strips empty `finally` clauses ([#4353](https://github.com/evanw/esbuild/issues/4353))
31+
32+
This improvement means that `finally` clauses containing dead code can potentially cause the associated `try` statement to be removed from the output entirely in minified builds:
33+
34+
```js
35+
// Original code
36+
function foo(callback) {
37+
if (DEBUG) stack.push(callback.name);
38+
try {
39+
callback();
40+
} finally {
41+
if (DEBUG) stack.pop();
42+
}
43+
}
44+
45+
// Old output (with --minify --define:DEBUG=false)
46+
function foo(a){try{a()}finally{}}
47+
48+
// New output (with --minify --define:DEBUG=false)
49+
function foo(a){a()}
50+
```
51+
3052
* Allow tree-shaking of the `Symbol` constructor
3153

3254
With this release, calling `Symbol` is now considered to be side-effect free when the argument is known to be a primitive value. This means esbuild can now tree-shake module-level symbol variables:

internal/js_parser/js_parser.go

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11084,41 +11084,62 @@ func (p *parser) visitAndAppendStmt(stmts []js_ast.Stmt, stmt js_ast.Stmt) []js_
1108411084
p.popScope()
1108511085
}
1108611086

11087-
// Drop the whole thing if the try body is empty
11088-
if p.options.minifySyntax && len(s.Block.Stmts) == 0 {
11089-
keepCatch := false
11087+
if p.options.minifySyntax {
11088+
if len(s.Block.Stmts) == 0 {
11089+
// Try to drop the whole thing if the try body is empty
11090+
keepCatch := false
1109011091

11091-
// Certain "catch" blocks need to be preserved:
11092-
//
11093-
// try {} catch { let foo } // Can be removed
11094-
// try {} catch { var foo } // Must be kept
11095-
//
11096-
if s.Catch != nil {
11097-
for _, stmt2 := range s.Catch.Block.Stmts {
11098-
if shouldKeepStmtInDeadControlFlow(stmt2) {
11099-
keepCatch = true
11100-
break
11092+
// Certain "catch" blocks need to be preserved:
11093+
//
11094+
// try {} catch { let foo } // Can be removed
11095+
// try {} catch { var foo } // Must be kept
11096+
//
11097+
if s.Catch != nil {
11098+
for _, stmt2 := range s.Catch.Block.Stmts {
11099+
if shouldKeepStmtInDeadControlFlow(stmt2) {
11100+
keepCatch = true
11101+
break
11102+
}
1110111103
}
1110211104
}
11103-
}
1110411105

11105-
// Make sure to preserve the "finally" block if present
11106-
if !keepCatch {
11107-
if s.Finally == nil {
11108-
return stmts
11109-
}
11110-
finallyNeedsBlock := false
11111-
for _, stmt2 := range s.Finally.Block.Stmts {
11112-
if statementCaresAboutScope(stmt2) {
11113-
finallyNeedsBlock = true
11114-
break
11106+
// Make sure to preserve the "finally" block if present
11107+
if !keepCatch {
11108+
if s.Finally == nil {
11109+
return stmts
11110+
}
11111+
finallyNeedsBlock := false
11112+
for _, stmt2 := range s.Finally.Block.Stmts {
11113+
if statementCaresAboutScope(stmt2) {
11114+
finallyNeedsBlock = true
11115+
break
11116+
}
1111511117
}
11118+
if !finallyNeedsBlock {
11119+
return append(stmts, s.Finally.Block.Stmts...)
11120+
}
11121+
block := s.Finally.Block
11122+
stmt = js_ast.Stmt{Loc: s.Finally.Loc, Data: &block}
1111611123
}
11117-
if !finallyNeedsBlock {
11118-
return append(stmts, s.Finally.Block.Stmts...)
11124+
} else if s.Finally != nil && len(s.Finally.Block.Stmts) == 0 {
11125+
if s.Catch != nil {
11126+
// Just remove the "finally" block if there's a "catch"
11127+
s.Finally = nil
11128+
} else {
11129+
// Otherwise, try to unwrap the whole "try" statement
11130+
tryNeedsBlock := false
11131+
for _, stmt2 := range s.Block.Stmts {
11132+
if statementCaresAboutScope(stmt2) {
11133+
tryNeedsBlock = true
11134+
break
11135+
}
11136+
}
11137+
if !tryNeedsBlock {
11138+
return append(stmts, s.Block.Stmts...)
11139+
}
11140+
block := s.Block
11141+
stmt = js_ast.Stmt{Loc: s.Finally.Loc, Data: &block}
1111911142
}
11120-
block := s.Finally.Block
11121-
stmt = js_ast.Stmt{Loc: s.Finally.Loc, Data: &block}
1112211143
}
1112311144
}
1112411145

internal/js_parser/js_parser_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6665,6 +6665,18 @@ func TestMangleTry(t *testing.T) {
66656665
expectPrintedMangle(t, "try {} finally { let x = foo() }", "{\n let x = foo();\n}\n")
66666666
expectPrintedMangle(t, "try {} catch (e) { foo() } finally { let x = bar() }", "{\n let x = bar();\n}\n")
66676667

6668+
expectPrintedMangle(t, "try { foo() } catch {}", "try {\n foo();\n} catch {\n}\n")
6669+
expectPrintedMangle(t, "try { foo() } catch {} finally {}", "try {\n foo();\n} catch {\n}\n")
6670+
expectPrintedMangle(t, "try { foo() } finally {}", "foo();\n")
6671+
6672+
expectPrintedMangle(t, "try { var x = foo() } catch {}", "try {\n var x = foo();\n} catch {\n}\n")
6673+
expectPrintedMangle(t, "try { var x = foo() } catch {} finally {}", "try {\n var x = foo();\n} catch {\n}\n")
6674+
expectPrintedMangle(t, "try { var x = foo() } finally {}", "var x = foo();\n")
6675+
6676+
expectPrintedMangle(t, "try { let x = foo() } catch {}", "try {\n let x = foo();\n} catch {\n}\n")
6677+
expectPrintedMangle(t, "try { let x = foo() } catch {} finally {}", "try {\n let x = foo();\n} catch {\n}\n")
6678+
expectPrintedMangle(t, "try { let x = foo() } finally {}", "{\n let x = foo();\n}\n")
6679+
66686680
// The Kotlin compiler apparently generates code like this.
66696681
// See https://github.com/evanw/esbuild/issues/4064 for info.
66706682
expectPrintedMangle(t, "x: try { while (true) ; break x } catch {}", "x: try {\n for (; ; ) ;\n break x;\n} catch {\n}\n")

0 commit comments

Comments
 (0)