Commit 60a9bf9f authored by Robert Griesemer's avatar Robert Griesemer

cmd/compile/internal/syntax: fix error handling for Read/Parse calls

- define syntax.Error for cleaner error reporting
- abort parsing after first error if no error handler is installed
- make sure to always report the first error, if any
- document behavior of API calls
- while at it: rename ReadXXX -> ParseXXX (clearer)
- adjust cmd/compile noder.go accordingly

Fixes #17774.

Change-Id: I7893eedea454a64acd753e32f7a8bf811ddbb03c
Reviewed-on: https://go-review.googlesource.com/32950Reviewed-by: 's avatarMatthew Dempsky <mdempsky@google.com>
parent ad020477
...@@ -6,6 +6,7 @@ package gc ...@@ -6,6 +6,7 @@ package gc
import ( import (
"fmt" "fmt"
"os"
"strconv" "strconv"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
...@@ -14,18 +15,21 @@ import ( ...@@ -14,18 +15,21 @@ import (
) )
func parseFile(filename string) { func parseFile(filename string) {
p := noder{baseline: lexlineno} src, err := os.Open(filename)
file, err := syntax.ReadFile(filename, p.error, p.pragma, 0)
if err != nil { if err != nil {
fmt.Printf("parse %s: %v\n", filename, err) fmt.Println(err)
errorexit() errorexit()
} }
defer src.Close()
p := noder{baseline: lexlineno}
file, _ := syntax.Parse(src, p.error, p.pragma, 0) // errors are tracked via p.error
p.file(file) p.file(file)
if !imported_unsafe { if !imported_unsafe {
for _, x := range p.linknames { for _, x := range p.linknames {
p.error(0, x, "//go:linkname only allowed in Go files that import \"unsafe\"") p.error(syntax.Error{0, x, "//go:linkname only allowed in Go files that import \"unsafe\""})
} }
} }
...@@ -1003,8 +1007,16 @@ func (p *noder) lineno(n syntax.Node) { ...@@ -1003,8 +1007,16 @@ func (p *noder) lineno(n syntax.Node) {
lineno = p.baseline + l - 1 lineno = p.baseline + l - 1
} }
func (p *noder) error(_, line int, msg string) { func (p *noder) error(err error) {
yyerrorl(p.baseline+int32(line)-1, "%s", msg) line := p.baseline
var msg string
if err, ok := err.(syntax.Error); ok {
line += int32(err.Line) - 1
msg = err.Msg
} else {
msg = err.Error()
}
yyerrorl(line, "%s", msg)
} }
func (p *noder) pragma(pos, line int, text string) syntax.Pragma { func (p *noder) pragma(pos, line int, text string) syntax.Pragma {
...@@ -1020,7 +1032,7 @@ func (p *noder) pragma(pos, line int, text string) syntax.Pragma { ...@@ -1020,7 +1032,7 @@ func (p *noder) pragma(pos, line int, text string) syntax.Pragma {
break break
} }
if n > 1e8 { if n > 1e8 {
p.error(pos, line, "line number out of range") p.error(syntax.Error{pos, line, "line number out of range"})
errorexit() errorexit()
} }
if n <= 0 { if n <= 0 {
...@@ -1036,7 +1048,7 @@ func (p *noder) pragma(pos, line int, text string) syntax.Pragma { ...@@ -1036,7 +1048,7 @@ func (p *noder) pragma(pos, line int, text string) syntax.Pragma {
f := strings.Fields(text) f := strings.Fields(text)
if len(f) != 3 { if len(f) != 3 {
p.error(pos, line, "usage: //go:linkname localname linkname") p.error(syntax.Error{pos, line, "usage: //go:linkname localname linkname"})
break break
} }
lookup(f[1]).Linkname = f[2] lookup(f[1]).Linkname = f[2]
......
...@@ -14,7 +14,7 @@ func TestDump(t *testing.T) { ...@@ -14,7 +14,7 @@ func TestDump(t *testing.T) {
t.Skip("skipping test in short mode") t.Skip("skipping test in short mode")
} }
ast, err := ReadFile(*src, nil, nil, 0) ast, err := ParseFile(*src, nil, nil, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
......
...@@ -24,27 +24,16 @@ type parser struct { ...@@ -24,27 +24,16 @@ type parser struct {
fnest int // function nesting level (for error handling) fnest int // function nesting level (for error handling)
xnest int // expression nesting level (for complit ambiguity resolution) xnest int // expression nesting level (for complit ambiguity resolution)
indent []byte // tracing support indent []byte // tracing support
nerrors int // error count
} }
type parserError string // for error recovery if no error handler was installed type parserError string // for error recovery if no error handler was installed
func (p *parser) init(src io.Reader, errh ErrorHandler, pragh PragmaHandler) { func (p *parser) init(src io.Reader, errh ErrorHandler, pragh PragmaHandler) {
p.scanner.init(src, func(pos, line int, msg string) { p.scanner.init(src, errh, pragh)
p.nerrors++
if !debug && errh != nil {
errh(pos, line, msg)
return
}
panic(parserError(fmt.Sprintf("%d: %s\n", line, msg)))
}, pragh)
p.fnest = 0 p.fnest = 0
p.xnest = 0 p.xnest = 0
p.indent = nil p.indent = nil
p.nerrors = 0
} }
func (p *parser) got(tok token) bool { func (p *parser) got(tok token) bool {
...@@ -76,7 +65,7 @@ func (p *parser) syntax_error_at(pos, line int, msg string) { ...@@ -76,7 +65,7 @@ func (p *parser) syntax_error_at(pos, line int, msg string) {
defer p.trace("syntax_error (" + msg + ")")() defer p.trace("syntax_error (" + msg + ")")()
} }
if p.tok == _EOF && p.nerrors > 0 { if p.tok == _EOF && p.first != nil {
return // avoid meaningless follow-up errors return // avoid meaningless follow-up errors
} }
...@@ -207,7 +196,7 @@ func (p *parser) file() *File { ...@@ -207,7 +196,7 @@ func (p *parser) file() *File {
p.want(_Semi) p.want(_Semi)
// don't bother continuing if package clause has errors // don't bother continuing if package clause has errors
if p.nerrors > 0 { if p.first != nil {
return nil return nil
} }
......
...@@ -22,7 +22,7 @@ var src = flag.String("src", "parser.go", "source file to parse") ...@@ -22,7 +22,7 @@ var src = flag.String("src", "parser.go", "source file to parse")
var verify = flag.Bool("verify", false, "verify idempotent printing") var verify = flag.Bool("verify", false, "verify idempotent printing")
func TestParse(t *testing.T) { func TestParse(t *testing.T) {
_, err := ReadFile(*src, nil, nil, 0) _, err := ParseFile(*src, nil, nil, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -52,7 +52,7 @@ func TestStdLib(t *testing.T) { ...@@ -52,7 +52,7 @@ func TestStdLib(t *testing.T) {
if debug { if debug {
fmt.Printf("parsing %s\n", filename) fmt.Printf("parsing %s\n", filename)
} }
ast, err := ReadFile(filename, nil, nil, 0) ast, err := ParseFile(filename, nil, nil, 0)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
...@@ -133,7 +133,7 @@ func verifyPrint(filename string, ast1 *File) { ...@@ -133,7 +133,7 @@ func verifyPrint(filename string, ast1 *File) {
panic(err) panic(err)
} }
ast2, err := ReadBytes(buf1.Bytes(), nil, nil, 0) ast2, err := ParseBytes(buf1.Bytes(), nil, nil, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }
...@@ -157,8 +157,28 @@ func verifyPrint(filename string, ast1 *File) { ...@@ -157,8 +157,28 @@ func verifyPrint(filename string, ast1 *File) {
} }
func TestIssue17697(t *testing.T) { func TestIssue17697(t *testing.T) {
_, err := ReadBytes(nil, nil, nil, 0) // return with parser error, don't panic _, err := ParseBytes(nil, nil, nil, 0) // return with parser error, don't panic
if err == nil { if err == nil {
t.Errorf("no error reported") t.Errorf("no error reported")
} }
} }
func TestParseFile(t *testing.T) {
_, err := ParseFile("", nil, nil, 0)
if err == nil {
t.Error("missing io error")
}
var first error
_, err = ParseFile("", func(err error) {
if first == nil {
first = err
}
}, nil, 0)
if err == nil || first == nil {
t.Error("missing io error")
}
if err != first {
t.Error("got %v; want first error %v", err, first)
}
}
...@@ -15,7 +15,7 @@ func TestPrint(t *testing.T) { ...@@ -15,7 +15,7 @@ func TestPrint(t *testing.T) {
t.Skip("skipping test in short mode") t.Skip("skipping test in short mode")
} }
ast, err := ReadFile(*src, nil, nil, 0) ast, err := ParseFile(*src, nil, nil, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
......
...@@ -322,21 +322,22 @@ func TestScanErrors(t *testing.T) { ...@@ -322,21 +322,22 @@ func TestScanErrors(t *testing.T) {
} { } {
var s scanner var s scanner
nerrors := 0 nerrors := 0
s.init(&bytesReader{[]byte(test.src)}, func(pos, line int, msg string) { s.init(&bytesReader{[]byte(test.src)}, func(err error) {
nerrors++ nerrors++
// only check the first error // only check the first error
e := err.(Error) // we know it's an Error
if nerrors == 1 { if nerrors == 1 {
if msg != test.msg { if e.Msg != test.msg {
t.Errorf("%q: got msg = %q; want %q", test.src, msg, test.msg) t.Errorf("%q: got msg = %q; want %q", test.src, e.Msg, test.msg)
} }
if pos != test.pos { if e.Pos != test.pos {
t.Errorf("%q: got pos = %d; want %d", test.src, pos, test.pos) t.Errorf("%q: got pos = %d; want %d", test.src, e.Pos, test.pos)
} }
if line != test.line { if e.Line != test.line {
t.Errorf("%q: got line = %d; want %d", test.src, line, test.line) t.Errorf("%q: got line = %d; want %d", test.src, e.Line, test.line)
} }
} else if nerrors > 1 { } else if nerrors > 1 {
t.Errorf("%q: got unexpected %q at pos = %d, line = %d", test.src, msg, pos, line) t.Errorf("%q: got unexpected %q at pos = %d, line = %d", test.src, e.Msg, e.Pos, e.Line)
} }
}, nil) }, nil)
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
package syntax package syntax
import ( import (
"fmt"
"io" "io"
"unicode/utf8" "unicode/utf8"
) )
...@@ -16,8 +15,9 @@ import ( ...@@ -16,8 +15,9 @@ import (
// suf r0 r w // suf r0 r w
type source struct { type source struct {
src io.Reader src io.Reader
errh ErrorHandler errh ErrorHandler
first error // first error encountered
// source buffer // source buffer
buf [4 << 10]byte buf [4 << 10]byte
...@@ -34,6 +34,7 @@ type source struct { ...@@ -34,6 +34,7 @@ type source struct {
func (s *source) init(src io.Reader, errh ErrorHandler) { func (s *source) init(src io.Reader, errh ErrorHandler) {
s.src = src s.src = src
s.errh = errh s.errh = errh
s.first = nil
s.buf[0] = utf8.RuneSelf // terminate with sentinel s.buf[0] = utf8.RuneSelf // terminate with sentinel
s.offs = 0 s.offs = 0
...@@ -50,11 +51,14 @@ func (s *source) error(msg string) { ...@@ -50,11 +51,14 @@ func (s *source) error(msg string) {
} }
func (s *source) error_at(pos, line int, msg string) { func (s *source) error_at(pos, line int, msg string) {
if s.errh != nil { err := Error{pos, line, msg}
s.errh(pos, line, msg) if s.first == nil {
return s.first = err
} }
panic(fmt.Sprintf("%d: %s", line, msg)) if s.errh == nil {
panic(s.first)
}
s.errh(err)
} }
// pos0 returns the byte position of the last character read. // pos0 returns the byte position of the last character read.
......
...@@ -5,35 +5,72 @@ ...@@ -5,35 +5,72 @@
package syntax package syntax
import ( import (
"errors"
"fmt" "fmt"
"io" "io"
"os" "os"
) )
// Mode describes the parser mode.
type Mode uint type Mode uint
// Error describes a syntax error. Error implements the error interface.
type Error struct {
// TODO(gri) decide what we really need here
Pos int // byte offset from file start
Line int // line (starting with 1)
Msg string
}
func (err Error) Error() string {
return fmt.Sprintf("%d: %s", err.Line, err.Msg)
}
var _ error = Error{} // verify that Error implements error
// An ErrorHandler is called for each error encountered reading a .go file.
type ErrorHandler func(err error)
// A Pragma value is a set of flags that augment a function or // A Pragma value is a set of flags that augment a function or
// type declaration. Callers may assign meaning to the flags as // type declaration. Callers may assign meaning to the flags as
// appropriate. // appropriate.
type Pragma uint16 type Pragma uint16
type ErrorHandler func(pos, line int, msg string)
// A PragmaHandler is used to process //line and //go: directives as // A PragmaHandler is used to process //line and //go: directives as
// they're scanned. The returned Pragma value will be unioned into the // they're scanned. The returned Pragma value will be unioned into the
// next FuncDecl node. // next FuncDecl node.
type PragmaHandler func(pos, line int, text string) Pragma type PragmaHandler func(pos, line int, text string) Pragma
// TODO(gri) These need a lot more work. // Parse parses a single Go source file from src and returns the corresponding
// syntax tree. If there are syntax errors, Parse will return the first error
// encountered.
//
// If errh != nil, it is called with each error encountered, and Parse will
// process as much source as possible. If errh is nil, Parse will terminate
// immediately upon encountering an error.
//
// If a PragmaHandler is provided, it is called with each pragma encountered.
//
// The Mode argument is currently ignored.
func Parse(src io.Reader, errh ErrorHandler, pragh PragmaHandler, mode Mode) (_ *File, err error) {
defer func() {
if p := recover(); p != nil {
var ok bool
if err, ok = p.(Error); ok {
return
}
panic(p)
}
}()
func ReadFile(filename string, errh ErrorHandler, pragh PragmaHandler, mode Mode) (*File, error) { var p parser
src, err := os.Open(filename) p.init(src, errh, pragh)
if err != nil { p.next()
return nil, err return p.file(), p.first
} }
defer src.Close()
return Read(src, errh, pragh, mode) // ParseBytes behaves like Parse but it reads the source from the []byte slice provided.
func ParseBytes(src []byte, errh ErrorHandler, pragh PragmaHandler, mode Mode) (*File, error) {
return Parse(&bytesReader{src}, errh, pragh, mode)
} }
type bytesReader struct { type bytesReader struct {
...@@ -49,37 +86,15 @@ func (r *bytesReader) Read(p []byte) (int, error) { ...@@ -49,37 +86,15 @@ func (r *bytesReader) Read(p []byte) (int, error) {
return 0, io.EOF return 0, io.EOF
} }
func ReadBytes(src []byte, errh ErrorHandler, pragh PragmaHandler, mode Mode) (*File, error) { // ParseFile behaves like Parse but it reads the source from the named file.
return Read(&bytesReader{src}, errh, pragh, mode) func ParseFile(filename string, errh ErrorHandler, pragh PragmaHandler, mode Mode) (*File, error) {
} src, err := os.Open(filename)
if err != nil {
func Read(src io.Reader, errh ErrorHandler, pragh PragmaHandler, mode Mode) (ast *File, err error) { if errh != nil {
defer func() { errh(err)
if p := recover(); p != nil {
if msg, ok := p.(parserError); ok {
err = errors.New(string(msg))
return
}
panic(p)
} }
}() return nil, err
var p parser
p.init(src, errh, pragh)
p.next()
ast = p.file()
// TODO(gri) This isn't quite right: Even if there's an error handler installed
// we should report an error if parsing found syntax errors. This also
// requires updating the noder's ReadFile call.
if errh == nil && p.nerrors > 0 {
ast = nil
err = fmt.Errorf("%d syntax errors", p.nerrors)
} }
defer src.Close()
return return Parse(src, errh, pragh, mode)
}
func Write(w io.Writer, n *File) error {
panic("unimplemented")
} }
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