Browse Source

Fixed compilation of falsy 'while' loops. Fixes #220.

Dmitry Panov 5 years ago
parent
commit
bf9dcfbbe7
3 changed files with 78 additions and 30 deletions
  1. 27 0
      compiler.go
  2. 8 18
      compiler_stmt.go
  3. 43 12
      compiler_test.go

+ 27 - 0
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")
 		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()
+}

+ 8 - 18
compiler_stmt.go

@@ -259,15 +259,12 @@ func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bo
 				if r.ToBoolean() {
 				if r.ToBoolean() {
 					testConst = true
 					testConst = true
 				} else {
 				} 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
 					goto end
 				}
 				}
 			} else {
 			} else {
@@ -398,10 +395,7 @@ func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResul
 			if t.ToBoolean() {
 			if t.ToBoolean() {
 				testTrue = true
 				testTrue = true
 			} else {
 			} else {
-				p := c.p
-				c.p = &Program{}
-				c.compileStatement(v.Body, false)
-				c.p = p
+				c.compileStatementDummy(v.Body)
 				goto end
 				goto end
 			}
 			}
 		} else {
 		} else {
@@ -605,11 +599,7 @@ func (c *compiler) compileIfStatement(v *ast.IfStatement, needResult bool) {
 				c.p = p
 				c.p = p
 			}
 			}
 		} else {
 		} 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 {
 			if v.Alternate != nil {
 				c.compileStatement(v.Alternate, needResult)
 				c.compileStatement(v.Alternate, needResult)
 			} else {
 			} else {

+ 43 - 12
compiler_test.go

@@ -2097,25 +2097,56 @@ func TestTryEmptyCatchStackLeak(t *testing.T) {
 	testScript1(SCRIPT, _undefined, 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) {
 func TestDummyCompile(t *testing.T) {
 	const SCRIPT = `
 	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) {
 func BenchmarkCompile(b *testing.B) {
 	f, err := os.Open("testdata/S15.10.2.12_A1_T1.js")
 	f, err := os.Open("testdata/S15.10.2.12_A1_T1.js")