diff options
Diffstat (limited to 'src/pkg/go/parser')
-rw-r--r-- | src/pkg/go/parser/error_test.go | 21 | ||||
-rw-r--r-- | src/pkg/go/parser/interface.go | 7 | ||||
-rw-r--r-- | src/pkg/go/parser/parser.go | 75 | ||||
-rw-r--r-- | src/pkg/go/parser/parser_test.go | 15 | ||||
-rw-r--r-- | src/pkg/go/parser/short_test.go | 25 |
5 files changed, 110 insertions, 33 deletions
diff --git a/src/pkg/go/parser/error_test.go b/src/pkg/go/parser/error_test.go index d4d4f909d..8506077ce 100644 --- a/src/pkg/go/parser/error_test.go +++ b/src/pkg/go/parser/error_test.go @@ -59,8 +59,11 @@ func getPos(filename string, offset int) token.Pos { // ERROR comments must be of the form /* ERROR "rx" */ and rx is // a regular expression that matches the expected error message. +// The special form /* ERROR HERE "rx" */ must be used for error +// messages that appear immediately after a token, rather than at +// a token's position. // -var errRx = regexp.MustCompile(`^/\* *ERROR *"([^"]*)" *\*/$`) +var errRx = regexp.MustCompile(`^/\* *ERROR *(HERE)? *"([^"]*)" *\*/$`) // expectedErrors collects the regular expressions of ERROR comments found // in files and returns them as a map of error positions to error messages. @@ -74,6 +77,7 @@ func expectedErrors(t *testing.T, filename string, src []byte) map[token.Pos]str // not match the position information collected by the parser s.Init(getFile(filename), src, nil, scanner.ScanComments) var prev token.Pos // position of last non-comment, non-semicolon token + var here token.Pos // position immediately after the token at position prev for { pos, tok, lit := s.Scan() @@ -82,11 +86,22 @@ func expectedErrors(t *testing.T, filename string, src []byte) map[token.Pos]str return errors case token.COMMENT: s := errRx.FindStringSubmatch(lit) - if len(s) == 2 { - errors[prev] = string(s[1]) + if len(s) == 3 { + pos := prev + if s[1] == "HERE" { + pos = here + } + errors[pos] = string(s[2]) } default: prev = pos + var l int // token length + if tok.IsLiteral() { + l = len(lit) + } else { + l = len(tok.String()) + } + here = prev + token.Pos(l) } } } diff --git a/src/pkg/go/parser/interface.go b/src/pkg/go/parser/interface.go index 0f83ca931..57da4ddcd 100644 --- a/src/pkg/go/parser/interface.go +++ b/src/pkg/go/parser/interface.go @@ -182,6 +182,13 @@ func ParseExpr(x string) (ast.Expr, error) { p.closeScope() assert(p.topScope == nil, "unbalanced scopes") + // If a semicolon was inserted, consume it; + // report an error if there's more tokens. + if p.tok == token.SEMICOLON { + p.next() + } + p.expect(token.EOF) + if p.errors.Len() > 0 { p.errors.Sort() return nil, p.errors.Err() diff --git a/src/pkg/go/parser/parser.go b/src/pkg/go/parser/parser.go index c4523318f..00dd532b2 100644 --- a/src/pkg/go/parser/parser.go +++ b/src/pkg/go/parser/parser.go @@ -492,6 +492,26 @@ func syncDecl(p *parser) { } } +// safePos returns a valid file position for a given position: If pos +// is valid to begin with, safePos returns pos. If pos is out-of-range, +// safePos returns the EOF position. +// +// This is hack to work around "artificial" end positions in the AST which +// are computed by adding 1 to (presumably valid) token positions. If the +// token positions are invalid due to parse errors, the resulting end position +// may be past the file's EOF position, which would lead to panics if used +// later on. +// +func (p *parser) safePos(pos token.Pos) (res token.Pos) { + defer func() { + if recover() != nil { + res = token.Pos(p.file.Base() + p.file.Size()) // EOF position + } + }() + _ = p.file.Offset(pos) // trigger a panic if position is out-of-range + return pos +} + // ---------------------------------------------------------------------------- // Identifiers @@ -679,7 +699,7 @@ func (p *parser) parseFieldDecl(scope *ast.Scope) *ast.Field { if n := len(list); n > 1 || !isTypeName(deref(typ)) { pos := typ.Pos() p.errorExpected(pos, "anonymous field") - typ = &ast.BadExpr{From: pos, To: list[n-1].End()} + typ = &ast.BadExpr{From: pos, To: p.safePos(list[n-1].End())} } } @@ -1168,16 +1188,19 @@ func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr { defer un(trace(p, "IndexOrSlice")) } + const N = 3 // change the 3 to 2 to disable 3-index slices lbrack := p.expect(token.LBRACK) p.exprLev++ - var index [3]ast.Expr // change the 3 to 2 to disable slice expressions w/ cap + var index [N]ast.Expr + var colons [N - 1]token.Pos if p.tok != token.COLON { index[0] = p.parseRhs() } ncolons := 0 - for p.tok == token.COLON && ncolons < len(index)-1 { - p.next() + for p.tok == token.COLON && ncolons < len(colons) { + colons[ncolons] = p.pos ncolons++ + p.next() if p.tok != token.COLON && p.tok != token.RBRACK && p.tok != token.EOF { index[ncolons] = p.parseRhs() } @@ -1187,7 +1210,21 @@ func (p *parser) parseIndexOrSlice(x ast.Expr) ast.Expr { if ncolons > 0 { // slice expression - return &ast.SliceExpr{X: x, Lbrack: lbrack, Low: index[0], High: index[1], Max: index[2], Slice3: ncolons == 2, Rbrack: rbrack} + slice3 := false + if ncolons == 2 { + slice3 = true + // Check presence of 2nd and 3rd index here rather than during type-checking + // to prevent erroneous programs from passing through gofmt (was issue 7305). + if index[1] == nil { + p.error(colons[0], "2nd index required in 3-index slice") + index[1] = &ast.BadExpr{From: colons[0] + 1, To: colons[1]} + } + if index[2] == nil { + p.error(colons[1], "3rd index required in 3-index slice") + index[2] = &ast.BadExpr{From: colons[1] + 1, To: rbrack} + } + } + return &ast.SliceExpr{X: x, Lbrack: lbrack, Low: index[0], High: index[1], Max: index[2], Slice3: slice3, Rbrack: rbrack} } return &ast.IndexExpr{X: x, Lbrack: lbrack, Index: index[0], Rbrack: rbrack} @@ -1320,7 +1357,7 @@ func (p *parser) checkExpr(x ast.Expr) ast.Expr { default: // all other nodes are not proper expressions p.errorExpected(x.Pos(), "expression") - x = &ast.BadExpr{From: x.Pos(), To: x.End()} + x = &ast.BadExpr{From: x.Pos(), To: p.safePos(x.End())} } return x } @@ -1383,7 +1420,7 @@ func (p *parser) checkExprOrType(x ast.Expr) ast.Expr { case *ast.ArrayType: if len, isEllipsis := t.Len.(*ast.Ellipsis); isEllipsis { p.error(len.Pos(), "expected array length, found '...'") - x = &ast.BadExpr{From: x.Pos(), To: x.End()} + x = &ast.BadExpr{From: x.Pos(), To: p.safePos(x.End())} } } @@ -1669,14 +1706,14 @@ func (p *parser) parseSimpleStmt(mode int) (ast.Stmt, bool) { return &ast.ExprStmt{X: x[0]}, false } -func (p *parser) parseCallExpr() *ast.CallExpr { +func (p *parser) parseCallExpr(callType string) *ast.CallExpr { x := p.parseRhsOrType() // could be a conversion: (some type)(x) if call, isCall := x.(*ast.CallExpr); isCall { return call } if _, isBad := x.(*ast.BadExpr); !isBad { // only report error if it's a new one - p.errorExpected(x.Pos(), "function/method call") + p.error(p.safePos(x.End()), fmt.Sprintf("function must be invoked in %s statement", callType)) } return nil } @@ -1687,7 +1724,7 @@ func (p *parser) parseGoStmt() ast.Stmt { } pos := p.expect(token.GO) - call := p.parseCallExpr() + call := p.parseCallExpr("go") p.expectSemi() if call == nil { return &ast.BadStmt{From: pos, To: pos + 2} // len("go") @@ -1702,7 +1739,7 @@ func (p *parser) parseDeferStmt() ast.Stmt { } pos := p.expect(token.DEFER) - call := p.parseCallExpr() + call := p.parseCallExpr("defer") p.expectSemi() if call == nil { return &ast.BadStmt{From: pos, To: pos + 5} // len("defer") @@ -1745,15 +1782,15 @@ 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) ast.Expr { +func (p *parser) makeExpr(s ast.Stmt, kind string) ast.Expr { if s == nil { return nil } if es, isExpr := s.(*ast.ExprStmt); isExpr { return p.checkExpr(es.X) } - p.error(s.Pos(), "expected condition, found simple statement") - return &ast.BadExpr{From: s.Pos(), To: s.End()} + p.error(s.Pos(), fmt.Sprintf("expected %s, found simple statement (missing parentheses around composite literal?)", kind)) + return &ast.BadExpr{From: s.Pos(), To: p.safePos(s.End())} } func (p *parser) parseIfStmt() *ast.IfStmt { @@ -1779,7 +1816,7 @@ func (p *parser) parseIfStmt() *ast.IfStmt { p.next() x = p.parseRhs() } else { - x = p.makeExpr(s) + x = p.makeExpr(s, "boolean expression") s = nil } } @@ -1910,7 +1947,7 @@ func (p *parser) parseSwitchStmt() ast.Stmt { return &ast.TypeSwitchStmt{Switch: pos, Init: s1, Assign: s2, Body: body} } - return &ast.SwitchStmt{Switch: pos, Init: s1, Tag: p.makeExpr(s2), Body: body} + return &ast.SwitchStmt{Switch: pos, Init: s1, Tag: p.makeExpr(s2, "switch expression"), Body: body} } func (p *parser) parseCommClause() *ast.CommClause { @@ -2035,7 +2072,7 @@ func (p *parser) parseForStmt() ast.Stmt { key = as.Lhs[0] default: p.errorExpected(as.Lhs[0].Pos(), "1 or 2 expressions") - return &ast.BadStmt{From: pos, To: body.End()} + return &ast.BadStmt{From: pos, To: p.safePos(body.End())} } // parseSimpleStmt returned a right-hand side that // is a single unary expression of the form "range x" @@ -2055,7 +2092,7 @@ func (p *parser) parseForStmt() ast.Stmt { return &ast.ForStmt{ For: pos, Init: s1, - Cond: p.makeExpr(s2), + Cond: p.makeExpr(s2, "boolean or range expression"), Post: s3, Body: body, } @@ -2282,7 +2319,7 @@ func (p *parser) parseReceiver(scope *ast.Scope) *ast.FieldList { p.errorExpected(base.Pos(), "(unqualified) identifier") } par.List = []*ast.Field{ - {Type: &ast.BadExpr{From: recv.Pos(), To: recv.End()}}, + {Type: &ast.BadExpr{From: recv.Pos(), To: p.safePos(recv.End())}}, } } diff --git a/src/pkg/go/parser/parser_test.go b/src/pkg/go/parser/parser_test.go index 0a34b7e50..2797ea518 100644 --- a/src/pkg/go/parser/parser_test.go +++ b/src/pkg/go/parser/parser_test.go @@ -78,7 +78,7 @@ func TestParseExpr(t *testing.T) { } // sanity check if _, ok := x.(*ast.BinaryExpr); !ok { - t.Errorf("ParseExpr(%s): got %T, expected *ast.BinaryExpr", src, x) + t.Errorf("ParseExpr(%s): got %T, want *ast.BinaryExpr", src, x) } // a valid type expression @@ -89,17 +89,24 @@ func TestParseExpr(t *testing.T) { } // sanity check if _, ok := x.(*ast.StructType); !ok { - t.Errorf("ParseExpr(%s): got %T, expected *ast.StructType", src, x) + t.Errorf("ParseExpr(%s): got %T, want *ast.StructType", src, x) } // an invalid expression src = "a + *" _, err = ParseExpr(src) if err == nil { - t.Fatalf("ParseExpr(%s): %v", src, err) + t.Fatalf("ParseExpr(%s): got no error", src) + } + + // a valid expression followed by extra tokens is invalid + src = "a[i] := x" + _, err = ParseExpr(src) + if err == nil { + t.Fatalf("ParseExpr(%s): got no error", src) } - // it must not crash + // ParseExpr must not crash for _, src := range valids { ParseExpr(src) } diff --git a/src/pkg/go/parser/short_test.go b/src/pkg/go/parser/short_test.go index 0ef0c560c..b79406099 100644 --- a/src/pkg/go/parser/short_test.go +++ b/src/pkg/go/parser/short_test.go @@ -48,14 +48,14 @@ var invalids = []string{ `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 condition" */ = range x; true {} };`, - `package p; func f() { switch _ /* ERROR "expected condition" */ = range x; true {} };`, + `package p; func f() { if _ /* ERROR "expected boolean expression" */ = range 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 {} };`, - `package p; func f() { for ; _ /* ERROR "expected condition" */ = range x ; {} };`, - `package p; func f() { switch t /* ERROR "expected condition" */ = t.(type) {} };`, - `package p; func f() { switch t /* ERROR "expected condition" */ , t = t.(type) {} };`, - `package p; func f() { switch t /* ERROR "expected condition" */ = t.(type), t {} };`, + `package p; func f() { for ; _ /* ERROR "expected boolean or range expression" */ = range x ; {} };`, + `package p; func f() { switch t /* ERROR "expected switch expression" */ = t.(type) {} };`, + `package p; func f() { switch t /* ERROR "expected switch expression" */ , t = t.(type) {} };`, + `package p; func f() { switch t /* ERROR "expected switch expression" */ = t.(type), t {} };`, `package p; var a = [ /* ERROR "expected expression" */ 1]int;`, `package p; var a = [ /* ERROR "expected expression" */ ...]int;`, `package p; var a = struct /* ERROR "expected expression" */ {}`, @@ -76,8 +76,19 @@ var invalids = []string{ `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" */ };`, - `package p; func f() { var s []int; _ = s[::: /* ERROR "expected ']'" */ ] };`, + `package p; func f() { var s []int; _ = s[i:j: /* ERROR "3rd index required" */ ] };`, + `package p; func f() { var s []int; _ = s[i: /* ERROR "2nd index required" */ :k] };`, + `package p; func f() { var s []int; _ = s[i: /* ERROR "2nd index required" */ :] };`, + `package p; func f() { var s []int; _ = s[: /* ERROR "2nd index required" */ :] };`, + `package p; func f() { var s []int; _ = s[: /* ERROR "2nd index required" */ ::] };`, `package p; func f() { var s []int; _ = s[i:j:k: /* ERROR "expected ']'" */ l] };`, + `package p; func f() { for x /* ERROR "boolean or range expression" */ = []string {} }`, + `package p; func f() { for x /* ERROR "boolean or range expression" */ := []string {} }`, + `package p; func f() { for i /* ERROR "boolean or range expression" */ , x = []string {} }`, + `package p; func f() { for i /* ERROR "boolean or range expression" */ , x := []string {} }`, + `package p; func f() { go f /* ERROR HERE "function must be invoked" */ }`, + `package p; func f() { defer func() {} /* ERROR HERE "function must be invoked" */ }`, + `package p; func f() { go func() { func() { f(x func /* ERROR "expected '\)'" */ (){}) } } }`, } func TestInvalid(t *testing.T) { |