diff --git a/compiler.go b/compiler.go index 0dd9d759..9375ef4d 100644 --- a/compiler.go +++ b/compiler.go @@ -462,3 +462,30 @@ func (c *compiler) checkIdentifierLName(name unistring.String, offset int) { c.throwSyntaxError(offset, "Assignment to eval or arguments is not allowed in strict mode") } } + +// Enter a 'dummy' compilation mode. Any code produced after this method is called will be discarded after +// leaveFunc is called with no additional side effects. This is useful for compiling code inside a +// constant falsy condition 'if' branch or a loop (i.e 'if (false) { ... } or while (false) { ... }). +// Such code should not be included in the final compilation result as it's never called, but it must +// still produce compilation errors if there are any. +func (c *compiler) enterDummyMode() (leaveFunc func()) { + savedBlock, savedBlockStart, savedProgram := c.block, c.blockStart, c.p + if savedBlock != nil { + c.block = &block{ + typ: savedBlock.typ, + label: savedBlock.label, + } + } + c.p = &Program{} + c.newScope() + return func() { + c.block, c.blockStart, c.p = savedBlock, savedBlockStart, savedProgram + c.popScope() + } +} + +func (c *compiler) compileStatementDummy(statement ast.Statement) { + leave := c.enterDummyMode() + c.compileStatement(statement, false) + leave() +} diff --git a/compiler_stmt.go b/compiler_stmt.go index 1b7af710..e2ca4887 100644 --- a/compiler_stmt.go +++ b/compiler_stmt.go @@ -259,15 +259,12 @@ func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bo if r.ToBoolean() { testConst = true } else { - // TODO: Properly implement dummy compilation (no garbage in block, scope, etc..) - /* - p := c.p - c.p = &program{} - c.compileStatement(v.Body, false) - if v.Update != nil { - c.compileExpression(v.Update).emitGetter(false) - } - c.p = p*/ + leave := c.enterDummyMode() + c.compileStatement(v.Body, false) + if v.Update != nil { + c.compileExpression(v.Update).emitGetter(false) + } + leave() goto end } } else { @@ -398,10 +395,7 @@ func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResul if t.ToBoolean() { testTrue = true } else { - p := c.p - c.p = &Program{} - c.compileStatement(v.Body, false) - c.p = p + c.compileStatementDummy(v.Body) goto end } } else { @@ -605,11 +599,7 @@ func (c *compiler) compileIfStatement(v *ast.IfStatement, needResult bool) { c.p = p } } else { - // TODO: Properly implement dummy compilation (no garbage in block, scope, etc..) - p := c.p - c.p = &Program{} - c.compileStatement(v.Consequent, false) - c.p = p + c.compileStatementDummy(v.Consequent) if v.Alternate != nil { c.compileStatement(v.Alternate, needResult) } else { diff --git a/compiler_test.go b/compiler_test.go index 22e91d12..5c836271 100644 --- a/compiler_test.go +++ b/compiler_test.go @@ -2097,25 +2097,56 @@ func TestTryEmptyCatchStackLeak(t *testing.T) { testScript1(SCRIPT, _undefined, t) } -// FIXME -/* +func TestFalsyLoopBreak(t *testing.T) { + const SCRIPT = ` + while(false) { + break; + } + for(;false;) { + break; + } + undefined; + ` + MustCompile("", SCRIPT, false) +} + +func TestFalsyLoopBreakWithResult(t *testing.T) { + const SCRIPT = ` + while(false) { + break; + } + ` + testScript1(SCRIPT, _undefined, t) +} + func TestDummyCompile(t *testing.T) { const SCRIPT = ` -'use strict'; + 'use strict'; + + for (;false;) { + eval = 1; + } + ` -for (;false;) { - eval = 1; + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("expected error") + } } +func TestDummyCompileForUpdate(t *testing.T) { + const SCRIPT = ` + 'use strict'; + + for (;false;eval=1) { + } ` - defer func() { - if recover() == nil { - t.Fatal("Expected panic") - } - }() - testScript1(SCRIPT, _undefined, t) -}*/ + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("expected error") + } +} func BenchmarkCompile(b *testing.B) { f, err := os.Open("testdata/S15.10.2.12_A1_T1.js")