mirror of https://github.com/golang/go.git
exp/types/staging: updated gcimporter
Mostly minor changes to match the new definitions in types.go and const.go. R=rsc, r CC=golang-dev https://golang.org/cl/6506101
This commit is contained in:
parent
ebf56e086d
commit
1785bfca6b
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright 2011 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.
|
||||
|
||||
// This file implements FindGcExportData.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func readGopackHeader(r *bufio.Reader) (name string, size int, err error) {
|
||||
// See $GOROOT/include/ar.h.
|
||||
hdr := make([]byte, 16+12+6+6+8+10+2)
|
||||
_, err = io.ReadFull(r, hdr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if trace {
|
||||
fmt.Printf("header: %s", hdr)
|
||||
}
|
||||
s := strings.TrimSpace(string(hdr[16+12+6+6+8:][:10]))
|
||||
size, err = strconv.Atoi(s)
|
||||
if err != nil || hdr[len(hdr)-2] != '`' || hdr[len(hdr)-1] != '\n' {
|
||||
err = errors.New("invalid archive header")
|
||||
return
|
||||
}
|
||||
name = strings.TrimSpace(string(hdr[:16]))
|
||||
return
|
||||
}
|
||||
|
||||
// FindGcExportData positions the reader r at the beginning of the
|
||||
// export data section of an underlying GC-created object/archive
|
||||
// file by reading from it. The reader must be positioned at the
|
||||
// start of the file before calling this function.
|
||||
//
|
||||
func FindGcExportData(r *bufio.Reader) (err error) {
|
||||
// Read first line to make sure this is an object file.
|
||||
line, err := r.ReadSlice('\n')
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if string(line) == "!<arch>\n" {
|
||||
// Archive file. Scan to __.PKGDEF, which should
|
||||
// be second archive entry.
|
||||
var name string
|
||||
var size int
|
||||
|
||||
// First entry should be __.GOSYMDEF.
|
||||
// Older archives used __.SYMDEF, so allow that too.
|
||||
// Read and discard.
|
||||
if name, size, err = readGopackHeader(r); err != nil {
|
||||
return
|
||||
}
|
||||
if name != "__.SYMDEF" && name != "__.GOSYMDEF" {
|
||||
err = errors.New("go archive does not begin with __.SYMDEF or __.GOSYMDEF")
|
||||
return
|
||||
}
|
||||
const block = 4096
|
||||
tmp := make([]byte, block)
|
||||
for size > 0 {
|
||||
n := size
|
||||
if n > block {
|
||||
n = block
|
||||
}
|
||||
if _, err = io.ReadFull(r, tmp[:n]); err != nil {
|
||||
return
|
||||
}
|
||||
size -= n
|
||||
}
|
||||
|
||||
// Second entry should be __.PKGDEF.
|
||||
if name, size, err = readGopackHeader(r); err != nil {
|
||||
return
|
||||
}
|
||||
if name != "__.PKGDEF" {
|
||||
err = errors.New("go archive is missing __.PKGDEF")
|
||||
return
|
||||
}
|
||||
|
||||
// Read first line of __.PKGDEF data, so that line
|
||||
// is once again the first line of the input.
|
||||
if line, err = r.ReadSlice('\n'); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Now at __.PKGDEF in archive or still at beginning of file.
|
||||
// Either way, line should begin with "go object ".
|
||||
if !strings.HasPrefix(string(line), "go object ") {
|
||||
err = errors.New("not a go object file")
|
||||
return
|
||||
}
|
||||
|
||||
// Skip over object header to export data.
|
||||
// Begins after first line with $$.
|
||||
for line[0] != '$' {
|
||||
if line, err = r.ReadSlice('\n'); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,887 @@
|
|||
// Copyright 2011 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.
|
||||
|
||||
// This file implements an ast.Importer for gc-generated object files.
|
||||
// TODO(gri) Eventually move this into a separate package outside types.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
)
|
||||
|
||||
var pkgExts = [...]string{".a", ".5", ".6", ".8"}
|
||||
|
||||
// FindPkg returns the filename and unique package id for an import
|
||||
// path based on package information provided by build.Import (using
|
||||
// the build.Default build.Context).
|
||||
// If no file was found, an empty filename is returned.
|
||||
//
|
||||
func FindPkg(path, srcDir string) (filename, id string) {
|
||||
if len(path) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
id = path
|
||||
var noext string
|
||||
switch {
|
||||
default:
|
||||
// "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
|
||||
bp, _ := build.Import(path, srcDir, build.FindOnly)
|
||||
if bp.PkgObj == "" {
|
||||
return
|
||||
}
|
||||
noext = bp.PkgObj
|
||||
if strings.HasSuffix(noext, ".a") {
|
||||
noext = noext[:len(noext)-len(".a")]
|
||||
}
|
||||
|
||||
case build.IsLocalImport(path):
|
||||
// "./x" -> "/this/directory/x.ext", "/this/directory/x"
|
||||
noext = filepath.Join(srcDir, path)
|
||||
id = noext
|
||||
|
||||
case filepath.IsAbs(path):
|
||||
// for completeness only - go/build.Import
|
||||
// does not support absolute imports
|
||||
// "/x" -> "/x.ext", "/x"
|
||||
noext = path
|
||||
}
|
||||
|
||||
// try extensions
|
||||
for _, ext := range pkgExts {
|
||||
filename = noext + ext
|
||||
if f, err := os.Stat(filename); err == nil && !f.IsDir() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
filename = "" // not found
|
||||
return
|
||||
}
|
||||
|
||||
// GcImportData imports a package by reading the gc-generated export data,
|
||||
// adds the corresponding package object to the imports map indexed by id,
|
||||
// and returns the object.
|
||||
//
|
||||
// The imports map must contains all packages already imported, and no map
|
||||
// entry with id as the key must be present. The data reader position must
|
||||
// be the beginning of the export data section. The filename is only used
|
||||
// in error messages.
|
||||
//
|
||||
func GcImportData(imports map[string]*ast.Object, filename, id string, data *bufio.Reader) (pkg *ast.Object, err error) {
|
||||
if trace {
|
||||
fmt.Printf("importing %s (%s)\n", id, filename)
|
||||
}
|
||||
|
||||
// support for gcParser error handling
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = r.(importError) // will re-panic if r is not an importError
|
||||
}
|
||||
}()
|
||||
|
||||
var p gcParser
|
||||
p.init(filename, id, data, imports)
|
||||
pkg = p.parseExport()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GcImport imports a gc-generated package given its import path, adds the
|
||||
// corresponding package object to the imports map, and returns the object.
|
||||
// Local import paths are interpreted relative to the current working directory.
|
||||
// The imports map must contains all packages already imported.
|
||||
// GcImport satisfies the ast.Importer signature.
|
||||
//
|
||||
func GcImport(imports map[string]*ast.Object, path string) (pkg *ast.Object, err error) {
|
||||
if path == "unsafe" {
|
||||
return Unsafe, nil
|
||||
}
|
||||
|
||||
srcDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
filename, id := FindPkg(path, srcDir)
|
||||
if filename == "" {
|
||||
err = errors.New("can't find import: " + id)
|
||||
return
|
||||
}
|
||||
|
||||
// Note: imports[id] may already contain a partially imported package.
|
||||
// We must continue doing the full import here since we don't
|
||||
// know if something is missing.
|
||||
// TODO: There's no need to re-import a package if we know that we
|
||||
// have done a full import before. At the moment we cannot
|
||||
// tell from the available information in this function alone.
|
||||
|
||||
// open file
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
f.Close()
|
||||
if err != nil {
|
||||
// Add file name to error.
|
||||
err = fmt.Errorf("reading export data: %s: %v", filename, err)
|
||||
}
|
||||
}()
|
||||
|
||||
buf := bufio.NewReader(f)
|
||||
if err = FindGcExportData(buf); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
pkg, err = GcImportData(imports, filename, id, buf)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// gcParser
|
||||
|
||||
// gcParser parses the exports inside a gc compiler-produced
|
||||
// object/archive file and populates its scope with the results.
|
||||
type gcParser struct {
|
||||
scanner scanner.Scanner
|
||||
tok rune // current token
|
||||
lit string // literal string; only valid for Ident, Int, String tokens
|
||||
id string // package id of imported package
|
||||
imports map[string]*ast.Object // package id -> package object
|
||||
}
|
||||
|
||||
func (p *gcParser) init(filename, id string, src io.Reader, imports map[string]*ast.Object) {
|
||||
p.scanner.Init(src)
|
||||
p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) }
|
||||
p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanChars | scanner.ScanStrings | scanner.ScanComments | scanner.SkipComments
|
||||
p.scanner.Whitespace = 1<<'\t' | 1<<' '
|
||||
p.scanner.Filename = filename // for good error messages
|
||||
p.next()
|
||||
p.id = id
|
||||
p.imports = imports
|
||||
}
|
||||
|
||||
func (p *gcParser) next() {
|
||||
p.tok = p.scanner.Scan()
|
||||
switch p.tok {
|
||||
case scanner.Ident, scanner.Int, scanner.Char, scanner.String, '·':
|
||||
p.lit = p.scanner.TokenText()
|
||||
default:
|
||||
p.lit = ""
|
||||
}
|
||||
if trace {
|
||||
fmt.Printf("%s: %q -> %q\n", scanner.TokenString(p.tok), p.scanner.TokenText(), p.lit)
|
||||
}
|
||||
}
|
||||
|
||||
// Declare inserts a named object of the given kind in scope.
|
||||
func (p *gcParser) declare(scope *ast.Scope, kind ast.ObjKind, name string) *ast.Object {
|
||||
// the object may have been imported before - if it exists
|
||||
// already in the respective package scope, return that object
|
||||
if obj := scope.Lookup(name); obj != nil {
|
||||
assert(obj.Kind == kind)
|
||||
return obj
|
||||
}
|
||||
|
||||
// otherwise create a new object and insert it into the package scope
|
||||
obj := ast.NewObj(kind, name)
|
||||
if scope.Insert(obj) != nil {
|
||||
p.errorf("already declared: %v %s", kind, obj.Name)
|
||||
}
|
||||
|
||||
// if the new type object is a named type it may be referred
|
||||
// to before the underlying type is known - set it up
|
||||
if kind == ast.Typ {
|
||||
obj.Type = &NamedType{Obj: obj}
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Error handling
|
||||
|
||||
// Internal errors are boxed as importErrors.
|
||||
type importError struct {
|
||||
pos scanner.Position
|
||||
err error
|
||||
}
|
||||
|
||||
func (e importError) Error() string {
|
||||
return fmt.Sprintf("import error %s (byte offset = %d): %s", e.pos, e.pos.Offset, e.err)
|
||||
}
|
||||
|
||||
func (p *gcParser) error(err interface{}) {
|
||||
if s, ok := err.(string); ok {
|
||||
err = errors.New(s)
|
||||
}
|
||||
// panic with a runtime.Error if err is not an error
|
||||
panic(importError{p.scanner.Pos(), err.(error)})
|
||||
}
|
||||
|
||||
func (p *gcParser) errorf(format string, args ...interface{}) {
|
||||
p.error(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (p *gcParser) expect(tok rune) string {
|
||||
lit := p.lit
|
||||
if p.tok != tok {
|
||||
p.errorf("expected %s, got %s (%s)", scanner.TokenString(tok), scanner.TokenString(p.tok), lit)
|
||||
}
|
||||
p.next()
|
||||
return lit
|
||||
}
|
||||
|
||||
func (p *gcParser) expectSpecial(tok string) {
|
||||
sep := 'x' // not white space
|
||||
i := 0
|
||||
for i < len(tok) && p.tok == rune(tok[i]) && sep > ' ' {
|
||||
sep = p.scanner.Peek() // if sep <= ' ', there is white space before the next token
|
||||
p.next()
|
||||
i++
|
||||
}
|
||||
if i < len(tok) {
|
||||
p.errorf("expected %q, got %q", tok, tok[0:i])
|
||||
}
|
||||
}
|
||||
|
||||
func (p *gcParser) expectKeyword(keyword string) {
|
||||
lit := p.expect(scanner.Ident)
|
||||
if lit != keyword {
|
||||
p.errorf("expected keyword %s, got %q", keyword, lit)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Import declarations
|
||||
|
||||
// ImportPath = string_lit .
|
||||
//
|
||||
func (p *gcParser) parsePkgId() *ast.Object {
|
||||
id, err := strconv.Unquote(p.expect(scanner.String))
|
||||
if err != nil {
|
||||
p.error(err)
|
||||
}
|
||||
|
||||
switch id {
|
||||
case "":
|
||||
// id == "" stands for the imported package id
|
||||
// (only known at time of package installation)
|
||||
id = p.id
|
||||
case "unsafe":
|
||||
// package unsafe is not in the imports map - handle explicitly
|
||||
return Unsafe
|
||||
}
|
||||
|
||||
pkg := p.imports[id]
|
||||
if pkg == nil {
|
||||
pkg = ast.NewObj(ast.Pkg, "")
|
||||
pkg.Data = ast.NewScope(nil)
|
||||
p.imports[id] = pkg
|
||||
}
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
||||
// dotIdentifier = ( ident | '·' ) { ident | int | '·' } .
|
||||
func (p *gcParser) parseDotIdent() string {
|
||||
ident := ""
|
||||
if p.tok != scanner.Int {
|
||||
sep := 'x' // not white space
|
||||
for (p.tok == scanner.Ident || p.tok == scanner.Int || p.tok == '·') && sep > ' ' {
|
||||
ident += p.lit
|
||||
sep = p.scanner.Peek() // if sep <= ' ', there is white space before the next token
|
||||
p.next()
|
||||
}
|
||||
}
|
||||
if ident == "" {
|
||||
p.expect(scanner.Ident) // use expect() for error handling
|
||||
}
|
||||
return ident
|
||||
}
|
||||
|
||||
// ExportedName = "@" ImportPath "." dotIdentifier .
|
||||
//
|
||||
func (p *gcParser) parseExportedName() (*ast.Object, string) {
|
||||
p.expect('@')
|
||||
pkg := p.parsePkgId()
|
||||
p.expect('.')
|
||||
name := p.parseDotIdent()
|
||||
return pkg, name
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Types
|
||||
|
||||
// BasicType = identifier .
|
||||
//
|
||||
func (p *gcParser) parseBasicType() Type {
|
||||
id := p.expect(scanner.Ident)
|
||||
obj := Universe.Lookup(id)
|
||||
if obj == nil || obj.Kind != ast.Typ {
|
||||
p.errorf("not a basic type: %s", id)
|
||||
}
|
||||
return obj.Type.(Type)
|
||||
}
|
||||
|
||||
// ArrayType = "[" int_lit "]" Type .
|
||||
//
|
||||
func (p *gcParser) parseArrayType() Type {
|
||||
// "[" already consumed and lookahead known not to be "]"
|
||||
lit := p.expect(scanner.Int)
|
||||
p.expect(']')
|
||||
elt := p.parseType()
|
||||
n, err := strconv.ParseInt(lit, 10, 64)
|
||||
if err != nil {
|
||||
p.error(err)
|
||||
}
|
||||
return &Array{Len: n, Elt: elt}
|
||||
}
|
||||
|
||||
// MapType = "map" "[" Type "]" Type .
|
||||
//
|
||||
func (p *gcParser) parseMapType() Type {
|
||||
p.expectKeyword("map")
|
||||
p.expect('[')
|
||||
key := p.parseType()
|
||||
p.expect(']')
|
||||
elt := p.parseType()
|
||||
return &Map{Key: key, Elt: elt}
|
||||
}
|
||||
|
||||
// Name = identifier | "?" | ExportedName .
|
||||
//
|
||||
func (p *gcParser) parseName() (name string) {
|
||||
switch p.tok {
|
||||
case scanner.Ident:
|
||||
name = p.lit
|
||||
p.next()
|
||||
case '?':
|
||||
// anonymous
|
||||
p.next()
|
||||
case '@':
|
||||
// exported name prefixed with package path
|
||||
_, name = p.parseExportedName()
|
||||
default:
|
||||
p.error("name expected")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Field = Name Type [ string_lit ] .
|
||||
//
|
||||
func (p *gcParser) parseField() *StructField {
|
||||
var f StructField
|
||||
f.Name = p.parseName()
|
||||
f.Type = p.parseType()
|
||||
if p.tok == scanner.String {
|
||||
f.Tag = p.expect(scanner.String)
|
||||
}
|
||||
if f.Name == "" {
|
||||
// anonymous field - typ must be T or *T and T must be a type name
|
||||
if typ, ok := deref(f.Type).(*NamedType); ok && typ.Obj != nil {
|
||||
f.Name = typ.Obj.Name
|
||||
} else {
|
||||
p.errorf("anonymous field expected")
|
||||
}
|
||||
}
|
||||
return &f
|
||||
}
|
||||
|
||||
// StructType = "struct" "{" [ FieldList ] "}" .
|
||||
// FieldList = Field { ";" Field } .
|
||||
//
|
||||
func (p *gcParser) parseStructType() Type {
|
||||
var fields []*StructField
|
||||
|
||||
parseField := func() {
|
||||
fields = append(fields, p.parseField())
|
||||
}
|
||||
|
||||
p.expectKeyword("struct")
|
||||
p.expect('{')
|
||||
if p.tok != '}' {
|
||||
parseField()
|
||||
for p.tok == ';' {
|
||||
p.next()
|
||||
parseField()
|
||||
}
|
||||
}
|
||||
p.expect('}')
|
||||
|
||||
return &Struct{Fields: fields}
|
||||
}
|
||||
|
||||
// Parameter = ( identifier | "?" ) [ "..." ] Type [ string_lit ] .
|
||||
//
|
||||
func (p *gcParser) parseParameter() (par *ast.Object, isVariadic bool) {
|
||||
name := p.parseName()
|
||||
if name == "" {
|
||||
name = "_" // cannot access unnamed identifiers
|
||||
}
|
||||
if p.tok == '.' {
|
||||
p.expectSpecial("...")
|
||||
isVariadic = true
|
||||
}
|
||||
ptyp := p.parseType()
|
||||
// ignore argument tag (e.g. "noescape")
|
||||
if p.tok == scanner.String {
|
||||
p.expect(scanner.String)
|
||||
}
|
||||
par = ast.NewObj(ast.Var, name)
|
||||
par.Type = ptyp
|
||||
return
|
||||
}
|
||||
|
||||
// Parameters = "(" [ ParameterList ] ")" .
|
||||
// ParameterList = { Parameter "," } Parameter .
|
||||
//
|
||||
func (p *gcParser) parseParameters() (list []*ast.Object, isVariadic bool) {
|
||||
parseParameter := func() {
|
||||
par, variadic := p.parseParameter()
|
||||
list = append(list, par)
|
||||
if variadic {
|
||||
if isVariadic {
|
||||
p.error("... not on final argument")
|
||||
}
|
||||
isVariadic = true
|
||||
}
|
||||
}
|
||||
|
||||
p.expect('(')
|
||||
if p.tok != ')' {
|
||||
parseParameter()
|
||||
for p.tok == ',' {
|
||||
p.next()
|
||||
parseParameter()
|
||||
}
|
||||
}
|
||||
p.expect(')')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Signature = Parameters [ Result ] .
|
||||
// Result = Type | Parameters .
|
||||
//
|
||||
func (p *gcParser) parseSignature() *Signature {
|
||||
params, isVariadic := p.parseParameters()
|
||||
|
||||
// optional result type
|
||||
var results []*ast.Object
|
||||
switch p.tok {
|
||||
case scanner.Ident, '[', '*', '<', '@':
|
||||
// single, unnamed result
|
||||
result := ast.NewObj(ast.Var, "_")
|
||||
result.Type = p.parseType()
|
||||
results = []*ast.Object{result}
|
||||
case '(':
|
||||
// named or multiple result(s)
|
||||
var variadic bool
|
||||
results, variadic = p.parseParameters()
|
||||
if variadic {
|
||||
p.error("... not permitted on result type")
|
||||
}
|
||||
}
|
||||
|
||||
return &Signature{Params: params, Results: results, IsVariadic: isVariadic}
|
||||
}
|
||||
|
||||
// InterfaceType = "interface" "{" [ MethodList ] "}" .
|
||||
// MethodList = Method { ";" Method } .
|
||||
// Method = Name Signature .
|
||||
//
|
||||
// (The methods of embedded interfaces are always "inlined"
|
||||
// by the compiler and thus embedded interfaces are never
|
||||
// visible in the export data.)
|
||||
//
|
||||
func (p *gcParser) parseInterfaceType() Type {
|
||||
var methods ObjList
|
||||
|
||||
parseMethod := func() {
|
||||
obj := ast.NewObj(ast.Fun, p.parseName())
|
||||
obj.Type = p.parseSignature()
|
||||
methods = append(methods, obj)
|
||||
}
|
||||
|
||||
p.expectKeyword("interface")
|
||||
p.expect('{')
|
||||
if p.tok != '}' {
|
||||
parseMethod()
|
||||
for p.tok == ';' {
|
||||
p.next()
|
||||
parseMethod()
|
||||
}
|
||||
}
|
||||
p.expect('}')
|
||||
|
||||
methods.Sort()
|
||||
return &Interface{Methods: methods}
|
||||
}
|
||||
|
||||
// ChanType = ( "chan" [ "<-" ] | "<-" "chan" ) Type .
|
||||
//
|
||||
func (p *gcParser) parseChanType() Type {
|
||||
dir := ast.SEND | ast.RECV
|
||||
if p.tok == scanner.Ident {
|
||||
p.expectKeyword("chan")
|
||||
if p.tok == '<' {
|
||||
p.expectSpecial("<-")
|
||||
dir = ast.SEND
|
||||
}
|
||||
} else {
|
||||
p.expectSpecial("<-")
|
||||
p.expectKeyword("chan")
|
||||
dir = ast.RECV
|
||||
}
|
||||
elt := p.parseType()
|
||||
return &Chan{Dir: dir, Elt: elt}
|
||||
}
|
||||
|
||||
// Type =
|
||||
// BasicType | TypeName | ArrayType | SliceType | StructType |
|
||||
// PointerType | FuncType | InterfaceType | MapType | ChanType |
|
||||
// "(" Type ")" .
|
||||
// BasicType = ident .
|
||||
// TypeName = ExportedName .
|
||||
// SliceType = "[" "]" Type .
|
||||
// PointerType = "*" Type .
|
||||
// FuncType = "func" Signature .
|
||||
//
|
||||
func (p *gcParser) parseType() Type {
|
||||
switch p.tok {
|
||||
case scanner.Ident:
|
||||
switch p.lit {
|
||||
default:
|
||||
return p.parseBasicType()
|
||||
case "struct":
|
||||
return p.parseStructType()
|
||||
case "func":
|
||||
// FuncType
|
||||
p.next()
|
||||
return p.parseSignature()
|
||||
case "interface":
|
||||
return p.parseInterfaceType()
|
||||
case "map":
|
||||
return p.parseMapType()
|
||||
case "chan":
|
||||
return p.parseChanType()
|
||||
}
|
||||
case '@':
|
||||
// TypeName
|
||||
pkg, name := p.parseExportedName()
|
||||
return p.declare(pkg.Data.(*ast.Scope), ast.Typ, name).Type.(Type)
|
||||
case '[':
|
||||
p.next() // look ahead
|
||||
if p.tok == ']' {
|
||||
// SliceType
|
||||
p.next()
|
||||
return &Slice{Elt: p.parseType()}
|
||||
}
|
||||
return p.parseArrayType()
|
||||
case '*':
|
||||
// PointerType
|
||||
p.next()
|
||||
return &Pointer{Base: p.parseType()}
|
||||
case '<':
|
||||
return p.parseChanType()
|
||||
case '(':
|
||||
// "(" Type ")"
|
||||
p.next()
|
||||
typ := p.parseType()
|
||||
p.expect(')')
|
||||
return typ
|
||||
}
|
||||
p.errorf("expected type, got %s (%q)", scanner.TokenString(p.tok), p.lit)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Declarations
|
||||
|
||||
// ImportDecl = "import" identifier string_lit .
|
||||
//
|
||||
func (p *gcParser) parseImportDecl() {
|
||||
p.expectKeyword("import")
|
||||
// The identifier has no semantic meaning in the import data.
|
||||
// It exists so that error messages can print the real package
|
||||
// name: binary.ByteOrder instead of "encoding/binary".ByteOrder.
|
||||
name := p.expect(scanner.Ident)
|
||||
pkg := p.parsePkgId()
|
||||
assert(pkg.Name == "" || pkg.Name == name)
|
||||
pkg.Name = name
|
||||
}
|
||||
|
||||
// int_lit = [ "+" | "-" ] { "0" ... "9" } .
|
||||
//
|
||||
func (p *gcParser) parseInt() (neg bool, val string) {
|
||||
switch p.tok {
|
||||
case '-':
|
||||
neg = true
|
||||
fallthrough
|
||||
case '+':
|
||||
p.next()
|
||||
}
|
||||
val = p.expect(scanner.Int)
|
||||
return
|
||||
}
|
||||
|
||||
// number = int_lit [ "p" int_lit ] .
|
||||
//
|
||||
func (p *gcParser) parseNumber() (x operand) {
|
||||
x.mode = constant
|
||||
|
||||
// mantissa
|
||||
neg, val := p.parseInt()
|
||||
mant, ok := new(big.Int).SetString(val, 0)
|
||||
assert(ok)
|
||||
if neg {
|
||||
mant.Neg(mant)
|
||||
}
|
||||
|
||||
if p.lit == "p" {
|
||||
// exponent (base 2)
|
||||
p.next()
|
||||
neg, val = p.parseInt()
|
||||
exp64, err := strconv.ParseUint(val, 10, 0)
|
||||
if err != nil {
|
||||
p.error(err)
|
||||
}
|
||||
exp := uint(exp64)
|
||||
if neg {
|
||||
denom := big.NewInt(1)
|
||||
denom.Lsh(denom, exp)
|
||||
x.typ = Typ[UntypedFloat]
|
||||
x.val = normalizeRatConst(new(big.Rat).SetFrac(mant, denom))
|
||||
return
|
||||
}
|
||||
if exp > 0 {
|
||||
mant.Lsh(mant, exp)
|
||||
}
|
||||
x.typ = Typ[UntypedFloat]
|
||||
x.val = normalizeIntConst(mant)
|
||||
return
|
||||
}
|
||||
|
||||
x.typ = Typ[UntypedInt]
|
||||
x.val = normalizeIntConst(mant)
|
||||
return
|
||||
}
|
||||
|
||||
// ConstDecl = "const" ExportedName [ Type ] "=" Literal .
|
||||
// Literal = bool_lit | int_lit | float_lit | complex_lit | rune_lit | string_lit .
|
||||
// bool_lit = "true" | "false" .
|
||||
// complex_lit = "(" float_lit "+" float_lit ")" .
|
||||
// rune_lit = "(" int_lit "+" int_lit ")" .
|
||||
// string_lit = `"` { unicode_char } `"` .
|
||||
//
|
||||
func (p *gcParser) parseConstDecl() {
|
||||
p.expectKeyword("const")
|
||||
pkg, name := p.parseExportedName()
|
||||
obj := p.declare(pkg.Data.(*ast.Scope), ast.Con, name)
|
||||
var x operand
|
||||
if p.tok != '=' {
|
||||
obj.Type = p.parseType()
|
||||
}
|
||||
p.expect('=')
|
||||
switch p.tok {
|
||||
case scanner.Ident:
|
||||
// bool_lit
|
||||
if p.lit != "true" && p.lit != "false" {
|
||||
p.error("expected true or false")
|
||||
}
|
||||
x.typ = Typ[UntypedBool]
|
||||
x.val = p.lit == "true"
|
||||
p.next()
|
||||
|
||||
case '-', scanner.Int:
|
||||
// int_lit
|
||||
x = p.parseNumber()
|
||||
|
||||
case '(':
|
||||
// complex_lit or rune_lit
|
||||
p.next()
|
||||
if p.tok == scanner.Char {
|
||||
p.next()
|
||||
p.expect('+')
|
||||
x = p.parseNumber()
|
||||
x.typ = Typ[UntypedRune]
|
||||
p.expect(')')
|
||||
break
|
||||
}
|
||||
re := p.parseNumber()
|
||||
p.expect('+')
|
||||
im := p.parseNumber()
|
||||
p.expect(')')
|
||||
x.typ = Typ[UntypedComplex]
|
||||
// TODO(gri) fix this
|
||||
_, _ = re, im
|
||||
x.val = zeroConst
|
||||
|
||||
case scanner.Char:
|
||||
// rune_lit
|
||||
x.setConst(token.CHAR, p.lit)
|
||||
p.next()
|
||||
|
||||
case scanner.String:
|
||||
// string_lit
|
||||
x.setConst(token.STRING, p.lit)
|
||||
p.next()
|
||||
|
||||
default:
|
||||
p.errorf("expected literal got %s", scanner.TokenString(p.tok))
|
||||
}
|
||||
if obj.Type == nil {
|
||||
obj.Type = x.typ
|
||||
}
|
||||
assert(x.val != nil)
|
||||
obj.Data = x.val
|
||||
}
|
||||
|
||||
// TypeDecl = "type" ExportedName Type .
|
||||
//
|
||||
func (p *gcParser) parseTypeDecl() {
|
||||
p.expectKeyword("type")
|
||||
pkg, name := p.parseExportedName()
|
||||
obj := p.declare(pkg.Data.(*ast.Scope), ast.Typ, name)
|
||||
|
||||
// The type object may have been imported before and thus already
|
||||
// have a type associated with it. We still need to parse the type
|
||||
// structure, but throw it away if the object already has a type.
|
||||
// This ensures that all imports refer to the same type object for
|
||||
// a given type declaration.
|
||||
typ := p.parseType()
|
||||
|
||||
if name := obj.Type.(*NamedType); name.Underlying == nil {
|
||||
name.Underlying = typ
|
||||
}
|
||||
}
|
||||
|
||||
// VarDecl = "var" ExportedName Type .
|
||||
//
|
||||
func (p *gcParser) parseVarDecl() {
|
||||
p.expectKeyword("var")
|
||||
pkg, name := p.parseExportedName()
|
||||
obj := p.declare(pkg.Data.(*ast.Scope), ast.Var, name)
|
||||
obj.Type = p.parseType()
|
||||
}
|
||||
|
||||
// FuncBody = "{" ... "}" .
|
||||
//
|
||||
func (p *gcParser) parseFuncBody() {
|
||||
p.expect('{')
|
||||
for i := 1; i > 0; p.next() {
|
||||
switch p.tok {
|
||||
case '{':
|
||||
i++
|
||||
case '}':
|
||||
i--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FuncDecl = "func" ExportedName Signature [ FuncBody ] .
|
||||
//
|
||||
func (p *gcParser) parseFuncDecl() {
|
||||
// "func" already consumed
|
||||
pkg, name := p.parseExportedName()
|
||||
obj := p.declare(pkg.Data.(*ast.Scope), ast.Fun, name)
|
||||
obj.Type = p.parseSignature()
|
||||
if p.tok == '{' {
|
||||
p.parseFuncBody()
|
||||
}
|
||||
}
|
||||
|
||||
// MethodDecl = "func" Receiver Name Signature .
|
||||
// Receiver = "(" ( identifier | "?" ) [ "*" ] ExportedName ")" [ FuncBody ].
|
||||
//
|
||||
func (p *gcParser) parseMethodDecl() {
|
||||
// "func" already consumed
|
||||
p.expect('(')
|
||||
p.parseParameter() // receiver
|
||||
p.expect(')')
|
||||
p.parseName() // unexported method names in imports are qualified with their package.
|
||||
p.parseSignature()
|
||||
if p.tok == '{' {
|
||||
p.parseFuncBody()
|
||||
}
|
||||
}
|
||||
|
||||
// Decl = [ ImportDecl | ConstDecl | TypeDecl | VarDecl | FuncDecl | MethodDecl ] "\n" .
|
||||
//
|
||||
func (p *gcParser) parseDecl() {
|
||||
switch p.lit {
|
||||
case "import":
|
||||
p.parseImportDecl()
|
||||
case "const":
|
||||
p.parseConstDecl()
|
||||
case "type":
|
||||
p.parseTypeDecl()
|
||||
case "var":
|
||||
p.parseVarDecl()
|
||||
case "func":
|
||||
p.next() // look ahead
|
||||
if p.tok == '(' {
|
||||
p.parseMethodDecl()
|
||||
} else {
|
||||
p.parseFuncDecl()
|
||||
}
|
||||
}
|
||||
p.expect('\n')
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Export
|
||||
|
||||
// Export = "PackageClause { Decl } "$$" .
|
||||
// PackageClause = "package" identifier [ "safe" ] "\n" .
|
||||
//
|
||||
func (p *gcParser) parseExport() *ast.Object {
|
||||
p.expectKeyword("package")
|
||||
name := p.expect(scanner.Ident)
|
||||
if p.tok != '\n' {
|
||||
// A package is safe if it was compiled with the -u flag,
|
||||
// which disables the unsafe package.
|
||||
// TODO(gri) remember "safe" package
|
||||
p.expectKeyword("safe")
|
||||
}
|
||||
p.expect('\n')
|
||||
|
||||
pkg := p.imports[p.id]
|
||||
if pkg == nil {
|
||||
pkg = ast.NewObj(ast.Pkg, name)
|
||||
pkg.Data = ast.NewScope(nil)
|
||||
p.imports[p.id] = pkg
|
||||
}
|
||||
|
||||
for p.tok != '$' && p.tok != scanner.EOF {
|
||||
p.parseDecl()
|
||||
}
|
||||
|
||||
if ch := p.scanner.Peek(); p.tok != '$' || ch != '$' {
|
||||
// don't call next()/expect() since reading past the
|
||||
// export data may cause scanner errors (e.g. NUL chars)
|
||||
p.errorf("expected '$$', got %s %c", scanner.TokenString(p.tok), ch)
|
||||
}
|
||||
|
||||
if n := p.scanner.ErrorCount; n != 0 {
|
||||
p.errorf("expected no scanner errors, got %d", n)
|
||||
}
|
||||
|
||||
return pkg
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
// Copyright 2011 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.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var gcPath string // Go compiler path
|
||||
|
||||
func init() {
|
||||
// determine compiler
|
||||
var gc string
|
||||
switch runtime.GOARCH {
|
||||
case "386":
|
||||
gc = "8g"
|
||||
case "amd64":
|
||||
gc = "6g"
|
||||
case "arm":
|
||||
gc = "5g"
|
||||
default:
|
||||
gcPath = "unknown-GOARCH-compiler"
|
||||
return
|
||||
}
|
||||
gcPath = filepath.Join(build.ToolDir, gc)
|
||||
}
|
||||
|
||||
func compile(t *testing.T, dirname, filename string) string {
|
||||
cmd := exec.Command(gcPath, filename)
|
||||
cmd.Dir = dirname
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("%s %s failed: %s", gcPath, filename, err)
|
||||
return ""
|
||||
}
|
||||
t.Logf("%s", string(out))
|
||||
archCh, _ := build.ArchChar(runtime.GOARCH)
|
||||
// filename should end with ".go"
|
||||
return filepath.Join(dirname, filename[:len(filename)-2]+archCh)
|
||||
}
|
||||
|
||||
// Use the same global imports map for all tests. The effect is
|
||||
// as if all tested packages were imported into a single package.
|
||||
var imports = make(map[string]*ast.Object)
|
||||
|
||||
func testPath(t *testing.T, path string) bool {
|
||||
_, err := GcImport(imports, path)
|
||||
if err != nil {
|
||||
t.Errorf("testPath(%s): %s", path, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const maxTime = 3 * time.Second
|
||||
|
||||
func testDir(t *testing.T, dir string, endTime time.Time) (nimports int) {
|
||||
dirname := filepath.Join(runtime.GOROOT(), "pkg", runtime.GOOS+"_"+runtime.GOARCH, dir)
|
||||
list, err := ioutil.ReadDir(dirname)
|
||||
if err != nil {
|
||||
t.Errorf("testDir(%s): %s", dirname, err)
|
||||
}
|
||||
for _, f := range list {
|
||||
if time.Now().After(endTime) {
|
||||
t.Log("testing time used up")
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case !f.IsDir():
|
||||
// try extensions
|
||||
for _, ext := range pkgExts {
|
||||
if strings.HasSuffix(f.Name(), ext) {
|
||||
name := f.Name()[0 : len(f.Name())-len(ext)] // remove extension
|
||||
if testPath(t, filepath.Join(dir, name)) {
|
||||
nimports++
|
||||
}
|
||||
}
|
||||
}
|
||||
case f.IsDir():
|
||||
nimports += testDir(t, filepath.Join(dir, f.Name()), endTime)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func TestGcImport(t *testing.T) {
|
||||
// On cross-compile builds, the path will not exist.
|
||||
// Need to use GOHOSTOS, which is not available.
|
||||
if _, err := os.Stat(gcPath); err != nil {
|
||||
t.Logf("skipping test: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if outFn := compile(t, "testdata", "exports.go"); outFn != "" {
|
||||
defer os.Remove(outFn)
|
||||
}
|
||||
|
||||
nimports := 0
|
||||
if testPath(t, "./testdata/exports") {
|
||||
nimports++
|
||||
}
|
||||
nimports += testDir(t, "", time.Now().Add(maxTime)) // installed packages
|
||||
t.Logf("tested %d imports", nimports)
|
||||
}
|
||||
|
||||
var importedObjectTests = []struct {
|
||||
name string
|
||||
kind ast.ObjKind
|
||||
typ string
|
||||
}{
|
||||
{"unsafe.Pointer", ast.Typ, "Pointer"},
|
||||
{"math.Pi", ast.Con, "untyped float"},
|
||||
{"io.Reader", ast.Typ, "interface{Read(p []byte) (n int, err error)}"},
|
||||
{"io.ReadWriter", ast.Typ, "interface{Read(p []byte) (n int, err error); Write(p []byte) (n int, err error)}"},
|
||||
{"math.Sin", ast.Fun, "func(x float64) (_ float64)"},
|
||||
// TODO(gri) add more tests
|
||||
}
|
||||
|
||||
func TestGcImportedTypes(t *testing.T) {
|
||||
for _, test := range importedObjectTests {
|
||||
s := strings.Split(test.name, ".")
|
||||
if len(s) != 2 {
|
||||
t.Fatal("inconsistent test data")
|
||||
}
|
||||
importPath := s[0]
|
||||
objName := s[1]
|
||||
|
||||
pkg, err := GcImport(imports, importPath)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
obj := pkg.Data.(*ast.Scope).Lookup(objName)
|
||||
if obj.Kind != test.kind {
|
||||
t.Errorf("%s: got kind = %q; want %q", test.name, obj.Kind, test.kind)
|
||||
}
|
||||
typ := typeString(underlying(obj.Type.(Type)))
|
||||
if typ != test.typ {
|
||||
t.Errorf("%s: got type = %q; want %q", test.name, typ, test.typ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2011 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.
|
||||
|
||||
// This file is used to generate an object file which
|
||||
// serves as test file for gcimporter_test.go.
|
||||
|
||||
package exports
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
)
|
||||
|
||||
// Issue 3682: Correctly read dotted identifiers from export data.
|
||||
const init1 = 0
|
||||
|
||||
func init() {}
|
||||
|
||||
const (
|
||||
C0 int = 0
|
||||
C1 = 3.14159265
|
||||
C2 = 2.718281828i
|
||||
C3 = -123.456e-789
|
||||
C4 = +123.456E+789
|
||||
C5 = 1234i
|
||||
C6 = "foo\n"
|
||||
C7 = `bar\n`
|
||||
)
|
||||
|
||||
type (
|
||||
T1 int
|
||||
T2 [10]int
|
||||
T3 []int
|
||||
T4 *int
|
||||
T5 chan int
|
||||
T6a chan<- int
|
||||
T6b chan (<-chan int)
|
||||
T6c chan<- (chan int)
|
||||
T7 <-chan *ast.File
|
||||
T8 struct{}
|
||||
T9 struct {
|
||||
a int
|
||||
b, c float32
|
||||
d []string `go:"tag"`
|
||||
}
|
||||
T10 struct {
|
||||
T8
|
||||
T9
|
||||
_ *T10
|
||||
}
|
||||
T11 map[int]string
|
||||
T12 interface{}
|
||||
T13 interface {
|
||||
m1()
|
||||
m2(int) float32
|
||||
}
|
||||
T14 interface {
|
||||
T12
|
||||
T13
|
||||
m3(x ...struct{}) []T9
|
||||
}
|
||||
T15 func()
|
||||
T16 func(int)
|
||||
T17 func(x int)
|
||||
T18 func() float32
|
||||
T19 func() (x float32)
|
||||
T20 func(...interface{})
|
||||
T21 struct{ next *T21 }
|
||||
T22 struct{ link *T23 }
|
||||
T23 struct{ link *T22 }
|
||||
T24 *T24
|
||||
T25 *T26
|
||||
T26 *T27
|
||||
T27 *T25
|
||||
T28 func(T28) T28
|
||||
)
|
||||
|
||||
var (
|
||||
V0 int
|
||||
V1 = -991.0
|
||||
)
|
||||
|
||||
func F1() {}
|
||||
func F2(x int) {}
|
||||
func F3() int { return 0 }
|
||||
func F4() float32 { return 0 }
|
||||
func F5(a, b, c int, u, v, w struct{ x, y T1 }, more ...interface{}) (p, q, r chan<- T10)
|
||||
|
||||
func (p *T1) M1()
|
||||
Loading…
Reference in New Issue