...

Source file src/go/build/read.go

Documentation: go/build

		 1  // Copyright 2012 The Go Authors. All rights reserved.
		 2  // Use of this source code is governed by a BSD-style
		 3  // license that can be found in the LICENSE file.
		 4  
		 5  package build
		 6  
		 7  import (
		 8  	"bufio"
		 9  	"bytes"
		10  	"errors"
		11  	"fmt"
		12  	"go/ast"
		13  	"go/parser"
		14  	"go/token"
		15  	"io"
		16  	"strconv"
		17  	"strings"
		18  	"unicode"
		19  	"unicode/utf8"
		20  )
		21  
		22  type importReader struct {
		23  	b		*bufio.Reader
		24  	buf	[]byte
		25  	peek byte
		26  	err	error
		27  	eof	bool
		28  	nerr int
		29  	pos	token.Position
		30  }
		31  
		32  var bom = []byte{0xef, 0xbb, 0xbf}
		33  
		34  func newImportReader(name string, r io.Reader) *importReader {
		35  	b := bufio.NewReader(r)
		36  	// Remove leading UTF-8 BOM.
		37  	// Per https://golang.org/ref/spec#Source_code_representation:
		38  	// a compiler may ignore a UTF-8-encoded byte order mark (U+FEFF)
		39  	// if it is the first Unicode code point in the source text.
		40  	if leadingBytes, err := b.Peek(3); err == nil && bytes.Equal(leadingBytes, bom) {
		41  		b.Discard(3)
		42  	}
		43  	return &importReader{
		44  		b: b,
		45  		pos: token.Position{
		46  			Filename: name,
		47  			Line:		 1,
		48  			Column:	 1,
		49  		},
		50  	}
		51  }
		52  
		53  func isIdent(c byte) bool {
		54  	return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '_' || c >= utf8.RuneSelf
		55  }
		56  
		57  var (
		58  	errSyntax = errors.New("syntax error")
		59  	errNUL		= errors.New("unexpected NUL in input")
		60  )
		61  
		62  // syntaxError records a syntax error, but only if an I/O error has not already been recorded.
		63  func (r *importReader) syntaxError() {
		64  	if r.err == nil {
		65  		r.err = errSyntax
		66  	}
		67  }
		68  
		69  // readByte reads the next byte from the input, saves it in buf, and returns it.
		70  // If an error occurs, readByte records the error in r.err and returns 0.
		71  func (r *importReader) readByte() byte {
		72  	c, err := r.b.ReadByte()
		73  	if err == nil {
		74  		r.buf = append(r.buf, c)
		75  		if c == 0 {
		76  			err = errNUL
		77  		}
		78  	}
		79  	if err != nil {
		80  		if err == io.EOF {
		81  			r.eof = true
		82  		} else if r.err == nil {
		83  			r.err = err
		84  		}
		85  		c = 0
		86  	}
		87  	return c
		88  }
		89  
		90  // readByteNoBuf is like readByte but doesn't buffer the byte.
		91  // It exhausts r.buf before reading from r.b.
		92  func (r *importReader) readByteNoBuf() byte {
		93  	var c byte
		94  	var err error
		95  	if len(r.buf) > 0 {
		96  		c = r.buf[0]
		97  		r.buf = r.buf[1:]
		98  	} else {
		99  		c, err = r.b.ReadByte()
	 100  		if err == nil && c == 0 {
	 101  			err = errNUL
	 102  		}
	 103  	}
	 104  
	 105  	if err != nil {
	 106  		if err == io.EOF {
	 107  			r.eof = true
	 108  		} else if r.err == nil {
	 109  			r.err = err
	 110  		}
	 111  		return 0
	 112  	}
	 113  	r.pos.Offset++
	 114  	if c == '\n' {
	 115  		r.pos.Line++
	 116  		r.pos.Column = 1
	 117  	} else {
	 118  		r.pos.Column++
	 119  	}
	 120  	return c
	 121  }
	 122  
	 123  // peekByte returns the next byte from the input reader but does not advance beyond it.
	 124  // If skipSpace is set, peekByte skips leading spaces and comments.
	 125  func (r *importReader) peekByte(skipSpace bool) byte {
	 126  	if r.err != nil {
	 127  		if r.nerr++; r.nerr > 10000 {
	 128  			panic("go/build: import reader looping")
	 129  		}
	 130  		return 0
	 131  	}
	 132  
	 133  	// Use r.peek as first input byte.
	 134  	// Don't just return r.peek here: it might have been left by peekByte(false)
	 135  	// and this might be peekByte(true).
	 136  	c := r.peek
	 137  	if c == 0 {
	 138  		c = r.readByte()
	 139  	}
	 140  	for r.err == nil && !r.eof {
	 141  		if skipSpace {
	 142  			// For the purposes of this reader, semicolons are never necessary to
	 143  			// understand the input and are treated as spaces.
	 144  			switch c {
	 145  			case ' ', '\f', '\t', '\r', '\n', ';':
	 146  				c = r.readByte()
	 147  				continue
	 148  
	 149  			case '/':
	 150  				c = r.readByte()
	 151  				if c == '/' {
	 152  					for c != '\n' && r.err == nil && !r.eof {
	 153  						c = r.readByte()
	 154  					}
	 155  				} else if c == '*' {
	 156  					var c1 byte
	 157  					for (c != '*' || c1 != '/') && r.err == nil {
	 158  						if r.eof {
	 159  							r.syntaxError()
	 160  						}
	 161  						c, c1 = c1, r.readByte()
	 162  					}
	 163  				} else {
	 164  					r.syntaxError()
	 165  				}
	 166  				c = r.readByte()
	 167  				continue
	 168  			}
	 169  		}
	 170  		break
	 171  	}
	 172  	r.peek = c
	 173  	return r.peek
	 174  }
	 175  
	 176  // nextByte is like peekByte but advances beyond the returned byte.
	 177  func (r *importReader) nextByte(skipSpace bool) byte {
	 178  	c := r.peekByte(skipSpace)
	 179  	r.peek = 0
	 180  	return c
	 181  }
	 182  
	 183  var goEmbed = []byte("go:embed")
	 184  
	 185  // findEmbed advances the input reader to the next //go:embed comment.
	 186  // It reports whether it found a comment.
	 187  // (Otherwise it found an error or EOF.)
	 188  func (r *importReader) findEmbed(first bool) bool {
	 189  	// The import block scan stopped after a non-space character,
	 190  	// so the reader is not at the start of a line on the first call.
	 191  	// After that, each //go:embed extraction leaves the reader
	 192  	// at the end of a line.
	 193  	startLine := !first
	 194  	var c byte
	 195  	for r.err == nil && !r.eof {
	 196  		c = r.readByteNoBuf()
	 197  	Reswitch:
	 198  		switch c {
	 199  		default:
	 200  			startLine = false
	 201  
	 202  		case '\n':
	 203  			startLine = true
	 204  
	 205  		case ' ', '\t':
	 206  			// leave startLine alone
	 207  
	 208  		case '"':
	 209  			startLine = false
	 210  			for r.err == nil {
	 211  				if r.eof {
	 212  					r.syntaxError()
	 213  				}
	 214  				c = r.readByteNoBuf()
	 215  				if c == '\\' {
	 216  					r.readByteNoBuf()
	 217  					if r.err != nil {
	 218  						r.syntaxError()
	 219  						return false
	 220  					}
	 221  					continue
	 222  				}
	 223  				if c == '"' {
	 224  					c = r.readByteNoBuf()
	 225  					goto Reswitch
	 226  				}
	 227  			}
	 228  			goto Reswitch
	 229  
	 230  		case '`':
	 231  			startLine = false
	 232  			for r.err == nil {
	 233  				if r.eof {
	 234  					r.syntaxError()
	 235  				}
	 236  				c = r.readByteNoBuf()
	 237  				if c == '`' {
	 238  					c = r.readByteNoBuf()
	 239  					goto Reswitch
	 240  				}
	 241  			}
	 242  
	 243  		case '/':
	 244  			c = r.readByteNoBuf()
	 245  			switch c {
	 246  			default:
	 247  				startLine = false
	 248  				goto Reswitch
	 249  
	 250  			case '*':
	 251  				var c1 byte
	 252  				for (c != '*' || c1 != '/') && r.err == nil {
	 253  					if r.eof {
	 254  						r.syntaxError()
	 255  					}
	 256  					c, c1 = c1, r.readByteNoBuf()
	 257  				}
	 258  				startLine = false
	 259  
	 260  			case '/':
	 261  				if startLine {
	 262  					// Try to read this as a //go:embed comment.
	 263  					for i := range goEmbed {
	 264  						c = r.readByteNoBuf()
	 265  						if c != goEmbed[i] {
	 266  							goto SkipSlashSlash
	 267  						}
	 268  					}
	 269  					c = r.readByteNoBuf()
	 270  					if c == ' ' || c == '\t' {
	 271  						// Found one!
	 272  						return true
	 273  					}
	 274  				}
	 275  			SkipSlashSlash:
	 276  				for c != '\n' && r.err == nil && !r.eof {
	 277  					c = r.readByteNoBuf()
	 278  				}
	 279  				startLine = true
	 280  			}
	 281  		}
	 282  	}
	 283  	return false
	 284  }
	 285  
	 286  // readKeyword reads the given keyword from the input.
	 287  // If the keyword is not present, readKeyword records a syntax error.
	 288  func (r *importReader) readKeyword(kw string) {
	 289  	r.peekByte(true)
	 290  	for i := 0; i < len(kw); i++ {
	 291  		if r.nextByte(false) != kw[i] {
	 292  			r.syntaxError()
	 293  			return
	 294  		}
	 295  	}
	 296  	if isIdent(r.peekByte(false)) {
	 297  		r.syntaxError()
	 298  	}
	 299  }
	 300  
	 301  // readIdent reads an identifier from the input.
	 302  // If an identifier is not present, readIdent records a syntax error.
	 303  func (r *importReader) readIdent() {
	 304  	c := r.peekByte(true)
	 305  	if !isIdent(c) {
	 306  		r.syntaxError()
	 307  		return
	 308  	}
	 309  	for isIdent(r.peekByte(false)) {
	 310  		r.peek = 0
	 311  	}
	 312  }
	 313  
	 314  // readString reads a quoted string literal from the input.
	 315  // If an identifier is not present, readString records a syntax error.
	 316  func (r *importReader) readString() {
	 317  	switch r.nextByte(true) {
	 318  	case '`':
	 319  		for r.err == nil {
	 320  			if r.nextByte(false) == '`' {
	 321  				break
	 322  			}
	 323  			if r.eof {
	 324  				r.syntaxError()
	 325  			}
	 326  		}
	 327  	case '"':
	 328  		for r.err == nil {
	 329  			c := r.nextByte(false)
	 330  			if c == '"' {
	 331  				break
	 332  			}
	 333  			if r.eof || c == '\n' {
	 334  				r.syntaxError()
	 335  			}
	 336  			if c == '\\' {
	 337  				r.nextByte(false)
	 338  			}
	 339  		}
	 340  	default:
	 341  		r.syntaxError()
	 342  	}
	 343  }
	 344  
	 345  // readImport reads an import clause - optional identifier followed by quoted string -
	 346  // from the input.
	 347  func (r *importReader) readImport() {
	 348  	c := r.peekByte(true)
	 349  	if c == '.' {
	 350  		r.peek = 0
	 351  	} else if isIdent(c) {
	 352  		r.readIdent()
	 353  	}
	 354  	r.readString()
	 355  }
	 356  
	 357  // readComments is like io.ReadAll, except that it only reads the leading
	 358  // block of comments in the file.
	 359  func readComments(f io.Reader) ([]byte, error) {
	 360  	r := newImportReader("", f)
	 361  	r.peekByte(true)
	 362  	if r.err == nil && !r.eof {
	 363  		// Didn't reach EOF, so must have found a non-space byte. Remove it.
	 364  		r.buf = r.buf[:len(r.buf)-1]
	 365  	}
	 366  	return r.buf, r.err
	 367  }
	 368  
	 369  // readGoInfo expects a Go file as input and reads the file up to and including the import section.
	 370  // It records what it learned in *info.
	 371  // If info.fset is non-nil, readGoInfo parses the file and sets info.parsed, info.parseErr,
	 372  // info.imports, info.embeds, and info.embedErr.
	 373  //
	 374  // It only returns an error if there are problems reading the file,
	 375  // not for syntax errors in the file itself.
	 376  func readGoInfo(f io.Reader, info *fileInfo) error {
	 377  	r := newImportReader(info.name, f)
	 378  
	 379  	r.readKeyword("package")
	 380  	r.readIdent()
	 381  	for r.peekByte(true) == 'i' {
	 382  		r.readKeyword("import")
	 383  		if r.peekByte(true) == '(' {
	 384  			r.nextByte(false)
	 385  			for r.peekByte(true) != ')' && r.err == nil {
	 386  				r.readImport()
	 387  			}
	 388  			r.nextByte(false)
	 389  		} else {
	 390  			r.readImport()
	 391  		}
	 392  	}
	 393  
	 394  	info.header = r.buf
	 395  
	 396  	// If we stopped successfully before EOF, we read a byte that told us we were done.
	 397  	// Return all but that last byte, which would cause a syntax error if we let it through.
	 398  	if r.err == nil && !r.eof {
	 399  		info.header = r.buf[:len(r.buf)-1]
	 400  	}
	 401  
	 402  	// If we stopped for a syntax error, consume the whole file so that
	 403  	// we are sure we don't change the errors that go/parser returns.
	 404  	if r.err == errSyntax {
	 405  		r.err = nil
	 406  		for r.err == nil && !r.eof {
	 407  			r.readByte()
	 408  		}
	 409  		info.header = r.buf
	 410  	}
	 411  	if r.err != nil {
	 412  		return r.err
	 413  	}
	 414  
	 415  	if info.fset == nil {
	 416  		return nil
	 417  	}
	 418  
	 419  	// Parse file header & record imports.
	 420  	info.parsed, info.parseErr = parser.ParseFile(info.fset, info.name, info.header, parser.ImportsOnly|parser.ParseComments)
	 421  	if info.parseErr != nil {
	 422  		return nil
	 423  	}
	 424  
	 425  	hasEmbed := false
	 426  	for _, decl := range info.parsed.Decls {
	 427  		d, ok := decl.(*ast.GenDecl)
	 428  		if !ok {
	 429  			continue
	 430  		}
	 431  		for _, dspec := range d.Specs {
	 432  			spec, ok := dspec.(*ast.ImportSpec)
	 433  			if !ok {
	 434  				continue
	 435  			}
	 436  			quoted := spec.Path.Value
	 437  			path, err := strconv.Unquote(quoted)
	 438  			if err != nil {
	 439  				return fmt.Errorf("parser returned invalid quoted string: <%s>", quoted)
	 440  			}
	 441  			if path == "embed" {
	 442  				hasEmbed = true
	 443  			}
	 444  
	 445  			doc := spec.Doc
	 446  			if doc == nil && len(d.Specs) == 1 {
	 447  				doc = d.Doc
	 448  			}
	 449  			info.imports = append(info.imports, fileImport{path, spec.Pos(), doc})
	 450  		}
	 451  	}
	 452  
	 453  	// If the file imports "embed",
	 454  	// we have to look for //go:embed comments
	 455  	// in the remainder of the file.
	 456  	// The compiler will enforce the mapping of comments to
	 457  	// declared variables. We just need to know the patterns.
	 458  	// If there were //go:embed comments earlier in the file
	 459  	// (near the package statement or imports), the compiler
	 460  	// will reject them. They can be (and have already been) ignored.
	 461  	if hasEmbed {
	 462  		var line []byte
	 463  		for first := true; r.findEmbed(first); first = false {
	 464  			line = line[:0]
	 465  			pos := r.pos
	 466  			for {
	 467  				c := r.readByteNoBuf()
	 468  				if c == '\n' || r.err != nil || r.eof {
	 469  					break
	 470  				}
	 471  				line = append(line, c)
	 472  			}
	 473  			// Add args if line is well-formed.
	 474  			// Ignore badly-formed lines - the compiler will report them when it finds them,
	 475  			// and we can pretend they are not there to help go list succeed with what it knows.
	 476  			embs, err := parseGoEmbed(string(line), pos)
	 477  			if err == nil {
	 478  				info.embeds = append(info.embeds, embs...)
	 479  			}
	 480  		}
	 481  	}
	 482  
	 483  	return nil
	 484  }
	 485  
	 486  // parseGoEmbed parses the text following "//go:embed" to extract the glob patterns.
	 487  // It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings.
	 488  // This is based on a similar function in cmd/compile/internal/gc/noder.go;
	 489  // this version calculates position information as well.
	 490  func parseGoEmbed(args string, pos token.Position) ([]fileEmbed, error) {
	 491  	trimBytes := func(n int) {
	 492  		pos.Offset += n
	 493  		pos.Column += utf8.RuneCountInString(args[:n])
	 494  		args = args[n:]
	 495  	}
	 496  	trimSpace := func() {
	 497  		trim := strings.TrimLeftFunc(args, unicode.IsSpace)
	 498  		trimBytes(len(args) - len(trim))
	 499  	}
	 500  
	 501  	var list []fileEmbed
	 502  	for trimSpace(); args != ""; trimSpace() {
	 503  		var path string
	 504  		pathPos := pos
	 505  	Switch:
	 506  		switch args[0] {
	 507  		default:
	 508  			i := len(args)
	 509  			for j, c := range args {
	 510  				if unicode.IsSpace(c) {
	 511  					i = j
	 512  					break
	 513  				}
	 514  			}
	 515  			path = args[:i]
	 516  			trimBytes(i)
	 517  
	 518  		case '`':
	 519  			i := strings.Index(args[1:], "`")
	 520  			if i < 0 {
	 521  				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
	 522  			}
	 523  			path = args[1 : 1+i]
	 524  			trimBytes(1 + i + 1)
	 525  
	 526  		case '"':
	 527  			i := 1
	 528  			for ; i < len(args); i++ {
	 529  				if args[i] == '\\' {
	 530  					i++
	 531  					continue
	 532  				}
	 533  				if args[i] == '"' {
	 534  					q, err := strconv.Unquote(args[:i+1])
	 535  					if err != nil {
	 536  						return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1])
	 537  					}
	 538  					path = q
	 539  					trimBytes(i + 1)
	 540  					break Switch
	 541  				}
	 542  			}
	 543  			if i >= len(args) {
	 544  				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
	 545  			}
	 546  		}
	 547  
	 548  		if args != "" {
	 549  			r, _ := utf8.DecodeRuneInString(args)
	 550  			if !unicode.IsSpace(r) {
	 551  				return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
	 552  			}
	 553  		}
	 554  		list = append(list, fileEmbed{path, pathPos})
	 555  	}
	 556  	return list, nil
	 557  }
	 558  

View as plain text