Commit c5f3a8b1 authored by Robert Griesemer's avatar Robert Griesemer

go/parser: more robust error handling for 'if' headers

R=go1.11

To fix this, this CL borrows code from the new syntax
package which has a better tuned parser at this point.

Fixes #11377.

Change-Id: Ib9212c945903d6f62abcc59ef5a5767d4ef36981
Reviewed-on: https://go-review.googlesource.com/87495Reviewed-by: default avatarMatthew Dempsky <mdempsky@google.com>
parent 29461ccc
......@@ -1808,48 +1808,90 @@ func (p *parser) parseBranchStmt(tok token.Token) *ast.BranchStmt {
return &ast.BranchStmt{TokPos: pos, Tok: tok, Label: label}
}
func (p *parser) makeExpr(s ast.Stmt, kind string) ast.Expr {
func (p *parser) makeExpr(s ast.Stmt, want string) ast.Expr {
if s == nil {
return nil
}
if es, isExpr := s.(*ast.ExprStmt); isExpr {
return p.checkExpr(es.X)
}
p.error(s.Pos(), fmt.Sprintf("expected %s, found simple statement (missing parentheses around composite literal?)", kind))
found := "simple statement"
if _, isAss := s.(*ast.AssignStmt); isAss {
found = "assignment"
}
p.error(s.Pos(), fmt.Sprintf("expected %s, found %s (missing parentheses around composite literal?)", want, found))
return &ast.BadExpr{From: s.Pos(), To: p.safePos(s.End())}
}
func (p *parser) parseIfStmt() *ast.IfStmt {
if p.trace {
defer un(trace(p, "IfStmt"))
// parseIfHeader is an adjusted version of parser.header
// in cmd/compile/internal/syntax/parser.go, which has
// been tuned for better error handling.
func (p *parser) parseIfHeader() (init ast.Stmt, cond ast.Expr) {
if p.tok == token.LBRACE {
p.error(p.pos, "missing condition in if statement")
return
}
// p.tok != token.LBRACE
pos := p.expect(token.IF)
p.openScope()
defer p.closeScope()
var s ast.Stmt
var x ast.Expr
{
prevLev := p.exprLev
outer := p.exprLev
p.exprLev = -1
if p.tok == token.SEMICOLON {
if p.tok != token.SEMICOLON {
// accept potential variable declaration but complain
if p.tok == token.VAR {
p.next()
x = p.parseRhs()
} else {
s, _ = p.parseSimpleStmt(basic)
p.error(p.pos, fmt.Sprintf("var declaration not allowed in 'IF' initializer"))
}
init, _ = p.parseSimpleStmt(basic)
}
var condStmt ast.Stmt
var semi struct {
pos token.Pos
lit string // ";" or "\n"; valid if pos.IsValid()
}
if p.tok != token.LBRACE {
if p.tok == token.SEMICOLON {
semi.pos = p.pos
semi.lit = p.lit
p.next()
x = p.parseRhs()
} else {
x = p.makeExpr(s, "boolean expression")
s = nil
p.expect(token.SEMICOLON)
}
if p.tok != token.LBRACE {
condStmt, _ = p.parseSimpleStmt(basic)
}
p.exprLev = prevLev
} else {
condStmt = init
init = nil
}
if condStmt != nil {
cond = p.makeExpr(condStmt, "boolean expression")
} else if semi.pos.IsValid() {
if semi.lit == "\n" {
p.error(semi.pos, "unexpected newline, expecting { after if clause")
} else {
p.error(semi.pos, "missing condition in if statement")
}
}
p.exprLev = outer
return
}
func (p *parser) parseIfStmt() *ast.IfStmt {
if p.trace {
defer un(trace(p, "IfStmt"))
}
pos := p.expect(token.IF)
p.openScope()
defer p.closeScope()
init, cond := p.parseIfHeader()
body := p.parseBlockStmt()
var else_ ast.Stmt
if p.tok == token.ELSE {
p.next()
......@@ -1867,7 +1909,7 @@ func (p *parser) parseIfStmt() *ast.IfStmt {
p.expectSemi()
}
return &ast.IfStmt{If: pos, Init: s, Cond: x, Body: body, Else: else_}
return &ast.IfStmt{If: pos, Init: init, Cond: cond, Body: body, Else: else_}
}
func (p *parser) parseTypeList() (list []ast.Expr) {
......
......@@ -58,10 +58,10 @@ func TestValid(t *testing.T) {
var invalids = []string{
`foo /* ERROR "expected 'package'" */ !`,
`package p; func f() { if { /* ERROR "expected operand" */ } };`,
`package p; func f() { if ; { /* ERROR "expected operand" */ } };`,
`package p; func f() { if f(); { /* ERROR "expected operand" */ } };`,
`package p; func f() { if _ /* ERROR "expected boolean expression" */ = range x; true {} };`,
`package p; func f() { if { /* ERROR "missing condition" */ } };`,
`package p; func f() { if ; /* ERROR "missing condition" */ {} };`,
`package p; func f() { if f(); /* ERROR "missing condition" */ {} };`,
`package p; func f() { if _ = range /* ERROR "expected operand" */ x; true {} };`,
`package p; func f() { switch _ /* ERROR "expected switch expression" */ = range x; true {} };`,
`package p; func f() { for _ = range x ; /* ERROR "expected '{'" */ ; {} };`,
`package p; func f() { for ; ; _ = range /* ERROR "expected operand" */ x {} };`,
......@@ -85,7 +85,7 @@ var invalids = []string{
`package p; func f() { _ = (<-<- /* ERROR "expected 'chan'" */ chan int)(nil) };`,
`package p; func f() { _ = (<-chan<-chan<-chan<-chan<-chan<- /* ERROR "expected channel type" */ int)(nil) };`,
`package p; func f() { var t []int; t /* ERROR "expected identifier on left side of :=" */ [0] := 0 };`,
`package p; func f() { if x := g(); x = /* ERROR "expected '=='" */ 0 {}};`,
`package p; func f() { if x := g(); x /* ERROR "expected boolean expression" */ = 0 {}};`,
`package p; func f() { _ = x = /* ERROR "expected '=='" */ 0 {}};`,
`package p; func f() { _ = 1 == func()int { var x bool; x = x = /* ERROR "expected '=='" */ true; return x }() };`,
`package p; func f() { var s []int; _ = s[] /* ERROR "expected operand" */ };`,
......
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Test case for issue 11377: Better synchronization of
// parser after certain syntax errors.
package p
func bad1() {
if f()) /* ERROR "expected ';', found '\)'" */ {
return
}
}
// There shouldn't be any errors down below.
func F1() {}
func F2() {}
func F3() {}
func F4() {}
func F5() {}
func F6() {}
func F7() {}
func F8() {}
func F9() {}
func F10() {}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment