mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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/32950 Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
parent
ad020477f4
commit
60a9bf9f95
8 changed files with 126 additions and 85 deletions
|
|
@ -6,6 +6,7 @@ package gc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
@ -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) {
|
||||||
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 {
|
||||||
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 {
|
||||||
|
|
||||||
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) {
|
||||||
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 {
|
||||||
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) {
|
||||||
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 {
|
||||||
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")
|
||||||
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) {
|
||||||
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) {
|
||||||
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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
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) {
|
||||||
} {
|
} {
|
||||||
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 @@
|
||||||
package syntax
|
package syntax
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
@ -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 {
|
||||||
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) {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 @@
|
||||||
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) {
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue