diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 13:15:26 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 13:15:26 +0000 |
commit | 82539ad8d59729fb45b0bb0edda8f2bddb719eb1 (patch) | |
tree | 58f0b58e6f44f0e04d4a6373132cf426fa835fa7 /src/go/internal | |
parent | Initial commit. (diff) | |
download | golang-1.17-upstream.tar.xz golang-1.17-upstream.zip |
Adding upstream version 1.17.13.upstream/1.17.13upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/go/internal')
67 files changed, 5207 insertions, 0 deletions
diff --git a/src/go/internal/gccgoimporter/ar.go b/src/go/internal/gccgoimporter/ar.go new file mode 100644 index 0000000..443aa26 --- /dev/null +++ b/src/go/internal/gccgoimporter/ar.go @@ -0,0 +1,171 @@ +// Copyright 2018 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 gccgoimporter + +import ( + "bytes" + "debug/elf" + "errors" + "fmt" + "internal/xcoff" + "io" + "strconv" + "strings" +) + +// Magic strings for different archive file formats. +const ( + armag = "!<arch>\n" + armagt = "!<thin>\n" + armagb = "<bigaf>\n" +) + +// Offsets and sizes for fields in a standard archive header. +const ( + arNameOff = 0 + arNameSize = 16 + arDateOff = arNameOff + arNameSize + arDateSize = 12 + arUIDOff = arDateOff + arDateSize + arUIDSize = 6 + arGIDOff = arUIDOff + arUIDSize + arGIDSize = 6 + arModeOff = arGIDOff + arGIDSize + arModeSize = 8 + arSizeOff = arModeOff + arModeSize + arSizeSize = 10 + arFmagOff = arSizeOff + arSizeSize + arFmagSize = 2 + + arHdrSize = arFmagOff + arFmagSize +) + +// The contents of the fmag field of a standard archive header. +const arfmag = "`\n" + +// arExportData takes an archive file and returns a ReadSeeker for the +// export data in that file. This assumes that there is only one +// object in the archive containing export data, which is not quite +// what gccgo does; gccgo concatenates together all the export data +// for all the objects in the file. In practice that case does not arise. +func arExportData(archive io.ReadSeeker) (io.ReadSeeker, error) { + if _, err := archive.Seek(0, io.SeekStart); err != nil { + return nil, err + } + + var buf [len(armag)]byte + if _, err := archive.Read(buf[:]); err != nil { + return nil, err + } + + switch string(buf[:]) { + case armag: + return standardArExportData(archive) + case armagt: + return nil, errors.New("unsupported thin archive") + case armagb: + return aixBigArExportData(archive) + default: + return nil, fmt.Errorf("unrecognized archive file format %q", buf[:]) + } +} + +// standardArExportData returns export data from a standard archive. +func standardArExportData(archive io.ReadSeeker) (io.ReadSeeker, error) { + off := int64(len(armag)) + for { + var hdrBuf [arHdrSize]byte + if _, err := archive.Read(hdrBuf[:]); err != nil { + return nil, err + } + off += arHdrSize + + if bytes.Compare(hdrBuf[arFmagOff:arFmagOff+arFmagSize], []byte(arfmag)) != 0 { + return nil, fmt.Errorf("archive header format header (%q)", hdrBuf[:]) + } + + size, err := strconv.ParseInt(strings.TrimSpace(string(hdrBuf[arSizeOff:arSizeOff+arSizeSize])), 10, 64) + if err != nil { + return nil, fmt.Errorf("error parsing size in archive header (%q): %v", hdrBuf[:], err) + } + + fn := hdrBuf[arNameOff : arNameOff+arNameSize] + if fn[0] == '/' && (fn[1] == ' ' || fn[1] == '/' || bytes.Compare(fn[:8], []byte("/SYM64/ ")) == 0) { + // Archive symbol table or extended name table, + // which we don't care about. + } else { + archiveAt := readerAtFromSeeker(archive) + ret, err := elfFromAr(io.NewSectionReader(archiveAt, off, size)) + if ret != nil || err != nil { + return ret, err + } + } + + if size&1 != 0 { + size++ + } + off += size + if _, err := archive.Seek(off, io.SeekStart); err != nil { + return nil, err + } + } +} + +// elfFromAr tries to get export data from an archive member as an ELF file. +// If there is no export data, this returns nil, nil. +func elfFromAr(member *io.SectionReader) (io.ReadSeeker, error) { + ef, err := elf.NewFile(member) + if err != nil { + return nil, err + } + sec := ef.Section(".go_export") + if sec == nil { + return nil, nil + } + return sec.Open(), nil +} + +// aixBigArExportData returns export data from an AIX big archive. +func aixBigArExportData(archive io.ReadSeeker) (io.ReadSeeker, error) { + archiveAt := readerAtFromSeeker(archive) + arch, err := xcoff.NewArchive(archiveAt) + if err != nil { + return nil, err + } + + for _, mem := range arch.Members { + f, err := arch.GetFile(mem.Name) + if err != nil { + return nil, err + } + sdat := f.CSect(".go_export") + if sdat != nil { + return bytes.NewReader(sdat), nil + } + } + + return nil, fmt.Errorf(".go_export not found in this archive") +} + +// readerAtFromSeeker turns an io.ReadSeeker into an io.ReaderAt. +// This is only safe because there won't be any concurrent seeks +// while this code is executing. +func readerAtFromSeeker(rs io.ReadSeeker) io.ReaderAt { + if ret, ok := rs.(io.ReaderAt); ok { + return ret + } + return seekerReadAt{rs} +} + +type seekerReadAt struct { + seeker io.ReadSeeker +} + +func (sra seekerReadAt) ReadAt(p []byte, off int64) (int, error) { + if _, err := sra.seeker.Seek(off, io.SeekStart); err != nil { + return 0, err + } + return sra.seeker.Read(p) +} diff --git a/src/go/internal/gccgoimporter/gccgoinstallation.go b/src/go/internal/gccgoimporter/gccgoinstallation.go new file mode 100644 index 0000000..e90a3cc --- /dev/null +++ b/src/go/internal/gccgoimporter/gccgoinstallation.go @@ -0,0 +1,97 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "bufio" + "go/types" + exec "internal/execabs" + "os" + "path/filepath" + "strings" +) + +// Information about a specific installation of gccgo. +type GccgoInstallation struct { + // Version of gcc (e.g. 4.8.0). + GccVersion string + + // Target triple (e.g. x86_64-unknown-linux-gnu). + TargetTriple string + + // Built-in library paths used by this installation. + LibPaths []string +} + +// Ask the driver at the given path for information for this GccgoInstallation. +// The given arguments are passed directly to the call of the driver. +func (inst *GccgoInstallation) InitFromDriver(gccgoPath string, args ...string) (err error) { + argv := append([]string{"-###", "-S", "-x", "go", "-"}, args...) + cmd := exec.Command(gccgoPath, argv...) + stderr, err := cmd.StderrPipe() + if err != nil { + return + } + + err = cmd.Start() + if err != nil { + return + } + + scanner := bufio.NewScanner(stderr) + for scanner.Scan() { + line := scanner.Text() + switch { + case strings.HasPrefix(line, "Target: "): + inst.TargetTriple = line[8:] + + case line[0] == ' ': + args := strings.Fields(line) + for _, arg := range args[1:] { + if strings.HasPrefix(arg, "-L") { + inst.LibPaths = append(inst.LibPaths, arg[2:]) + } + } + } + } + + argv = append([]string{"-dumpversion"}, args...) + stdout, err := exec.Command(gccgoPath, argv...).Output() + if err != nil { + return + } + inst.GccVersion = strings.TrimSpace(string(stdout)) + + return +} + +// Return the list of export search paths for this GccgoInstallation. +func (inst *GccgoInstallation) SearchPaths() (paths []string) { + for _, lpath := range inst.LibPaths { + spath := filepath.Join(lpath, "go", inst.GccVersion) + fi, err := os.Stat(spath) + if err != nil || !fi.IsDir() { + continue + } + paths = append(paths, spath) + + spath = filepath.Join(spath, inst.TargetTriple) + fi, err = os.Stat(spath) + if err != nil || !fi.IsDir() { + continue + } + paths = append(paths, spath) + } + + paths = append(paths, inst.LibPaths...) + + return +} + +// Return an importer that searches incpaths followed by the gcc installation's +// built-in search paths and the current directory. +func (inst *GccgoInstallation) GetImporter(incpaths []string, initmap map[*types.Package]InitData) Importer { + return GetImporter(append(append(incpaths, inst.SearchPaths()...), "."), initmap) +} diff --git a/src/go/internal/gccgoimporter/gccgoinstallation_test.go b/src/go/internal/gccgoimporter/gccgoinstallation_test.go new file mode 100644 index 0000000..df0188a --- /dev/null +++ b/src/go/internal/gccgoimporter/gccgoinstallation_test.go @@ -0,0 +1,192 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "go/types" + "testing" +) + +// importablePackages is a list of packages that we verify that we can +// import. This should be all standard library packages in all relevant +// versions of gccgo. Note that since gccgo follows a different release +// cycle, and since different systems have different versions installed, +// we can't use the last-two-versions rule of the gc toolchain. +var importablePackages = [...]string{ + "archive/tar", + "archive/zip", + "bufio", + "bytes", + "compress/bzip2", + "compress/flate", + "compress/gzip", + "compress/lzw", + "compress/zlib", + "container/heap", + "container/list", + "container/ring", + "crypto/aes", + "crypto/cipher", + "crypto/des", + "crypto/dsa", + "crypto/ecdsa", + "crypto/elliptic", + "crypto", + "crypto/hmac", + "crypto/md5", + "crypto/rand", + "crypto/rc4", + "crypto/rsa", + "crypto/sha1", + "crypto/sha256", + "crypto/sha512", + "crypto/subtle", + "crypto/tls", + "crypto/x509", + "crypto/x509/pkix", + "database/sql/driver", + "database/sql", + "debug/dwarf", + "debug/elf", + "debug/gosym", + "debug/macho", + "debug/pe", + "encoding/ascii85", + "encoding/asn1", + "encoding/base32", + "encoding/base64", + "encoding/binary", + "encoding/csv", + "encoding/gob", + // "encoding", // Added in GCC 4.9. + "encoding/hex", + "encoding/json", + "encoding/pem", + "encoding/xml", + "errors", + "expvar", + "flag", + "fmt", + "go/ast", + "go/build", + "go/doc", + // "go/format", // Added in GCC 4.8. + "go/parser", + "go/printer", + "go/scanner", + "go/token", + "hash/adler32", + "hash/crc32", + "hash/crc64", + "hash/fnv", + "hash", + "html", + "html/template", + "image/color", + // "image/color/palette", // Added in GCC 4.9. + "image/draw", + "image/gif", + "image", + "image/jpeg", + "image/png", + "index/suffixarray", + "io", + "io/ioutil", + "log", + "log/syslog", + "math/big", + "math/cmplx", + "math", + "math/rand", + "mime", + "mime/multipart", + "net", + "net/http/cgi", + // "net/http/cookiejar", // Added in GCC 4.8. + "net/http/fcgi", + "net/http", + "net/http/httptest", + "net/http/httputil", + "net/http/pprof", + "net/mail", + "net/rpc", + "net/rpc/jsonrpc", + "net/smtp", + "net/textproto", + "net/url", + "os/exec", + "os", + "os/signal", + "os/user", + "path/filepath", + "path", + "reflect", + "regexp", + "regexp/syntax", + "runtime/debug", + "runtime", + "runtime/pprof", + "sort", + "strconv", + "strings", + "sync/atomic", + "sync", + "syscall", + "testing", + "testing/iotest", + "testing/quick", + "text/scanner", + "text/tabwriter", + "text/template", + "text/template/parse", + "time", + "unicode", + "unicode/utf16", + "unicode/utf8", +} + +func TestInstallationImporter(t *testing.T) { + // This test relies on gccgo being around. + gpath := gccgoPath() + if gpath == "" { + t.Skip("This test needs gccgo") + } + + var inst GccgoInstallation + err := inst.InitFromDriver(gpath) + if err != nil { + t.Fatal(err) + } + imp := inst.GetImporter(nil, nil) + + // Ensure we don't regress the number of packages we can parse. First import + // all packages into the same map and then each individually. + pkgMap := make(map[string]*types.Package) + for _, pkg := range importablePackages { + _, err = imp(pkgMap, pkg, ".", nil) + if err != nil { + t.Error(err) + } + } + + for _, pkg := range importablePackages { + _, err = imp(make(map[string]*types.Package), pkg, ".", nil) + if err != nil { + t.Error(err) + } + } + + // Test for certain specific entities in the imported data. + for _, test := range [...]importerTest{ + {pkgpath: "io", name: "Reader", want: "type Reader interface{Read(p []byte) (n int, err error)}"}, + {pkgpath: "io", name: "ReadWriter", want: "type ReadWriter interface{Reader; Writer}"}, + {pkgpath: "math", name: "Pi", want: "const Pi untyped float"}, + {pkgpath: "math", name: "Sin", want: "func Sin(x float64) float64"}, + {pkgpath: "sort", name: "Search", want: "func Search(n int, f func(int) bool) int"}, + {pkgpath: "unsafe", name: "Pointer", want: "type Pointer"}, + } { + runImporterTest(t, imp, nil, &test) + } +} diff --git a/src/go/internal/gccgoimporter/importer.go b/src/go/internal/gccgoimporter/importer.go new file mode 100644 index 0000000..94f2def --- /dev/null +++ b/src/go/internal/gccgoimporter/importer.go @@ -0,0 +1,261 @@ +// Copyright 2013 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 gccgoimporter implements Import for gccgo-generated object files. +package gccgoimporter // import "go/internal/gccgoimporter" + +import ( + "bytes" + "debug/elf" + "fmt" + "go/types" + "internal/xcoff" + "io" + "os" + "path/filepath" + "strings" +) + +// A PackageInit describes an imported package that needs initialization. +type PackageInit struct { + Name string // short package name + InitFunc string // name of init function + Priority int // priority of init function, see InitData.Priority +} + +// The gccgo-specific init data for a package. +type InitData struct { + // Initialization priority of this package relative to other packages. + // This is based on the maximum depth of the package's dependency graph; + // it is guaranteed to be greater than that of its dependencies. + Priority int + + // The list of packages which this package depends on to be initialized, + // including itself if needed. This is the subset of the transitive closure of + // the package's dependencies that need initialization. + Inits []PackageInit +} + +// Locate the file from which to read export data. +// This is intended to replicate the logic in gofrontend. +func findExportFile(searchpaths []string, pkgpath string) (string, error) { + for _, spath := range searchpaths { + pkgfullpath := filepath.Join(spath, pkgpath) + pkgdir, name := filepath.Split(pkgfullpath) + + for _, filepath := range [...]string{ + pkgfullpath, + pkgfullpath + ".gox", + pkgdir + "lib" + name + ".so", + pkgdir + "lib" + name + ".a", + pkgfullpath + ".o", + } { + fi, err := os.Stat(filepath) + if err == nil && !fi.IsDir() { + return filepath, nil + } + } + } + + return "", fmt.Errorf("%s: could not find export data (tried %s)", pkgpath, strings.Join(searchpaths, ":")) +} + +const ( + gccgov1Magic = "v1;\n" + gccgov2Magic = "v2;\n" + gccgov3Magic = "v3;\n" + goimporterMagic = "\n$$ " + archiveMagic = "!<ar" + aixbigafMagic = "<big" +) + +// Opens the export data file at the given path. If this is an ELF file, +// searches for and opens the .go_export section. If this is an archive, +// reads the export data from the first member, which is assumed to be an ELF file. +// This is intended to replicate the logic in gofrontend. +func openExportFile(fpath string) (reader io.ReadSeeker, closer io.Closer, err error) { + f, err := os.Open(fpath) + if err != nil { + return + } + closer = f + defer func() { + if err != nil && closer != nil { + f.Close() + } + }() + + var magic [4]byte + _, err = f.ReadAt(magic[:], 0) + if err != nil { + return + } + + var objreader io.ReaderAt + switch string(magic[:]) { + case gccgov1Magic, gccgov2Magic, gccgov3Magic, goimporterMagic: + // Raw export data. + reader = f + return + + case archiveMagic, aixbigafMagic: + reader, err = arExportData(f) + return + + default: + objreader = f + } + + ef, err := elf.NewFile(objreader) + if err == nil { + sec := ef.Section(".go_export") + if sec == nil { + err = fmt.Errorf("%s: .go_export section not found", fpath) + return + } + reader = sec.Open() + return + } + + xf, err := xcoff.NewFile(objreader) + if err == nil { + sdat := xf.CSect(".go_export") + if sdat == nil { + err = fmt.Errorf("%s: .go_export section not found", fpath) + return + } + reader = bytes.NewReader(sdat) + return + } + + err = fmt.Errorf("%s: unrecognized file format", fpath) + return +} + +// An Importer resolves import paths to Packages. The imports map records +// packages already known, indexed by package path. +// An importer must determine the canonical package path and check imports +// to see if it is already present in the map. If so, the Importer can return +// the map entry. Otherwise, the importer must load the package data for the +// given path into a new *Package, record it in imports map, and return the +// package. +type Importer func(imports map[string]*types.Package, path, srcDir string, lookup func(string) (io.ReadCloser, error)) (*types.Package, error) + +func GetImporter(searchpaths []string, initmap map[*types.Package]InitData) Importer { + return func(imports map[string]*types.Package, pkgpath, srcDir string, lookup func(string) (io.ReadCloser, error)) (pkg *types.Package, err error) { + // TODO(gri): Use srcDir. + // Or not. It's possible that srcDir will fade in importance as + // the go command and other tools provide a translation table + // for relative imports (like ./foo or vendored imports). + if pkgpath == "unsafe" { + return types.Unsafe, nil + } + + var reader io.ReadSeeker + var fpath string + var rc io.ReadCloser + if lookup != nil { + if p := imports[pkgpath]; p != nil && p.Complete() { + return p, nil + } + rc, err = lookup(pkgpath) + if err != nil { + return nil, err + } + } + if rc != nil { + defer rc.Close() + rs, ok := rc.(io.ReadSeeker) + if !ok { + return nil, fmt.Errorf("gccgo importer requires lookup to return an io.ReadSeeker, have %T", rc) + } + reader = rs + fpath = "<lookup " + pkgpath + ">" + // Take name from Name method (like on os.File) if present. + if n, ok := rc.(interface{ Name() string }); ok { + fpath = n.Name() + } + } else { + fpath, err = findExportFile(searchpaths, pkgpath) + if err != nil { + return nil, err + } + + r, closer, err := openExportFile(fpath) + if err != nil { + return nil, err + } + if closer != nil { + defer closer.Close() + } + reader = r + } + + var magics string + magics, err = readMagic(reader) + if err != nil { + return + } + + if magics == archiveMagic || magics == aixbigafMagic { + reader, err = arExportData(reader) + if err != nil { + return + } + magics, err = readMagic(reader) + if err != nil { + return + } + } + + switch magics { + case gccgov1Magic, gccgov2Magic, gccgov3Magic: + var p parser + p.init(fpath, reader, imports) + pkg = p.parsePackage() + if initmap != nil { + initmap[pkg] = p.initdata + } + + // Excluded for now: Standard gccgo doesn't support this import format currently. + // case goimporterMagic: + // var data []byte + // data, err = io.ReadAll(reader) + // if err != nil { + // return + // } + // var n int + // n, pkg, err = importer.ImportData(imports, data) + // if err != nil { + // return + // } + + // if initmap != nil { + // suffixreader := bytes.NewReader(data[n:]) + // var p parser + // p.init(fpath, suffixreader, nil) + // p.parseInitData() + // initmap[pkg] = p.initdata + // } + + default: + err = fmt.Errorf("unrecognized magic string: %q", magics) + } + + return + } +} + +// readMagic reads the four bytes at the start of a ReadSeeker and +// returns them as a string. +func readMagic(reader io.ReadSeeker) (string, error) { + var magic [4]byte + if _, err := reader.Read(magic[:]); err != nil { + return "", err + } + if _, err := reader.Seek(0, io.SeekStart); err != nil { + return "", err + } + return string(magic[:]), nil +} diff --git a/src/go/internal/gccgoimporter/importer_test.go b/src/go/internal/gccgoimporter/importer_test.go new file mode 100644 index 0000000..8a7ee1a --- /dev/null +++ b/src/go/internal/gccgoimporter/importer_test.go @@ -0,0 +1,199 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "go/types" + "internal/testenv" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "testing" +) + +type importerTest struct { + pkgpath, name, want, wantval string + wantinits []string + gccgoVersion int // minimum gccgo version (0 => any) +} + +func runImporterTest(t *testing.T, imp Importer, initmap map[*types.Package]InitData, test *importerTest) { + pkg, err := imp(make(map[string]*types.Package), test.pkgpath, ".", nil) + if err != nil { + t.Error(err) + return + } + + if test.name != "" { + obj := pkg.Scope().Lookup(test.name) + if obj == nil { + t.Errorf("%s: object not found", test.name) + return + } + + got := types.ObjectString(obj, types.RelativeTo(pkg)) + if got != test.want { + t.Errorf("%s: got %q; want %q", test.name, got, test.want) + } + + if test.wantval != "" { + gotval := obj.(*types.Const).Val().String() + if gotval != test.wantval { + t.Errorf("%s: got val %q; want val %q", test.name, gotval, test.wantval) + } + } + } + + if len(test.wantinits) > 0 { + initdata := initmap[pkg] + found := false + // Check that the package's own init function has the package's priority + for _, pkginit := range initdata.Inits { + if pkginit.InitFunc == test.wantinits[0] { + found = true + break + } + } + + if !found { + t.Errorf("%s: could not find expected function %q", test.pkgpath, test.wantinits[0]) + } + + // FIXME: the original version of this test was written against + // the v1 export data scheme for capturing init functions, so it + // verified the priority values. We moved away from the priority + // scheme some time ago; it is not clear how much work it would be + // to validate the new init export data. + } +} + +// When adding tests to this list, be sure to set the 'gccgoVersion' +// field if the testcases uses a "recent" Go addition (ex: aliases). +var importerTests = [...]importerTest{ + {pkgpath: "pointer", name: "Int8Ptr", want: "type Int8Ptr *int8"}, + {pkgpath: "complexnums", name: "NN", want: "const NN untyped complex", wantval: "(-1 + -1i)"}, + {pkgpath: "complexnums", name: "NP", want: "const NP untyped complex", wantval: "(-1 + 1i)"}, + {pkgpath: "complexnums", name: "PN", want: "const PN untyped complex", wantval: "(1 + -1i)"}, + {pkgpath: "complexnums", name: "PP", want: "const PP untyped complex", wantval: "(1 + 1i)"}, + {pkgpath: "conversions", name: "Bits", want: "const Bits Units", wantval: `"bits"`}, + {pkgpath: "time", name: "Duration", want: "type Duration int64"}, + {pkgpath: "time", name: "Nanosecond", want: "const Nanosecond Duration", wantval: "1"}, + {pkgpath: "unicode", name: "IsUpper", want: "func IsUpper(r rune) bool"}, + {pkgpath: "unicode", name: "MaxRune", want: "const MaxRune untyped rune", wantval: "1114111"}, + {pkgpath: "imports", wantinits: []string{"imports..import", "fmt..import"}}, + {pkgpath: "importsar", name: "Hello", want: "var Hello string"}, + {pkgpath: "aliases", name: "A14", gccgoVersion: 7, want: "type A14 = func(int, T0) chan T2"}, + {pkgpath: "aliases", name: "C0", gccgoVersion: 7, want: "type C0 struct{f1 C1; f2 C1}"}, + {pkgpath: "escapeinfo", name: "NewT", want: "func NewT(data []byte) *T"}, + {pkgpath: "issue27856", name: "M", gccgoVersion: 7, want: "type M struct{E F}"}, + {pkgpath: "v1reflect", name: "Type", want: "type Type interface{Align() int; AssignableTo(u Type) bool; Bits() int; ChanDir() ChanDir; Elem() Type; Field(i int) StructField; FieldAlign() int; FieldByIndex(index []int) StructField; FieldByName(name string) (StructField, bool); FieldByNameFunc(match func(string) bool) (StructField, bool); Implements(u Type) bool; In(i int) Type; IsVariadic() bool; Key() Type; Kind() Kind; Len() int; Method(int) Method; MethodByName(string) (Method, bool); Name() string; NumField() int; NumIn() int; NumMethod() int; NumOut() int; Out(i int) Type; PkgPath() string; Size() uintptr; String() string; common() *commonType; rawString() string; runtimeType() *runtimeType; uncommon() *uncommonType}"}, + {pkgpath: "nointerface", name: "I", want: "type I int"}, + {pkgpath: "issue29198", name: "FooServer", gccgoVersion: 7, want: "type FooServer struct{FooServer *FooServer; user string; ctx context.Context}"}, + {pkgpath: "issue30628", name: "Apple", want: "type Apple struct{hey sync.RWMutex; x int; RQ [517]struct{Count uintptr; NumBytes uintptr; Last uintptr}}"}, + {pkgpath: "issue31540", name: "S", gccgoVersion: 7, want: "type S struct{b int; map[Y]Z}"}, // should want "type S struct{b int; A2}" (issue #44410) + {pkgpath: "issue34182", name: "T1", want: "type T1 struct{f *T2}"}, + {pkgpath: "notinheap", name: "S", want: "type S struct{}"}, +} + +func TestGoxImporter(t *testing.T) { + testenv.MustHaveExec(t) + initmap := make(map[*types.Package]InitData) + imp := GetImporter([]string{"testdata"}, initmap) + + for _, test := range importerTests { + runImporterTest(t, imp, initmap, &test) + } +} + +// gccgoPath returns a path to gccgo if it is present (either in +// path or specified via GCCGO environment variable), or an +// empty string if no gccgo is available. +func gccgoPath() string { + gccgoname := os.Getenv("GCCGO") + if gccgoname == "" { + gccgoname = "gccgo" + } + if gpath, gerr := exec.LookPath(gccgoname); gerr == nil { + return gpath + } + return "" +} + +func TestObjImporter(t *testing.T) { + // This test relies on gccgo being around. + gpath := gccgoPath() + if gpath == "" { + t.Skip("This test needs gccgo") + } + + verout, err := exec.Command(gpath, "--version").CombinedOutput() + if err != nil { + t.Logf("%s", verout) + t.Fatal(err) + } + vers := regexp.MustCompile(`([0-9]+)\.([0-9]+)`).FindSubmatch(verout) + if len(vers) == 0 { + t.Fatalf("could not find version number in %s", verout) + } + major, err := strconv.Atoi(string(vers[1])) + if err != nil { + t.Fatal(err) + } + minor, err := strconv.Atoi(string(vers[2])) + if err != nil { + t.Fatal(err) + } + t.Logf("gccgo version %d.%d", major, minor) + + tmpdir := t.TempDir() + initmap := make(map[*types.Package]InitData) + imp := GetImporter([]string{tmpdir}, initmap) + + artmpdir := t.TempDir() + arinitmap := make(map[*types.Package]InitData) + arimp := GetImporter([]string{artmpdir}, arinitmap) + + for _, test := range importerTests { + if major < test.gccgoVersion { + // Support for type aliases was added in GCC 7. + t.Logf("skipping %q: not supported before gccgo version %d", test.pkgpath, test.gccgoVersion) + continue + } + + gofile := filepath.Join("testdata", test.pkgpath+".go") + if _, err := os.Stat(gofile); os.IsNotExist(err) { + continue + } + ofile := filepath.Join(tmpdir, test.pkgpath+".o") + afile := filepath.Join(artmpdir, "lib"+test.pkgpath+".a") + + cmd := exec.Command(gpath, "-fgo-pkgpath="+test.pkgpath, "-c", "-o", ofile, gofile) + out, err := cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("gccgo %s failed: %s", gofile, err) + } + + runImporterTest(t, imp, initmap, &test) + + cmd = exec.Command("ar", "cr", afile, ofile) + out, err = cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("ar cr %s %s failed: %s", afile, ofile, err) + } + + runImporterTest(t, arimp, arinitmap, &test) + + if err = os.Remove(ofile); err != nil { + t.Fatal(err) + } + if err = os.Remove(afile); err != nil { + t.Fatal(err) + } + } +} diff --git a/src/go/internal/gccgoimporter/parser.go b/src/go/internal/gccgoimporter/parser.go new file mode 100644 index 0000000..1b1d07d --- /dev/null +++ b/src/go/internal/gccgoimporter/parser.go @@ -0,0 +1,1284 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "bytes" + "errors" + "fmt" + "go/constant" + "go/token" + "go/types" + "io" + "strconv" + "strings" + "text/scanner" + "unicode/utf8" +) + +type parser struct { + scanner *scanner.Scanner + version string // format version + tok rune // current token + lit string // literal string; only valid for Ident, Int, String tokens + pkgpath string // package path of imported package + pkgname string // name of imported package + pkg *types.Package // reference to imported package + imports map[string]*types.Package // package path -> package object + typeList []types.Type // type number -> type + typeData []string // unparsed type data (v3 and later) + fixups []fixupRecord // fixups to apply at end of parsing + initdata InitData // package init priority data + aliases map[int]string // maps saved type number to alias name +} + +// When reading export data it's possible to encounter a defined type +// N1 with an underlying defined type N2 while we are still reading in +// that defined type N2; see issues #29006 and #29198 for instances +// of this. Example: +// +// type N1 N2 +// type N2 struct { +// ... +// p *N1 +// } +// +// To handle such cases, the parser generates a fixup record (below) and +// delays setting of N1's underlying type until parsing is complete, at +// which point fixups are applied. + +type fixupRecord struct { + toUpdate *types.Named // type to modify when fixup is processed + target types.Type // type that was incomplete when fixup was created +} + +func (p *parser) init(filename string, src io.Reader, imports map[string]*types.Package) { + p.scanner = new(scanner.Scanner) + p.initScanner(filename, src) + p.imports = imports + p.aliases = make(map[int]string) + p.typeList = make([]types.Type, 1 /* type numbers start at 1 */, 16) +} + +func (p *parser) initScanner(filename string, src io.Reader) { + p.scanner.Init(src) + p.scanner.Error = func(_ *scanner.Scanner, msg string) { p.error(msg) } + p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings + p.scanner.Whitespace = 1<<'\t' | 1<<' ' + p.scanner.Filename = filename // for good error messages + p.next() +} + +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 *parser) 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 *parser) errorf(format string, args ...interface{}) { + p.error(fmt.Errorf(format, args...)) +} + +func (p *parser) 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 *parser) expectEOL() { + if p.version == "v1" || p.version == "v2" { + p.expect(';') + } + p.expect('\n') +} + +func (p *parser) expectKeyword(keyword string) { + lit := p.expect(scanner.Ident) + if lit != keyword { + p.errorf("expected keyword %s, got %q", keyword, lit) + } +} + +func (p *parser) parseString() string { + str, err := strconv.Unquote(p.expect(scanner.String)) + if err != nil { + p.error(err) + } + return str +} + +// unquotedString = { unquotedStringChar } . +// unquotedStringChar = <neither a whitespace nor a ';' char> . +func (p *parser) parseUnquotedString() string { + if p.tok == scanner.EOF { + p.error("unexpected EOF") + } + var buf bytes.Buffer + buf.WriteString(p.scanner.TokenText()) + // This loop needs to examine each character before deciding whether to consume it. If we see a semicolon, + // we need to let it be consumed by p.next(). + for ch := p.scanner.Peek(); ch != '\n' && ch != ';' && ch != scanner.EOF && p.scanner.Whitespace&(1<<uint(ch)) == 0; ch = p.scanner.Peek() { + buf.WriteRune(ch) + p.scanner.Next() + } + p.next() + return buf.String() +} + +func (p *parser) next() { + p.tok = p.scanner.Scan() + switch p.tok { + case scanner.Ident, scanner.Int, scanner.Float, scanner.String, '·': + p.lit = p.scanner.TokenText() + default: + p.lit = "" + } +} + +func (p *parser) parseQualifiedName() (path, name string) { + return p.parseQualifiedNameStr(p.parseString()) +} + +func (p *parser) parseUnquotedQualifiedName() (path, name string) { + return p.parseQualifiedNameStr(p.parseUnquotedString()) +} + +// qualifiedName = [ ["."] unquotedString "." ] unquotedString . +// +// The above production uses greedy matching. +func (p *parser) parseQualifiedNameStr(unquotedName string) (pkgpath, name string) { + parts := strings.Split(unquotedName, ".") + if parts[0] == "" { + parts = parts[1:] + } + + switch len(parts) { + case 0: + p.errorf("malformed qualified name: %q", unquotedName) + case 1: + // unqualified name + pkgpath = p.pkgpath + name = parts[0] + default: + // qualified name, which may contain periods + pkgpath = strings.Join(parts[0:len(parts)-1], ".") + name = parts[len(parts)-1] + } + + return +} + +// getPkg returns the package for a given path. If the package is +// not found but we have a package name, create the package and +// add it to the p.imports map. +// +func (p *parser) getPkg(pkgpath, name string) *types.Package { + // package unsafe is not in the imports map - handle explicitly + if pkgpath == "unsafe" { + return types.Unsafe + } + pkg := p.imports[pkgpath] + if pkg == nil && name != "" { + pkg = types.NewPackage(pkgpath, name) + p.imports[pkgpath] = pkg + } + return pkg +} + +// parseExportedName is like parseQualifiedName, but +// the package path is resolved to an imported *types.Package. +// +// ExportedName = string [string] . +func (p *parser) parseExportedName() (pkg *types.Package, name string) { + path, name := p.parseQualifiedName() + var pkgname string + if p.tok == scanner.String { + pkgname = p.parseString() + } + pkg = p.getPkg(path, pkgname) + if pkg == nil { + p.errorf("package %s (path = %q) not found", name, path) + } + return +} + +// Name = QualifiedName | "?" . +func (p *parser) parseName() string { + if p.tok == '?' { + // Anonymous. + p.next() + return "" + } + // The package path is redundant for us. Don't try to parse it. + _, name := p.parseUnquotedQualifiedName() + return name +} + +func deref(typ types.Type) types.Type { + if p, _ := typ.(*types.Pointer); p != nil { + typ = p.Elem() + } + return typ +} + +// Field = Name Type [string] . +func (p *parser) parseField(pkg *types.Package) (field *types.Var, tag string) { + name := p.parseName() + typ, n := p.parseTypeExtended(pkg) + anon := false + if name == "" { + anon = true + // Alias? + if aname, ok := p.aliases[n]; ok { + name = aname + } else { + switch typ := deref(typ).(type) { + case *types.Basic: + name = typ.Name() + case *types.Named: + name = typ.Obj().Name() + default: + p.error("embedded field expected") + } + } + } + field = types.NewField(token.NoPos, pkg, name, typ, anon) + if p.tok == scanner.String { + tag = p.parseString() + } + return +} + +// Param = Name ["..."] Type . +func (p *parser) parseParam(pkg *types.Package) (param *types.Var, isVariadic bool) { + name := p.parseName() + // Ignore names invented for inlinable functions. + if strings.HasPrefix(name, "p.") || strings.HasPrefix(name, "r.") || strings.HasPrefix(name, "$ret") { + name = "" + } + if p.tok == '<' && p.scanner.Peek() == 'e' { + // EscInfo = "<esc:" int ">" . (optional and ignored) + p.next() + p.expectKeyword("esc") + p.expect(':') + p.expect(scanner.Int) + p.expect('>') + } + if p.tok == '.' { + p.next() + p.expect('.') + p.expect('.') + isVariadic = true + } + typ := p.parseType(pkg) + if isVariadic { + typ = types.NewSlice(typ) + } + param = types.NewParam(token.NoPos, pkg, name, typ) + return +} + +// Var = Name Type . +func (p *parser) parseVar(pkg *types.Package) *types.Var { + name := p.parseName() + v := types.NewVar(token.NoPos, pkg, name, p.parseType(pkg)) + if name[0] == '.' || name[0] == '<' { + // This is an unexported variable, + // or a variable defined in a different package. + // We only want to record exported variables. + return nil + } + return v +} + +// Conversion = "convert" "(" Type "," ConstValue ")" . +func (p *parser) parseConversion(pkg *types.Package) (val constant.Value, typ types.Type) { + p.expectKeyword("convert") + p.expect('(') + typ = p.parseType(pkg) + p.expect(',') + val, _ = p.parseConstValue(pkg) + p.expect(')') + return +} + +// ConstValue = string | "false" | "true" | ["-"] (int ["'"] | FloatOrComplex) | Conversion . +// FloatOrComplex = float ["i" | ("+"|"-") float "i"] . +func (p *parser) parseConstValue(pkg *types.Package) (val constant.Value, typ types.Type) { + // v3 changed to $false, $true, $convert, to avoid confusion + // with variable names in inline function bodies. + if p.tok == '$' { + p.next() + if p.tok != scanner.Ident { + p.errorf("expected identifier after '$', got %s (%q)", scanner.TokenString(p.tok), p.lit) + } + } + + switch p.tok { + case scanner.String: + str := p.parseString() + val = constant.MakeString(str) + typ = types.Typ[types.UntypedString] + return + + case scanner.Ident: + b := false + switch p.lit { + case "false": + case "true": + b = true + + case "convert": + return p.parseConversion(pkg) + + default: + p.errorf("expected const value, got %s (%q)", scanner.TokenString(p.tok), p.lit) + } + + p.next() + val = constant.MakeBool(b) + typ = types.Typ[types.UntypedBool] + return + } + + sign := "" + if p.tok == '-' { + p.next() + sign = "-" + } + + switch p.tok { + case scanner.Int: + val = constant.MakeFromLiteral(sign+p.lit, token.INT, 0) + if val == nil { + p.error("could not parse integer literal") + } + + p.next() + if p.tok == '\'' { + p.next() + typ = types.Typ[types.UntypedRune] + } else { + typ = types.Typ[types.UntypedInt] + } + + case scanner.Float: + re := sign + p.lit + p.next() + + var im string + switch p.tok { + case '+': + p.next() + im = p.expect(scanner.Float) + + case '-': + p.next() + im = "-" + p.expect(scanner.Float) + + case scanner.Ident: + // re is in fact the imaginary component. Expect "i" below. + im = re + re = "0" + + default: + val = constant.MakeFromLiteral(re, token.FLOAT, 0) + if val == nil { + p.error("could not parse float literal") + } + typ = types.Typ[types.UntypedFloat] + return + } + + p.expectKeyword("i") + reval := constant.MakeFromLiteral(re, token.FLOAT, 0) + if reval == nil { + p.error("could not parse real component of complex literal") + } + imval := constant.MakeFromLiteral(im+"i", token.IMAG, 0) + if imval == nil { + p.error("could not parse imag component of complex literal") + } + val = constant.BinaryOp(reval, token.ADD, imval) + typ = types.Typ[types.UntypedComplex] + + default: + p.errorf("expected const value, got %s (%q)", scanner.TokenString(p.tok), p.lit) + } + + return +} + +// Const = Name [Type] "=" ConstValue . +func (p *parser) parseConst(pkg *types.Package) *types.Const { + name := p.parseName() + var typ types.Type + if p.tok == '<' { + typ = p.parseType(pkg) + } + p.expect('=') + val, vtyp := p.parseConstValue(pkg) + if typ == nil { + typ = vtyp + } + return types.NewConst(token.NoPos, pkg, name, typ, val) +} + +// reserved is a singleton type used to fill type map slots that have +// been reserved (i.e., for which a type number has been parsed) but +// which don't have their actual type yet. When the type map is updated, +// the actual type must replace a reserved entry (or we have an internal +// error). Used for self-verification only - not required for correctness. +var reserved = new(struct{ types.Type }) + +// reserve reserves the type map entry n for future use. +func (p *parser) reserve(n int) { + // Notes: + // - for pre-V3 export data, the type numbers we see are + // guaranteed to be in increasing order, so we append a + // reserved entry onto the list. + // - for V3+ export data, type numbers can appear in + // any order, however the 'types' section tells us the + // total number of types, hence typeList is pre-allocated. + if len(p.typeData) == 0 { + if n != len(p.typeList) { + p.errorf("invalid type number %d (out of sync)", n) + } + p.typeList = append(p.typeList, reserved) + } else { + if p.typeList[n] != nil { + p.errorf("previously visited type number %d", n) + } + p.typeList[n] = reserved + } +} + +// update sets the type map entries for the entries in nlist to t. +// An entry in nlist can be a type number in p.typeList, +// used to resolve named types, or it can be a *types.Pointer, +// used to resolve pointers to named types in case they are referenced +// by embedded fields. +func (p *parser) update(t types.Type, nlist []interface{}) { + if t == reserved { + p.errorf("internal error: update(%v) invoked on reserved", nlist) + } + if t == nil { + p.errorf("internal error: update(%v) invoked on nil", nlist) + } + for _, n := range nlist { + switch n := n.(type) { + case int: + if p.typeList[n] == t { + continue + } + if p.typeList[n] != reserved { + p.errorf("internal error: update(%v): %d not reserved", nlist, n) + } + p.typeList[n] = t + case *types.Pointer: + if *n != (types.Pointer{}) { + elem := n.Elem() + if elem == t { + continue + } + p.errorf("internal error: update: pointer already set to %v, expected %v", elem, t) + } + *n = *types.NewPointer(t) + default: + p.errorf("internal error: %T on nlist", n) + } + } +} + +// NamedType = TypeName [ "=" ] Type { Method } . +// TypeName = ExportedName . +// Method = "func" "(" Param ")" Name ParamList ResultList [InlineBody] ";" . +func (p *parser) parseNamedType(nlist []interface{}) types.Type { + pkg, name := p.parseExportedName() + scope := pkg.Scope() + obj := scope.Lookup(name) + if obj != nil && obj.Type() == nil { + p.errorf("%v has nil type", obj) + } + + if p.tok == scanner.Ident && p.lit == "notinheap" { + p.next() + // The go/types package has no way of recording that + // this type is marked notinheap. Presumably no user + // of this package actually cares. + } + + // type alias + if p.tok == '=' { + p.next() + p.aliases[nlist[len(nlist)-1].(int)] = name + if obj != nil { + // use the previously imported (canonical) type + t := obj.Type() + p.update(t, nlist) + p.parseType(pkg) // discard + return t + } + t := p.parseType(pkg, nlist...) + obj = types.NewTypeName(token.NoPos, pkg, name, t) + scope.Insert(obj) + return t + } + + // defined type + if obj == nil { + // A named type may be referred to before the underlying type + // is known - set it up. + tname := types.NewTypeName(token.NoPos, pkg, name, nil) + types.NewNamed(tname, nil, nil) + scope.Insert(tname) + obj = tname + } + + // use the previously imported (canonical), or newly created type + t := obj.Type() + p.update(t, nlist) + + nt, ok := t.(*types.Named) + if !ok { + // This can happen for unsafe.Pointer, which is a TypeName holding a Basic type. + pt := p.parseType(pkg) + if pt != t { + p.error("unexpected underlying type for non-named TypeName") + } + return t + } + + underlying := p.parseType(pkg) + if nt.Underlying() == nil { + if underlying.Underlying() == nil { + fix := fixupRecord{toUpdate: nt, target: underlying} + p.fixups = append(p.fixups, fix) + } else { + nt.SetUnderlying(underlying.Underlying()) + } + } + + if p.tok == '\n' { + p.next() + // collect associated methods + for p.tok == scanner.Ident { + p.expectKeyword("func") + if p.tok == '/' { + // Skip a /*nointerface*/ or /*asm ID */ comment. + p.expect('/') + p.expect('*') + if p.expect(scanner.Ident) == "asm" { + p.parseUnquotedString() + } + p.expect('*') + p.expect('/') + } + p.expect('(') + receiver, _ := p.parseParam(pkg) + p.expect(')') + name := p.parseName() + params, isVariadic := p.parseParamList(pkg) + results := p.parseResultList(pkg) + p.skipInlineBody() + p.expectEOL() + + sig := types.NewSignature(receiver, params, results, isVariadic) + nt.AddMethod(types.NewFunc(token.NoPos, pkg, name, sig)) + } + } + + return nt +} + +func (p *parser) parseInt64() int64 { + lit := p.expect(scanner.Int) + n, err := strconv.ParseInt(lit, 10, 64) + if err != nil { + p.error(err) + } + return n +} + +func (p *parser) parseInt() int { + lit := p.expect(scanner.Int) + n, err := strconv.ParseInt(lit, 10, 0 /* int */) + if err != nil { + p.error(err) + } + return int(n) +} + +// ArrayOrSliceType = "[" [ int ] "]" Type . +func (p *parser) parseArrayOrSliceType(pkg *types.Package, nlist []interface{}) types.Type { + p.expect('[') + if p.tok == ']' { + p.next() + + t := new(types.Slice) + p.update(t, nlist) + + *t = *types.NewSlice(p.parseType(pkg)) + return t + } + + t := new(types.Array) + p.update(t, nlist) + + len := p.parseInt64() + p.expect(']') + + *t = *types.NewArray(p.parseType(pkg), len) + return t +} + +// MapType = "map" "[" Type "]" Type . +func (p *parser) parseMapType(pkg *types.Package, nlist []interface{}) types.Type { + p.expectKeyword("map") + + t := new(types.Map) + p.update(t, nlist) + + p.expect('[') + key := p.parseType(pkg) + p.expect(']') + elem := p.parseType(pkg) + + *t = *types.NewMap(key, elem) + return t +} + +// ChanType = "chan" ["<-" | "-<"] Type . +func (p *parser) parseChanType(pkg *types.Package, nlist []interface{}) types.Type { + p.expectKeyword("chan") + + t := new(types.Chan) + p.update(t, nlist) + + dir := types.SendRecv + switch p.tok { + case '-': + p.next() + p.expect('<') + dir = types.SendOnly + + case '<': + // don't consume '<' if it belongs to Type + if p.scanner.Peek() == '-' { + p.next() + p.expect('-') + dir = types.RecvOnly + } + } + + *t = *types.NewChan(dir, p.parseType(pkg)) + return t +} + +// StructType = "struct" "{" { Field } "}" . +func (p *parser) parseStructType(pkg *types.Package, nlist []interface{}) types.Type { + p.expectKeyword("struct") + + t := new(types.Struct) + p.update(t, nlist) + + var fields []*types.Var + var tags []string + + p.expect('{') + for p.tok != '}' && p.tok != scanner.EOF { + field, tag := p.parseField(pkg) + p.expect(';') + fields = append(fields, field) + tags = append(tags, tag) + } + p.expect('}') + + *t = *types.NewStruct(fields, tags) + return t +} + +// ParamList = "(" [ { Parameter "," } Parameter ] ")" . +func (p *parser) parseParamList(pkg *types.Package) (*types.Tuple, bool) { + var list []*types.Var + isVariadic := false + + p.expect('(') + for p.tok != ')' && p.tok != scanner.EOF { + if len(list) > 0 { + p.expect(',') + } + par, variadic := p.parseParam(pkg) + list = append(list, par) + if variadic { + if isVariadic { + p.error("... not on final argument") + } + isVariadic = true + } + } + p.expect(')') + + return types.NewTuple(list...), isVariadic +} + +// ResultList = Type | ParamList . +func (p *parser) parseResultList(pkg *types.Package) *types.Tuple { + switch p.tok { + case '<': + p.next() + if p.tok == scanner.Ident && p.lit == "inl" { + return nil + } + taa, _ := p.parseTypeAfterAngle(pkg) + return types.NewTuple(types.NewParam(token.NoPos, pkg, "", taa)) + + case '(': + params, _ := p.parseParamList(pkg) + return params + + default: + return nil + } +} + +// FunctionType = ParamList ResultList . +func (p *parser) parseFunctionType(pkg *types.Package, nlist []interface{}) *types.Signature { + t := new(types.Signature) + p.update(t, nlist) + + params, isVariadic := p.parseParamList(pkg) + results := p.parseResultList(pkg) + + *t = *types.NewSignature(nil, params, results, isVariadic) + return t +} + +// Func = Name FunctionType [InlineBody] . +func (p *parser) parseFunc(pkg *types.Package) *types.Func { + if p.tok == '/' { + // Skip an /*asm ID */ comment. + p.expect('/') + p.expect('*') + if p.expect(scanner.Ident) == "asm" { + p.parseUnquotedString() + } + p.expect('*') + p.expect('/') + } + + name := p.parseName() + f := types.NewFunc(token.NoPos, pkg, name, p.parseFunctionType(pkg, nil)) + p.skipInlineBody() + + if name[0] == '.' || name[0] == '<' || strings.ContainsRune(name, '$') { + // This is an unexported function, + // or a function defined in a different package, + // or a type$equal or type$hash function. + // We only want to record exported functions. + return nil + } + + return f +} + +// InterfaceType = "interface" "{" { ("?" Type | Func) ";" } "}" . +func (p *parser) parseInterfaceType(pkg *types.Package, nlist []interface{}) types.Type { + p.expectKeyword("interface") + + t := new(types.Interface) + p.update(t, nlist) + + var methods []*types.Func + var embeddeds []types.Type + + p.expect('{') + for p.tok != '}' && p.tok != scanner.EOF { + if p.tok == '?' { + p.next() + embeddeds = append(embeddeds, p.parseType(pkg)) + } else { + method := p.parseFunc(pkg) + if method != nil { + methods = append(methods, method) + } + } + p.expect(';') + } + p.expect('}') + + *t = *types.NewInterfaceType(methods, embeddeds) + return t +} + +// PointerType = "*" ("any" | Type) . +func (p *parser) parsePointerType(pkg *types.Package, nlist []interface{}) types.Type { + p.expect('*') + if p.tok == scanner.Ident { + p.expectKeyword("any") + t := types.Typ[types.UnsafePointer] + p.update(t, nlist) + return t + } + + t := new(types.Pointer) + p.update(t, nlist) + + *t = *types.NewPointer(p.parseType(pkg, t)) + + return t +} + +// TypeSpec = NamedType | MapType | ChanType | StructType | InterfaceType | PointerType | ArrayOrSliceType | FunctionType . +func (p *parser) parseTypeSpec(pkg *types.Package, nlist []interface{}) types.Type { + switch p.tok { + case scanner.String: + return p.parseNamedType(nlist) + + case scanner.Ident: + switch p.lit { + case "map": + return p.parseMapType(pkg, nlist) + + case "chan": + return p.parseChanType(pkg, nlist) + + case "struct": + return p.parseStructType(pkg, nlist) + + case "interface": + return p.parseInterfaceType(pkg, nlist) + } + + case '*': + return p.parsePointerType(pkg, nlist) + + case '[': + return p.parseArrayOrSliceType(pkg, nlist) + + case '(': + return p.parseFunctionType(pkg, nlist) + } + + p.errorf("expected type name or literal, got %s", scanner.TokenString(p.tok)) + return nil +} + +const ( + // From gofrontend/go/export.h + // Note that these values are negative in the gofrontend and have been made positive + // in the gccgoimporter. + gccgoBuiltinINT8 = 1 + gccgoBuiltinINT16 = 2 + gccgoBuiltinINT32 = 3 + gccgoBuiltinINT64 = 4 + gccgoBuiltinUINT8 = 5 + gccgoBuiltinUINT16 = 6 + gccgoBuiltinUINT32 = 7 + gccgoBuiltinUINT64 = 8 + gccgoBuiltinFLOAT32 = 9 + gccgoBuiltinFLOAT64 = 10 + gccgoBuiltinINT = 11 + gccgoBuiltinUINT = 12 + gccgoBuiltinUINTPTR = 13 + gccgoBuiltinBOOL = 15 + gccgoBuiltinSTRING = 16 + gccgoBuiltinCOMPLEX64 = 17 + gccgoBuiltinCOMPLEX128 = 18 + gccgoBuiltinERROR = 19 + gccgoBuiltinBYTE = 20 + gccgoBuiltinRUNE = 21 +) + +func lookupBuiltinType(typ int) types.Type { + return [...]types.Type{ + gccgoBuiltinINT8: types.Typ[types.Int8], + gccgoBuiltinINT16: types.Typ[types.Int16], + gccgoBuiltinINT32: types.Typ[types.Int32], + gccgoBuiltinINT64: types.Typ[types.Int64], + gccgoBuiltinUINT8: types.Typ[types.Uint8], + gccgoBuiltinUINT16: types.Typ[types.Uint16], + gccgoBuiltinUINT32: types.Typ[types.Uint32], + gccgoBuiltinUINT64: types.Typ[types.Uint64], + gccgoBuiltinFLOAT32: types.Typ[types.Float32], + gccgoBuiltinFLOAT64: types.Typ[types.Float64], + gccgoBuiltinINT: types.Typ[types.Int], + gccgoBuiltinUINT: types.Typ[types.Uint], + gccgoBuiltinUINTPTR: types.Typ[types.Uintptr], + gccgoBuiltinBOOL: types.Typ[types.Bool], + gccgoBuiltinSTRING: types.Typ[types.String], + gccgoBuiltinCOMPLEX64: types.Typ[types.Complex64], + gccgoBuiltinCOMPLEX128: types.Typ[types.Complex128], + gccgoBuiltinERROR: types.Universe.Lookup("error").Type(), + gccgoBuiltinBYTE: types.Universe.Lookup("byte").Type(), + gccgoBuiltinRUNE: types.Universe.Lookup("rune").Type(), + }[typ] +} + +// Type = "<" "type" ( "-" int | int [ TypeSpec ] ) ">" . +// +// parseType updates the type map to t for all type numbers n. +// +func (p *parser) parseType(pkg *types.Package, n ...interface{}) types.Type { + p.expect('<') + t, _ := p.parseTypeAfterAngle(pkg, n...) + return t +} + +// (*parser).Type after reading the "<". +func (p *parser) parseTypeAfterAngle(pkg *types.Package, n ...interface{}) (t types.Type, n1 int) { + p.expectKeyword("type") + + n1 = 0 + switch p.tok { + case scanner.Int: + n1 = p.parseInt() + if p.tok == '>' { + if len(p.typeData) > 0 && p.typeList[n1] == nil { + p.parseSavedType(pkg, n1, n) + } + t = p.typeList[n1] + if len(p.typeData) == 0 && t == reserved { + p.errorf("invalid type cycle, type %d not yet defined (nlist=%v)", n1, n) + } + p.update(t, n) + } else { + p.reserve(n1) + t = p.parseTypeSpec(pkg, append(n, n1)) + } + + case '-': + p.next() + n1 := p.parseInt() + t = lookupBuiltinType(n1) + p.update(t, n) + + default: + p.errorf("expected type number, got %s (%q)", scanner.TokenString(p.tok), p.lit) + return nil, 0 + } + + if t == nil || t == reserved { + p.errorf("internal error: bad return from parseType(%v)", n) + } + + p.expect('>') + return +} + +// parseTypeExtended is identical to parseType, but if the type in +// question is a saved type, returns the index as well as the type +// pointer (index returned is zero if we parsed a builtin). +func (p *parser) parseTypeExtended(pkg *types.Package, n ...interface{}) (t types.Type, n1 int) { + p.expect('<') + t, n1 = p.parseTypeAfterAngle(pkg, n...) + return +} + +// InlineBody = "<inl:NN>" .{NN} +// Reports whether a body was skipped. +func (p *parser) skipInlineBody() { + // We may or may not have seen the '<' already, depending on + // whether the function had a result type or not. + if p.tok == '<' { + p.next() + p.expectKeyword("inl") + } else if p.tok != scanner.Ident || p.lit != "inl" { + return + } else { + p.next() + } + + p.expect(':') + want := p.parseInt() + p.expect('>') + + defer func(w uint64) { + p.scanner.Whitespace = w + }(p.scanner.Whitespace) + p.scanner.Whitespace = 0 + + got := 0 + for got < want { + r := p.scanner.Next() + if r == scanner.EOF { + p.error("unexpected EOF") + } + got += utf8.RuneLen(r) + } +} + +// Types = "types" maxp1 exportedp1 (offset length)* . +func (p *parser) parseTypes(pkg *types.Package) { + maxp1 := p.parseInt() + exportedp1 := p.parseInt() + p.typeList = make([]types.Type, maxp1, maxp1) + + type typeOffset struct { + offset int + length int + } + var typeOffsets []typeOffset + + total := 0 + for i := 1; i < maxp1; i++ { + len := p.parseInt() + typeOffsets = append(typeOffsets, typeOffset{total, len}) + total += len + } + + defer func(w uint64) { + p.scanner.Whitespace = w + }(p.scanner.Whitespace) + p.scanner.Whitespace = 0 + + // We should now have p.tok pointing to the final newline. + // The next runes from the scanner should be the type data. + + var sb strings.Builder + for sb.Len() < total { + r := p.scanner.Next() + if r == scanner.EOF { + p.error("unexpected EOF") + } + sb.WriteRune(r) + } + allTypeData := sb.String() + + p.typeData = []string{""} // type 0, unused + for _, to := range typeOffsets { + p.typeData = append(p.typeData, allTypeData[to.offset:to.offset+to.length]) + } + + for i := 1; i < int(exportedp1); i++ { + p.parseSavedType(pkg, i, nil) + } +} + +// parseSavedType parses one saved type definition. +func (p *parser) parseSavedType(pkg *types.Package, i int, nlist []interface{}) { + defer func(s *scanner.Scanner, tok rune, lit string) { + p.scanner = s + p.tok = tok + p.lit = lit + }(p.scanner, p.tok, p.lit) + + p.scanner = new(scanner.Scanner) + p.initScanner(p.scanner.Filename, strings.NewReader(p.typeData[i])) + p.expectKeyword("type") + id := p.parseInt() + if id != i { + p.errorf("type ID mismatch: got %d, want %d", id, i) + } + if p.typeList[i] == reserved { + p.errorf("internal error: %d already reserved in parseSavedType", i) + } + if p.typeList[i] == nil { + p.reserve(i) + p.parseTypeSpec(pkg, append(nlist, i)) + } + if p.typeList[i] == nil || p.typeList[i] == reserved { + p.errorf("internal error: parseSavedType(%d,%v) reserved/nil", i, nlist) + } +} + +// PackageInit = unquotedString unquotedString int . +func (p *parser) parsePackageInit() PackageInit { + name := p.parseUnquotedString() + initfunc := p.parseUnquotedString() + priority := -1 + if p.version == "v1" { + priority = p.parseInt() + } + return PackageInit{Name: name, InitFunc: initfunc, Priority: priority} +} + +// Create the package if we have parsed both the package path and package name. +func (p *parser) maybeCreatePackage() { + if p.pkgname != "" && p.pkgpath != "" { + p.pkg = p.getPkg(p.pkgpath, p.pkgname) + } +} + +// InitDataDirective = ( "v1" | "v2" | "v3" ) ";" | +// "priority" int ";" | +// "init" { PackageInit } ";" | +// "checksum" unquotedString ";" . +func (p *parser) parseInitDataDirective() { + if p.tok != scanner.Ident { + // unexpected token kind; panic + p.expect(scanner.Ident) + } + + switch p.lit { + case "v1", "v2", "v3": + p.version = p.lit + p.next() + p.expect(';') + p.expect('\n') + + case "priority": + p.next() + p.initdata.Priority = p.parseInt() + p.expectEOL() + + case "init": + p.next() + for p.tok != '\n' && p.tok != ';' && p.tok != scanner.EOF { + p.initdata.Inits = append(p.initdata.Inits, p.parsePackageInit()) + } + p.expectEOL() + + case "init_graph": + p.next() + // The graph data is thrown away for now. + for p.tok != '\n' && p.tok != ';' && p.tok != scanner.EOF { + p.parseInt64() + p.parseInt64() + } + p.expectEOL() + + case "checksum": + // Don't let the scanner try to parse the checksum as a number. + defer func(mode uint) { + p.scanner.Mode = mode + }(p.scanner.Mode) + p.scanner.Mode &^= scanner.ScanInts | scanner.ScanFloats + p.next() + p.parseUnquotedString() + p.expectEOL() + + default: + p.errorf("unexpected identifier: %q", p.lit) + } +} + +// Directive = InitDataDirective | +// "package" unquotedString [ unquotedString ] [ unquotedString ] ";" | +// "pkgpath" unquotedString ";" | +// "prefix" unquotedString ";" | +// "import" unquotedString unquotedString string ";" | +// "indirectimport" unquotedString unquotedstring ";" | +// "func" Func ";" | +// "type" Type ";" | +// "var" Var ";" | +// "const" Const ";" . +func (p *parser) parseDirective() { + if p.tok != scanner.Ident { + // unexpected token kind; panic + p.expect(scanner.Ident) + } + + switch p.lit { + case "v1", "v2", "v3", "priority", "init", "init_graph", "checksum": + p.parseInitDataDirective() + + case "package": + p.next() + p.pkgname = p.parseUnquotedString() + p.maybeCreatePackage() + if p.version != "v1" && p.tok != '\n' && p.tok != ';' { + p.parseUnquotedString() + p.parseUnquotedString() + } + p.expectEOL() + + case "pkgpath": + p.next() + p.pkgpath = p.parseUnquotedString() + p.maybeCreatePackage() + p.expectEOL() + + case "prefix": + p.next() + p.pkgpath = p.parseUnquotedString() + p.expectEOL() + + case "import": + p.next() + pkgname := p.parseUnquotedString() + pkgpath := p.parseUnquotedString() + p.getPkg(pkgpath, pkgname) + p.parseString() + p.expectEOL() + + case "indirectimport": + p.next() + pkgname := p.parseUnquotedString() + pkgpath := p.parseUnquotedString() + p.getPkg(pkgpath, pkgname) + p.expectEOL() + + case "types": + p.next() + p.parseTypes(p.pkg) + p.expectEOL() + + case "func": + p.next() + fun := p.parseFunc(p.pkg) + if fun != nil { + p.pkg.Scope().Insert(fun) + } + p.expectEOL() + + case "type": + p.next() + p.parseType(p.pkg) + p.expectEOL() + + case "var": + p.next() + v := p.parseVar(p.pkg) + if v != nil { + p.pkg.Scope().Insert(v) + } + p.expectEOL() + + case "const": + p.next() + c := p.parseConst(p.pkg) + p.pkg.Scope().Insert(c) + p.expectEOL() + + default: + p.errorf("unexpected identifier: %q", p.lit) + } +} + +// Package = { Directive } . +func (p *parser) parsePackage() *types.Package { + for p.tok != scanner.EOF { + p.parseDirective() + } + for _, f := range p.fixups { + if f.target.Underlying() == nil { + p.errorf("internal error: fixup can't be applied, loop required") + } + f.toUpdate.SetUnderlying(f.target.Underlying()) + } + p.fixups = nil + for _, typ := range p.typeList { + if it, ok := typ.(*types.Interface); ok { + it.Complete() + } + } + p.pkg.MarkComplete() + return p.pkg +} diff --git a/src/go/internal/gccgoimporter/parser_test.go b/src/go/internal/gccgoimporter/parser_test.go new file mode 100644 index 0000000..00128b4 --- /dev/null +++ b/src/go/internal/gccgoimporter/parser_test.go @@ -0,0 +1,78 @@ +// Copyright 2013 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 gccgoimporter + +import ( + "bytes" + "go/types" + "strings" + "testing" + "text/scanner" +) + +var typeParserTests = []struct { + id, typ, want, underlying, methods string +}{ + {id: "foo", typ: "<type -1>", want: "int8"}, + {id: "foo", typ: "<type 1 *<type -19>>", want: "*error"}, + {id: "foo", typ: "<type 1 *any>", want: "unsafe.Pointer"}, + {id: "foo", typ: "<type 1 \"Bar\" <type 2 *<type 1>>>", want: "foo.Bar", underlying: "*foo.Bar"}, + {id: "foo", typ: "<type 1 \"bar.Foo\" \"bar\" <type -1>\nfunc (? <type 1>) M ();\n>", want: "bar.Foo", underlying: "int8", methods: "func (bar.Foo).M()"}, + {id: "foo", typ: "<type 1 \".bar.foo\" \"bar\" <type -1>>", want: "bar.foo", underlying: "int8"}, + {id: "foo", typ: "<type 1 []<type -1>>", want: "[]int8"}, + {id: "foo", typ: "<type 1 [42]<type -1>>", want: "[42]int8"}, + {id: "foo", typ: "<type 1 map [<type -1>] <type -2>>", want: "map[int8]int16"}, + {id: "foo", typ: "<type 1 chan <type -1>>", want: "chan int8"}, + {id: "foo", typ: "<type 1 chan <- <type -1>>", want: "<-chan int8"}, + {id: "foo", typ: "<type 1 chan -< <type -1>>", want: "chan<- int8"}, + {id: "foo", typ: "<type 1 struct { I8 <type -1>; I16 <type -2> \"i16\"; }>", want: "struct{I8 int8; I16 int16 \"i16\"}"}, + {id: "foo", typ: "<type 1 interface { Foo (a <type -1>, b <type -2>) <type -1>; Bar (? <type -2>, ? ...<type -1>) (? <type -2>, ? <type -1>); Baz (); }>", want: "interface{Bar(int16, ...int8) (int16, int8); Baz(); Foo(a int8, b int16) int8}"}, + {id: "foo", typ: "<type 1 (? <type -1>) <type -2>>", want: "func(int8) int16"}, +} + +func TestTypeParser(t *testing.T) { + for _, test := range typeParserTests { + var p parser + p.init("test.gox", strings.NewReader(test.typ), make(map[string]*types.Package)) + p.version = "v2" + p.pkgname = test.id + p.pkgpath = test.id + p.maybeCreatePackage() + typ := p.parseType(p.pkg) + + if p.tok != scanner.EOF { + t.Errorf("expected full parse, stopped at %q", p.lit) + } + + // interfaces must be explicitly completed + if ityp, _ := typ.(*types.Interface); ityp != nil { + ityp.Complete() + } + + got := typ.String() + if got != test.want { + t.Errorf("got type %q, expected %q", got, test.want) + } + + if test.underlying != "" { + underlying := typ.Underlying().String() + if underlying != test.underlying { + t.Errorf("got underlying type %q, expected %q", underlying, test.underlying) + } + } + + if test.methods != "" { + nt := typ.(*types.Named) + var buf bytes.Buffer + for i := 0; i != nt.NumMethods(); i++ { + buf.WriteString(nt.Method(i).String()) + } + methods := buf.String() + if methods != test.methods { + t.Errorf("got methods %q, expected %q", methods, test.methods) + } + } + } +} diff --git a/src/go/internal/gccgoimporter/testdata/aliases.go b/src/go/internal/gccgoimporter/testdata/aliases.go new file mode 100644 index 0000000..cfb59b3 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/aliases.go @@ -0,0 +1,65 @@ +package aliases + +type ( + T0 [10]int + T1 []byte + T2 struct { + x int + } + T3 interface { + m() T2 + } + T4 func(int, T0) chan T2 +) + +// basic aliases +type ( + Ai = int + A0 = T0 + A1 = T1 + A2 = T2 + A3 = T3 + A4 = T4 + + A10 = [10]int + A11 = []byte + A12 = struct { + x int + } + A13 = interface { + m() A2 + } + A14 = func(int, A0) chan A2 +) + +// alias receiver types +func (T0) m1() {} +func (A0) m2() {} + +// alias receiver types (long type declaration chains) +type ( + V0 = V1 + V1 = (V2) + V2 = (V3) + V3 = T0 +) + +func (V1) n() {} + +// cycles +type C0 struct { + f1 C1 + f2 C2 +} + +type ( + C1 *C0 + C2 = C1 +) + +type ( + C5 struct { + f *C6 + } + C6 = C5 +) diff --git a/src/go/internal/gccgoimporter/testdata/aliases.gox b/src/go/internal/gccgoimporter/testdata/aliases.gox new file mode 100644 index 0000000..2428c06 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/aliases.gox @@ -0,0 +1,33 @@ +v2; +package aliases; +prefix go; +package aliases go.aliases go.aliases; +type <type 1 "A0" = <type 2 "T0" <type 3 [10 ] <type -11>> + func (? <esc:0x1> <type 2>) .go.aliases.m1 (); + func (? <esc:0x1> <type 1>) .go.aliases.m2 (); + func (? <esc:0x1> <type 4 "V1" = <type 5 "V2" = <type 6 "V3" = <type 2>>>>) .go.aliases.n (); +>>; +type <type 7 "A1" = <type 8 "T1" <type 9 [] <type -20>>>>; +type <type 10 "A10" = <type 11 [10 ] <type -11>>>; +type <type 12 "A11" = <type 13 [] <type -20>>>; +type <type 14 "A12" = <type 15 struct { .go.aliases.x <type -11>; }>>; +type <type 16 "A13" = <type 17 interface { .go.aliases.m () <type 18 "A2" = <type 19 "T2" <type 20 struct { .go.aliases.x <type -11>; }>>>; }>>; +type <type 21 "A14" = <type 22 (? <type -11>, ? <type 1>) <type 23 chan <type 18>>>>; +type <type 18>; +type <type 24 "A3" = <type 25 "T3" <type 26 interface { .go.aliases.m () <type 19>; }>>>; +type <type 27 "A4" = <type 28 "T4" <type 29 (? <type -11>, ? <type 2>) <type 30 chan <type 19>>>>>; +type <type 31 "Ai" = <type -11>>; +type <type 32 "C0" <type 33 struct { .go.aliases.f1 <type 34 "C1" <type 35 *<type 32>>>; .go.aliases.f2 <type 36 "C2" = <type 34>>; }>>; +type <type 34>; +type <type 36>; +type <type 37 "C5" <type 38 struct { .go.aliases.f <type 39 *<type 40 "C6" = <type 37>>>; }>>; +type <type 40>; +type <type 2>; +type <type 8>; +type <type 19>; +type <type 25>; +type <type 28>; +type <type 41 "V0" = <type 4>>; +type <type 4>; +type <type 5>; +type <type 6>; diff --git a/src/go/internal/gccgoimporter/testdata/complexnums.go b/src/go/internal/gccgoimporter/testdata/complexnums.go new file mode 100644 index 0000000..a51b6b0 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/complexnums.go @@ -0,0 +1,6 @@ +package complexnums + +const NN = -1 - 1i +const NP = -1 + 1i +const PN = 1 - 1i +const PP = 1 + 1i diff --git a/src/go/internal/gccgoimporter/testdata/complexnums.gox b/src/go/internal/gccgoimporter/testdata/complexnums.gox new file mode 100644 index 0000000..b66524f --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/complexnums.gox @@ -0,0 +1,8 @@ +v1; +package complexnums; +pkgpath complexnums; +priority 1; +const NN = -0.1E1-0.1E1i ; +const NP = -0.1E1+0.1E1i ; +const PN = 0.1E1-0.1E1i ; +const PP = 0.1E1+0.1E1i ; diff --git a/src/go/internal/gccgoimporter/testdata/conversions.go b/src/go/internal/gccgoimporter/testdata/conversions.go new file mode 100644 index 0000000..653927a --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/conversions.go @@ -0,0 +1,5 @@ +package conversions + +type Units string + +const Bits = Units("bits") diff --git a/src/go/internal/gccgoimporter/testdata/conversions.gox b/src/go/internal/gccgoimporter/testdata/conversions.gox new file mode 100644 index 0000000..7de6cda --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/conversions.gox @@ -0,0 +1,6 @@ +v2; +package conversions; +prefix go; +package conversions go.conversions go.conversions; +const Bits <type 1 "Units" <type -16>> = convert(<type 1>, "bits"); +type <type 1>; diff --git a/src/go/internal/gccgoimporter/testdata/escapeinfo.go b/src/go/internal/gccgoimporter/testdata/escapeinfo.go new file mode 100644 index 0000000..103ad95 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/escapeinfo.go @@ -0,0 +1,13 @@ +// Test case for escape info in export data. To compile and extract .gox file: +// gccgo -fgo-optimize-allocs -c escapeinfo.go +// objcopy -j .go_export escapeinfo.o escapeinfo.gox + +package escapeinfo + +type T struct{ data []byte } + +func NewT(data []byte) *T { + return &T{data} +} + +func (*T) Read(p []byte) {} diff --git a/src/go/internal/gccgoimporter/testdata/escapeinfo.gox b/src/go/internal/gccgoimporter/testdata/escapeinfo.gox Binary files differnew file mode 100644 index 0000000..1db8156 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/escapeinfo.gox diff --git a/src/go/internal/gccgoimporter/testdata/imports.go b/src/go/internal/gccgoimporter/testdata/imports.go new file mode 100644 index 0000000..7907316 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/imports.go @@ -0,0 +1,5 @@ +package imports + +import "fmt" + +var Hello = fmt.Sprintf("Hello, world") diff --git a/src/go/internal/gccgoimporter/testdata/imports.gox b/src/go/internal/gccgoimporter/testdata/imports.gox new file mode 100644 index 0000000..958a4f5 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/imports.gox @@ -0,0 +1,7 @@ +v1; +package imports; +pkgpath imports; +priority 7; +import fmt fmt "fmt"; +init imports imports..import 7 math math..import 1 runtime runtime..import 1 strconv strconv..import 2 io io..import 3 reflect reflect..import 3 syscall syscall..import 3 time time..import 4 os os..import 5 fmt fmt..import 6; +var Hello <type -16>; diff --git a/src/go/internal/gccgoimporter/testdata/issue27856.go b/src/go/internal/gccgoimporter/testdata/issue27856.go new file mode 100644 index 0000000..bf361e1 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue27856.go @@ -0,0 +1,9 @@ +package lib + +type M struct { + E E +} +type F struct { + _ *M +} +type E = F diff --git a/src/go/internal/gccgoimporter/testdata/issue27856.gox b/src/go/internal/gccgoimporter/testdata/issue27856.gox new file mode 100644 index 0000000..6665e64 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue27856.gox @@ -0,0 +1,9 @@ +v2; +package main; +pkgpath main; +import runtime runtime "runtime"; +init runtime runtime..import sys runtime_internal_sys..import; +init_graph 0 1; +type <type 1 "E" = <type 2 "F" <type 3 struct { .main._ <type 4 *<type 5 "M" <type 6 struct { E <type 1>; }>>>; }>>>; +type <type 2>; +type <type 5>; diff --git a/src/go/internal/gccgoimporter/testdata/issue29198.go b/src/go/internal/gccgoimporter/testdata/issue29198.go new file mode 100644 index 0000000..75c2162 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue29198.go @@ -0,0 +1,37 @@ +// Copyright 2018 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 server + +import ( + "context" + "errors" +) + +type A struct { + x int +} + +func (a *A) AMethod(y int) *Server { + return nil +} + +// FooServer is a server that provides Foo services +type FooServer Server + +func (f *FooServer) WriteEvents(ctx context.Context, x int) error { + return errors.New("hey!") +} + +type Server struct { + FooServer *FooServer + user string + ctx context.Context +} + +func New(sctx context.Context, u string) (*Server, error) { + s := &Server{user: u, ctx: sctx} + s.FooServer = (*FooServer)(s) + return s, nil +} diff --git a/src/go/internal/gccgoimporter/testdata/issue29198.gox b/src/go/internal/gccgoimporter/testdata/issue29198.gox new file mode 100644 index 0000000..905c866 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue29198.gox @@ -0,0 +1,86 @@ +v2; +package server; +pkgpath issue29198; +import context context "context"; +import errors errors "errors"; +init context context..import fmt fmt..import poll internal_poll..import testlog internal_testlog..import io io..import os os..import reflect reflect..import runtime runtime..import sys runtime_internal_sys..import strconv strconv..import sync sync..import syscall syscall..import time time..import unicode unicode..import; +init_graph 0 1 0 2 0 3 0 4 0 5 0 6 0 7 0 8 0 9 0 10 0 11 0 12 0 13 1 2 1 3 1 4 1 5 1 6 1 7 1 8 1 9 1 10 1 11 1 12 1 13 2 4 2 7 2 8 2 10 2 11 2 12 4 7 4 8 4 10 5 2 5 3 5 4 5 7 5 8 5 10 5 11 5 12 6 7 6 8 6 9 6 10 6 13 7 8 9 7 9 8 10 7 10 8 11 7 11 8 11 10 12 7 12 8 12 10 12 11; +type <type 1 "A" <type 2 struct { .issue29198.x <type -11>; }> + func (a <esc:0x1> <type 3 *<type 1>>) AMethod (y <type -11>) <type 4 *<type 5 "Server" <type 6 struct { FooServer <type 7 *<type 8 "FooServer" <type 5> + func (f <esc:0x1> <type 9 *<type 8>>) WriteEvents (ctx <esc:0x1> <type 10 "context.Context" <type 11 interface { Deadline () (deadline <type 12 "time.Time" "time" <type 13 struct { .time.wall <type -8>; .time.ext <type -4>; .time.loc <type 14 *<type 15 "time.Location" <type 16 struct { .time.name <type -16>; .time.zone <type 17 [] <type 18 ".time.zone" <type 19 struct { .time.name <type -16>; .time.offset <type -11>; .time.isDST <type -15>; }>>>; .time.tx <type 20 [] <type 21 ".time.zoneTrans" <type 22 struct { .time.when <type -4>; .time.index <type -5>; .time.isstd <type -15>; .time.isutc <type -15>; }>>>; .time.cacheStart <type -4>; .time.cacheEnd <type -4>; .time.cacheZone <type 23 *<type 18>>; }> + func (l <esc:0x22> <type 24 *<type 15>>) String () <type -16>; + func (l <esc:0x1> <type 24>) .time.lookupFirstZone () <type -11>; + func (l <esc:0x12> <type 24>) .time.get () <type 24>; + func (l <esc:0x32> <type 24>) .time.lookup (sec <type -4>) (name <type -16>, offset <type -11>, isDST <type -15>, start <type -4>, end <type -4>); + func (l <esc:0x1> <type 24>) .time.lookupName (name <esc:0x1> <type -16>, unix <type -4>) (offset <type -11>, ok <type -15>); + func (l <esc:0x1> <type 24>) .time.firstZoneUsed () <type -15>; +>>; }> + func (t <esc:0x12> <type 12>) In (loc <type 14>) <type 12>; + func (t <esc:0x1> <type 12>) .time.date (full <type -15>) (year <type -11>, month <type 25 "time.Month" <type -11> + func (m <type 25>) String () <type -16>; +>, day <type -11>, yday <type -11>); + func (t <esc:0x1> <type 12>) Sub (u <esc:0x1> <type 12>) <type 26 "time.Duration" <type -4> + func (d <type 26>) Truncate (m <type 26>) <type 26>; + func (d <type 26>) String () <type -16>; + func (d <type 26>) Round (m <type 26>) <type 26>; + func (d <type 26>) Seconds () <type -10>; + func (d <type 26>) Nanoseconds () <type -4>; + func (d <type 26>) Minutes () <type -10>; + func (d <type 26>) Hours () <type -10>; +>; + func (t <esc:0x12> <type 12>) Add (d <type 26>) <type 12>; + func (t <esc:0x12> <type 12>) UTC () <type 12>; + func (t <type 12>) AddDate (years <type -11>, months <type -11>, days <type -11>) <type 12>; + func (t <esc:0x1> <type 12>) MarshalBinary () (? <type 27 [] <type -20>>, ? <type -19>); + func (t <esc:0x1> <type 12>) Nanosecond () <type -11>; + func (t <esc:0x12> <type 12>) Round (d <type 26>) <type 12>; + func (t <esc:0x1> <type 12>) Minute () <type -11>; + func (t <esc:0x1> <type 12>) Clock () (hour <type -11>, min <type -11>, sec <type -11>); + func (t <esc:0x1> <type 12>) ISOWeek () (year <type -11>, week <type -11>); + func (t <esc:0x1> <type 12>) Day () <type -11>; + func (t <esc:0x1> <type 28 *<type 12>>) .time.mono () <type -4>; + func (t <esc:0x1> <type 12>) UnixNano () <type -4>; + func (t <esc:0x1> <type 28>) .time.sec () <type -4>; + func (t <esc:0x1> <type 12>) Second () <type -11>; + func (t <esc:0x1> <type 12>) Before (u <esc:0x1> <type 12>) <type -15>; + func (t <esc:0x1> <type 28>) UnmarshalBinary (data <esc:0x1> <type 29 [] <type -20>>) <type -19>; + func (t <esc:0x1> <type 12>) Month () <type 25>; + func (t <esc:0x1> <type 12>) YearDay () <type -11>; + func (t <esc:0x12> <type 12>) Location () <type 14>; + func (t <esc:0x32> <type 12>) Zone () (name <type -16>, offset <type -11>); + func (t <esc:0x12> <type 12>) Local () <type 12>; + func (t <esc:0x1> <type 28>) .time.setLoc (loc <type 14>); + func (t <esc:0x12> <type 12>) Truncate (d <type 26>) <type 12>; + func (t <esc:0x1> <type 12>) MarshalJSON () (? <type 30 [] <type -20>>, ? <type -19>); + func (t <esc:0x1> <type 12>) AppendFormat (b <esc:0x12> <type 31 [] <type -20>>, layout <esc:0x1> <type -16>) <type 32 [] <type -20>>; + func (t <esc:0x1> <type 28>) GobDecode (data <esc:0x1> <type 33 [] <type -20>>) <type -19>; + func (t <esc:0x1> <type 28>) UnmarshalJSON (data <esc:0x1> <type 34 [] <type -20>>) <type -19>; + func (t <esc:0x1> <type 12>) MarshalText () (? <type 35 [] <type -20>>, ? <type -19>); + func (t <esc:0x1> <type 12>) GobEncode () (? <type 36 [] <type -20>>, ? <type -19>); + func (t <esc:0x1> <type 28>) .time.stripMono (); + func (t <esc:0x1> <type 12>) After (u <esc:0x1> <type 12>) <type -15>; + func (t <esc:0x1> <type 12>) Hour () <type -11>; + func (t <esc:0x1> <type 28>) UnmarshalText (data <esc:0x1> <type 37 [] <type -20>>) <type -19>; + func (t <esc:0x1> <type 12>) Equal (u <esc:0x1> <type 12>) <type -15>; + func (t <esc:0x1> <type 28>) .time.setMono (m <type -4>); + func (t <esc:0x1> <type 12>) Year () <type -11>; + func (t <esc:0x1> <type 12>) IsZero () <type -15>; + func (t <esc:0x1> <type 28>) .time.addSec (d <type -4>); + func (t <esc:0x1> <type 12>) Weekday () <type 38 "time.Weekday" <type -11> + func (d <type 38>) String () <type -16>; +>; + func (t <esc:0x1> <type 12>) String () <type -16>; + func (t <esc:0x1> <type 28>) .time.nsec () <type -3>; + func (t <esc:0x1> <type 12>) Format (layout <esc:0x1> <type -16>) <type -16>; + func (t <esc:0x1> <type 28>) .time.unixSec () <type -4>; + func (t <esc:0x1> <type 12>) Unix () <type -4>; + func (t <esc:0x1> <type 12>) .time.abs () <type -8>; + func (t <esc:0x32> <type 12>) .time.locabs () (name <type -16>, offset <type -11>, abs <type -8>); + func (t <esc:0x1> <type 12>) Date () (year <type -11>, month <type 25>, day <type -11>); +>, ok <type -15>); Done () <type 39 chan <- <type 40 struct { }>>; Err () <type -19>; Value (key <type 41 interface { }>) <type 42 interface { }>; }>>, x <type -11>) <type -19>; +>>; .issue29198.user <type -16>; .issue29198.ctx <type 10>; }>>>; +>; +type <type 8>; +func New (sctx <type 10>, u <type -16>) (? <type 43 *<type 5>>, ? <type -19>); +type <type 5>; +checksum 86C8D76B2582F55A8BD2CA9E00060358EC1CE214; diff --git a/src/go/internal/gccgoimporter/testdata/issue30628.go b/src/go/internal/gccgoimporter/testdata/issue30628.go new file mode 100644 index 0000000..8fd7c13 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue30628.go @@ -0,0 +1,18 @@ +package issue30628 + +import ( + "os" + "sync" +) + +const numR = int32(os.O_TRUNC + 5) + +type Apple struct { + hey sync.RWMutex + x int + RQ [numR]struct { + Count uintptr + NumBytes uintptr + Last uintptr + } +} diff --git a/src/go/internal/gccgoimporter/testdata/issue30628.gox b/src/go/internal/gccgoimporter/testdata/issue30628.gox new file mode 100644 index 0000000..0ff6259 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue30628.gox @@ -0,0 +1,28 @@ +v3; +package issue30628 +pkgpath issue30628 +import os os "os" +import sync sync "sync" +init cpu internal..z2fcpu..import poll internal..z2fpoll..import testlog internal..z2ftestlog..import io io..import os os..import runtime runtime..import sys runtime..z2finternal..z2fsys..import sync sync..import syscall syscall..import time time..import +init_graph 1 0 1 3 1 5 1 6 1 7 1 8 1 9 3 0 3 5 3 6 3 7 4 0 4 1 4 2 4 3 4 5 4 6 4 7 4 8 4 9 5 0 5 6 7 0 7 5 7 6 8 0 8 5 8 6 8 7 9 0 9 5 9 6 9 7 9 8 +types 13 2 24 84 208 17 30 41 147 86 17 64 25 75 +type 1 "Apple" <type 2> +type 2 struct { .issue30628.hey <type 3>; .issue30628.x <type -11>; RQ <type 11>; } +type 3 "sync.RWMutex" <type 7> + func (rw <type 4>) Lock () + func (rw <esc:0x12> <type 4>) RLocker () ($ret8 <type 5>) + func (rw <type 4>) RUnlock () + func (rw <type 4>) Unlock () + func (rw <type 4>) RLock () +type 4 *<type 3> +type 5 "sync.Locker" <type 6> +type 6 interface { Lock (); Unlock (); } +type 7 struct { .sync.w <type 8>; .sync.writerSem <type -7>; .sync.readerSem <type -7>; .sync.readerCount <type -3>; .sync.readerWait <type -3>; } +type 8 "sync.Mutex" <type 10> + func (m <type 9>) Unlock () + func (m <type 9>) Lock () +type 9 *<type 8> +type 10 struct { .sync.state <type -3>; .sync.sema <type -7>; } +type 11 [517 ] <type 12> +type 12 struct { Count <type -13>; NumBytes <type -13>; Last <type -13>; } +checksum 199DCF6D3EE2FCF39F715B4E42B5F87F5B15D3AF diff --git a/src/go/internal/gccgoimporter/testdata/issue31540.go b/src/go/internal/gccgoimporter/testdata/issue31540.go new file mode 100644 index 0000000..2c6799e --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue31540.go @@ -0,0 +1,26 @@ +// Copyright 2019 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 issue31540 + +type Y struct { + q int +} + +type Z map[int]int + +type X = map[Y]Z + +type A1 = X + +type A2 = A1 + +type S struct { + b int + A2 +} + +func Hallo() S { + return S{} +} diff --git a/src/go/internal/gccgoimporter/testdata/issue31540.gox b/src/go/internal/gccgoimporter/testdata/issue31540.gox new file mode 100644 index 0000000..abdc696 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue31540.gox @@ -0,0 +1,16 @@ +v3; +package issue31540 +pkgpath issue31540 +types 11 7 23 23 20 22 20 21 57 31 45 36 +type 1 "A1" = <type 4> +type 2 "A2" = <type 1> +type 3 "S" <type 7> +type 4 "X" = <type 8> +type 5 "Y" <type 9> +type 6 "Z" <type 10> +type 7 struct { .go.mapalias.b <type -11>; ? <type 2>; } +type 8 map [<type 5>] <type 6> +type 9 struct { .go.mapalias.q <type -11>; } +type 10 map [<type -11>] <type -11> +func Hallo () <type 3> +checksum C3FAF2524A90BC11225EE65D059BF27DFB69134B diff --git a/src/go/internal/gccgoimporter/testdata/issue34182.go b/src/go/internal/gccgoimporter/testdata/issue34182.go new file mode 100644 index 0000000..2a5c333 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue34182.go @@ -0,0 +1,17 @@ +// Copyright 2019 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 issue34182 + +type T1 struct { + f *T2 +} + +type T2 struct { + f T3 +} + +type T3 struct { + *T2 +} diff --git a/src/go/internal/gccgoimporter/testdata/issue34182.gox b/src/go/internal/gccgoimporter/testdata/issue34182.gox new file mode 100644 index 0000000..671a7d6 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/issue34182.gox @@ -0,0 +1,13 @@ +v3; +package issue34182 +pkgpath issue34182 +init issue34182 ~go.issue34182 +types 8 4 21 21 21 17 30 45 45 +type 1 "T1" <type 6> +type 2 "T2" <type 7> +type 3 "T3" <type 5> +type 4 *<type 2> +type 5 struct { ? <type 4>; } +type 6 struct { .go.issue34182.f <type 4>; } +type 7 struct { .go.issue34182.f <type 3>; } +checksum FF02C49BAF44B06C087ED4E573F7CC880C79C208 diff --git a/src/go/internal/gccgoimporter/testdata/libimportsar.a b/src/go/internal/gccgoimporter/testdata/libimportsar.a Binary files differnew file mode 100644 index 0000000..6f30758 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/libimportsar.a diff --git a/src/go/internal/gccgoimporter/testdata/nointerface.go b/src/go/internal/gccgoimporter/testdata/nointerface.go new file mode 100644 index 0000000..6a545f2 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/nointerface.go @@ -0,0 +1,12 @@ +// Copyright 2018 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 nointerface + +type I int + +//go:nointerface +func (p *I) Get() int { return int(*p) } + +func (p *I) Set(v int) { *p = I(v) } diff --git a/src/go/internal/gccgoimporter/testdata/nointerface.gox b/src/go/internal/gccgoimporter/testdata/nointerface.gox new file mode 100644 index 0000000..7b73d17 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/nointerface.gox @@ -0,0 +1,8 @@ +v3; +package nointerface +pkgpath nointerface +types 3 2 133 17 +type 1 "I" <type -11> + func /*nointerface*/ (p <esc:0x1> <type 2>) Get () <type -11> + func (p <esc:0x1> <type 2>) Set (v <type -11>) +type 2 *<type 1> diff --git a/src/go/internal/gccgoimporter/testdata/notinheap.go b/src/go/internal/gccgoimporter/testdata/notinheap.go new file mode 100644 index 0000000..b1ac967 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/notinheap.go @@ -0,0 +1,4 @@ +package notinheap + +//go:notinheap +type S struct{} diff --git a/src/go/internal/gccgoimporter/testdata/notinheap.gox b/src/go/internal/gccgoimporter/testdata/notinheap.gox new file mode 100644 index 0000000..cc438e7 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/notinheap.gox @@ -0,0 +1,7 @@ +v3; +package notinheap +pkgpath notinheap +init notinheap ~notinheap +types 3 2 30 18 +type 1 "S" notinheap <type 2> +type 2 struct { } diff --git a/src/go/internal/gccgoimporter/testdata/pointer.go b/src/go/internal/gccgoimporter/testdata/pointer.go new file mode 100644 index 0000000..4ebc671 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/pointer.go @@ -0,0 +1,3 @@ +package pointer + +type Int8Ptr *int8 diff --git a/src/go/internal/gccgoimporter/testdata/pointer.gox b/src/go/internal/gccgoimporter/testdata/pointer.gox new file mode 100644 index 0000000..d96ebbd --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/pointer.gox @@ -0,0 +1,4 @@ +v1; +package pointer; +pkgpath pointer; +type <type 1 "Int8Ptr" <type 2 *<type -1>>>; diff --git a/src/go/internal/gccgoimporter/testdata/time.gox b/src/go/internal/gccgoimporter/testdata/time.gox Binary files differnew file mode 100644 index 0000000..80c2dbc --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/time.gox diff --git a/src/go/internal/gccgoimporter/testdata/unicode.gox b/src/go/internal/gccgoimporter/testdata/unicode.gox Binary files differnew file mode 100644 index 0000000..e70e539 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/unicode.gox diff --git a/src/go/internal/gccgoimporter/testdata/v1reflect.gox b/src/go/internal/gccgoimporter/testdata/v1reflect.gox Binary files differnew file mode 100644 index 0000000..ea46841 --- /dev/null +++ b/src/go/internal/gccgoimporter/testdata/v1reflect.gox diff --git a/src/go/internal/gcimporter/exportdata.go b/src/go/internal/gcimporter/exportdata.go new file mode 100644 index 0000000..c12e459 --- /dev/null +++ b/src/go/internal/gcimporter/exportdata.go @@ -0,0 +1,91 @@ +// 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 FindExportData. + +package gcimporter + +import ( + "bufio" + "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 + } + // leave for debugging + if false { + 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 = fmt.Errorf("invalid archive header") + return + } + name = strings.TrimSpace(string(hdr[:16])) + return +} + +// FindExportData 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. The hdr result +// is the string before the export data, either "$$" or "$$B". +// +func FindExportData(r *bufio.Reader) (hdr string, err error) { + // Read first line to make sure this is an object file. + line, err := r.ReadSlice('\n') + if err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + + if string(line) == "!<arch>\n" { + // Archive file. Scan to __.PKGDEF. + var name string + if name, _, err = readGopackHeader(r); err != nil { + return + } + + // First entry should be __.PKGDEF. + if name != "__.PKGDEF" { + err = fmt.Errorf("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 { + err = fmt.Errorf("can't find export data (%v)", err) + 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 = fmt.Errorf("not a Go object file") + return + } + + // Skip over object header to export data. + // Begins after first line starting with $$. + for line[0] != '$' { + if line, err = r.ReadSlice('\n'); err != nil { + err = fmt.Errorf("can't find export data (%v)", err) + return + } + } + hdr = string(line) + + return +} diff --git a/src/go/internal/gcimporter/gcimporter.go b/src/go/internal/gcimporter/gcimporter.go new file mode 100644 index 0000000..73cf633 --- /dev/null +++ b/src/go/internal/gcimporter/gcimporter.go @@ -0,0 +1,171 @@ +// 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 gcimporter implements Import for gc-generated object files. +package gcimporter // import "go/internal/gcimporter" + +import ( + "bufio" + "fmt" + "go/build" + "go/token" + "go/types" + "io" + "os" + "path/filepath" + "strings" +) + +// debugging/development support +const debug = false + +var pkgExts = [...]string{".a", ".o"} + +// 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). A relative srcDir is interpreted +// relative to the current working directory. +// If no file was found, an empty filename is returned. +// +func FindPkg(path, srcDir string) (filename, id string) { + if path == "" { + return + } + + var noext string + switch { + default: + // "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x" + // Don't require the source files to be present. + if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282 + srcDir = abs + } + bp, _ := build.Import(path, srcDir, build.FindOnly|build.AllowBinary) + if bp.PkgObj == "" { + id = path // make sure we have an id to print in error message + return + } + noext = strings.TrimSuffix(bp.PkgObj, ".a") + id = bp.ImportPath + + 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 + id = path + } + + if false { // for debugging + if path != id { + fmt.Printf("%s -> %s\n", path, id) + } + } + + // try extensions + for _, ext := range pkgExts { + filename = noext + ext + if f, err := os.Stat(filename); err == nil && !f.IsDir() { + return + } + } + + filename = "" // not found + return +} + +// Import imports a gc-generated package given its import path and srcDir, adds +// the corresponding package object to the packages map, and returns the object. +// The packages map must contain all packages already imported. +// +func Import(fset *token.FileSet, packages map[string]*types.Package, path, srcDir string, lookup func(path string) (io.ReadCloser, error)) (pkg *types.Package, err error) { + var rc io.ReadCloser + var id string + if lookup != nil { + // With custom lookup specified, assume that caller has + // converted path to a canonical import path for use in the map. + if path == "unsafe" { + return types.Unsafe, nil + } + id = path + + // No need to re-import if the package was imported completely before. + if pkg = packages[id]; pkg != nil && pkg.Complete() { + return + } + f, err := lookup(path) + if err != nil { + return nil, err + } + rc = f + } else { + var filename string + filename, id = FindPkg(path, srcDir) + if filename == "" { + if path == "unsafe" { + return types.Unsafe, nil + } + return nil, fmt.Errorf("can't find import: %q", id) + } + + // no need to re-import if the package was imported completely before + if pkg = packages[id]; pkg != nil && pkg.Complete() { + return + } + + // open file + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer func() { + if err != nil { + // add file name to error + err = fmt.Errorf("%s: %v", filename, err) + } + }() + rc = f + } + defer rc.Close() + + var hdr string + buf := bufio.NewReader(rc) + if hdr, err = FindExportData(buf); err != nil { + return + } + + switch hdr { + case "$$\n": + err = fmt.Errorf("import %q: old textual export format no longer supported (recompile library)", path) + + case "$$B\n": + var exportFormat byte + exportFormat, err = buf.ReadByte() + + // The indexed export format starts with an 'i'; the older + // binary export format starts with a 'c', 'd', or 'v' + // (from "version"). Select appropriate importer. + if err == nil && exportFormat == 'i' { + pkg, err = iImportData(fset, packages, buf, id) + } else { + err = fmt.Errorf("import %q: old binary export format no longer supported (recompile library)", path) + } + + default: + err = fmt.Errorf("import %q: unknown export data header: %q", path, hdr) + } + + return +} + +type byPath []*types.Package + +func (a byPath) Len() int { return len(a) } +func (a byPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byPath) Less(i, j int) bool { return a[i].Path() < a[j].Path() } diff --git a/src/go/internal/gcimporter/gcimporter_test.go b/src/go/internal/gcimporter/gcimporter_test.go new file mode 100644 index 0000000..3c76aaf --- /dev/null +++ b/src/go/internal/gcimporter/gcimporter_test.go @@ -0,0 +1,619 @@ +// 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 gcimporter + +import ( + "bytes" + "fmt" + "internal/testenv" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "go/token" + "go/types" +) + +// skipSpecialPlatforms causes the test to be skipped for platforms where +// builders (build.golang.org) don't have access to compiled packages for +// import. +func skipSpecialPlatforms(t *testing.T) { + switch platform := runtime.GOOS + "-" + runtime.GOARCH; platform { + case "darwin-arm64": + t.Skipf("no compiled packages available for import on %s", platform) + } +} + +// compile runs the compiler on filename, with dirname as the working directory, +// and writes the output file to outdirname. +func compile(t *testing.T, dirname, filename, outdirname string) string { + // filename must end with ".go" + if !strings.HasSuffix(filename, ".go") { + t.Fatalf("filename doesn't end in .go: %s", filename) + } + basename := filepath.Base(filename) + outname := filepath.Join(outdirname, basename[:len(basename)-2]+"o") + cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-o", outname, filename) + cmd.Dir = dirname + out, err := cmd.CombinedOutput() + if err != nil { + t.Logf("%s", out) + t.Fatalf("go tool compile %s failed: %s", filename, err) + } + return outname +} + +func testPath(t *testing.T, path, srcDir string) *types.Package { + t0 := time.Now() + fset := token.NewFileSet() + pkg, err := Import(fset, make(map[string]*types.Package), path, srcDir, nil) + if err != nil { + t.Errorf("testPath(%s): %s", path, err) + return nil + } + t.Logf("testPath(%s): %v", path, time.Since(t0)) + return pkg +} + +const maxTime = 30 * 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 := os.ReadDir(dirname) + if err != nil { + t.Fatalf("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), dir) != nil { + nimports++ + } + } + } + case f.IsDir(): + nimports += testDir(t, filepath.Join(dir, f.Name()), endTime) + } + } + return +} + +func mktmpdir(t *testing.T) string { + tmpdir, err := os.MkdirTemp("", "gcimporter_test") + if err != nil { + t.Fatal("mktmpdir:", err) + } + if err := os.Mkdir(filepath.Join(tmpdir, "testdata"), 0700); err != nil { + os.RemoveAll(tmpdir) + t.Fatal("mktmpdir:", err) + } + return tmpdir +} + +func TestImportTestdata(t *testing.T) { + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + + compile(t, "testdata", "exports.go", filepath.Join(tmpdir, "testdata")) + + if pkg := testPath(t, "./testdata/exports", tmpdir); pkg != nil { + // The package's Imports list must include all packages + // explicitly imported by exports.go, plus all packages + // referenced indirectly via exported objects in exports.go. + // With the textual export format, the list may also include + // additional packages that are not strictly required for + // import processing alone (they are exported to err "on + // the safe side"). + // TODO(gri) update the want list to be precise, now that + // the textual export data is gone. + got := fmt.Sprint(pkg.Imports()) + for _, want := range []string{"go/ast", "go/token"} { + if !strings.Contains(got, want) { + t.Errorf(`Package("exports").Imports() = %s, does not contain %s`, got, want) + } + } + } +} + +func TestVersionHandling(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + const dir = "./testdata/versions" + list, err := os.ReadDir(dir) + if err != nil { + t.Fatal(err) + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + corruptdir := filepath.Join(tmpdir, "testdata", "versions") + if err := os.Mkdir(corruptdir, 0700); err != nil { + t.Fatal(err) + } + + fset := token.NewFileSet() + + for _, f := range list { + name := f.Name() + if !strings.HasSuffix(name, ".a") { + continue // not a package file + } + if strings.Contains(name, "corrupted") { + continue // don't process a leftover corrupted file + } + pkgpath := "./" + name[:len(name)-2] + + if testing.Verbose() { + t.Logf("importing %s", name) + } + + // test that export data can be imported + _, err := Import(fset, make(map[string]*types.Package), pkgpath, dir, nil) + if err != nil { + // ok to fail if it fails with a no longer supported error for select files + if strings.Contains(err.Error(), "no longer supported") { + switch name { + case "test_go1.7_0.a", "test_go1.7_1.a", + "test_go1.8_4.a", "test_go1.8_5.a", + "test_go1.11_6b.a", "test_go1.11_999b.a": + continue + } + // fall through + } + // ok to fail if it fails with a newer version error for select files + if strings.Contains(err.Error(), "newer version") { + switch name { + case "test_go1.11_999i.a": + continue + } + // fall through + } + t.Errorf("import %q failed: %v", pkgpath, err) + continue + } + + // create file with corrupted export data + // 1) read file + data, err := os.ReadFile(filepath.Join(dir, name)) + if err != nil { + t.Fatal(err) + } + // 2) find export data + i := bytes.Index(data, []byte("\n$$B\n")) + 5 + j := bytes.Index(data[i:], []byte("\n$$\n")) + i + if i < 0 || j < 0 || i > j { + t.Fatalf("export data section not found (i = %d, j = %d)", i, j) + } + // 3) corrupt the data (increment every 7th byte) + for k := j - 13; k >= i; k -= 7 { + data[k]++ + } + // 4) write the file + pkgpath += "_corrupted" + filename := filepath.Join(corruptdir, pkgpath) + ".a" + os.WriteFile(filename, data, 0666) + + // test that importing the corrupted file results in an error + _, err = Import(fset, make(map[string]*types.Package), pkgpath, corruptdir, nil) + if err == nil { + t.Errorf("import corrupted %q succeeded", pkgpath) + } else if msg := err.Error(); !strings.Contains(msg, "version skew") { + t.Errorf("import %q error incorrect (%s)", pkgpath, msg) + } + } +} + +func TestImportStdLib(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + dt := maxTime + if testing.Short() && testenv.Builder() == "" { + dt = 10 * time.Millisecond + } + nimports := testDir(t, "", time.Now().Add(dt)) // installed packages + t.Logf("tested %d imports", nimports) +} + +var importedObjectTests = []struct { + name string + want string +}{ + // non-interfaces + {"crypto.Hash", "type Hash uint"}, + {"go/ast.ObjKind", "type ObjKind int"}, + {"go/types.Qualifier", "type Qualifier func(*Package) string"}, + {"go/types.Comparable", "func Comparable(T Type) bool"}, + {"math.Pi", "const Pi untyped float"}, + {"math.Sin", "func Sin(x float64) float64"}, + {"go/ast.NotNilFilter", "func NotNilFilter(_ string, v reflect.Value) bool"}, + {"go/internal/gcimporter.FindPkg", "func FindPkg(path string, srcDir string) (filename string, id string)"}, + + // interfaces + {"context.Context", "type Context interface{Deadline() (deadline time.Time, ok bool); Done() <-chan struct{}; Err() error; Value(key interface{}) interface{}}"}, + {"crypto.Decrypter", "type Decrypter interface{Decrypt(rand io.Reader, msg []byte, opts DecrypterOpts) (plaintext []byte, err error); Public() PublicKey}"}, + {"encoding.BinaryMarshaler", "type BinaryMarshaler interface{MarshalBinary() (data []byte, err error)}"}, + {"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"}, + {"io.ReadWriter", "type ReadWriter interface{Reader; Writer}"}, + {"go/ast.Node", "type Node interface{End() go/token.Pos; Pos() go/token.Pos}"}, + {"go/types.Type", "type Type interface{String() string; Underlying() Type}"}, +} + +func TestImportedTypes(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + fset := token.NewFileSet() + 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 := Import(fset, make(map[string]*types.Package), importPath, ".", nil) + if err != nil { + t.Error(err) + continue + } + + obj := pkg.Scope().Lookup(objName) + if obj == nil { + t.Errorf("%s: object not found", test.name) + continue + } + + got := types.ObjectString(obj, types.RelativeTo(pkg)) + if got != test.want { + t.Errorf("%s: got %q; want %q", test.name, got, test.want) + } + + if named, _ := obj.Type().(*types.Named); named != nil { + verifyInterfaceMethodRecvs(t, named, 0) + } + } +} + +// verifyInterfaceMethodRecvs verifies that method receiver types +// are named if the methods belong to a named interface type. +func verifyInterfaceMethodRecvs(t *testing.T, named *types.Named, level int) { + // avoid endless recursion in case of an embedding bug that lead to a cycle + if level > 10 { + t.Errorf("%s: embeds itself", named) + return + } + + iface, _ := named.Underlying().(*types.Interface) + if iface == nil { + return // not an interface + } + + // check explicitly declared methods + for i := 0; i < iface.NumExplicitMethods(); i++ { + m := iface.ExplicitMethod(i) + recv := m.Type().(*types.Signature).Recv() + if recv == nil { + t.Errorf("%s: missing receiver type", m) + continue + } + if recv.Type() != named { + t.Errorf("%s: got recv type %s; want %s", m, recv.Type(), named) + } + } + + // check embedded interfaces (if they are named, too) + for i := 0; i < iface.NumEmbeddeds(); i++ { + // embedding of interfaces cannot have cycles; recursion will terminate + if etype, _ := iface.EmbeddedType(i).(*types.Named); etype != nil { + verifyInterfaceMethodRecvs(t, etype, level+1) + } + } +} + +func TestIssue5815(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + pkg := importPkg(t, "strings", ".") + + scope := pkg.Scope() + for _, name := range scope.Names() { + obj := scope.Lookup(name) + if obj.Pkg() == nil { + t.Errorf("no pkg for %s", obj) + } + if tname, _ := obj.(*types.TypeName); tname != nil { + named := tname.Type().(*types.Named) + for i := 0; i < named.NumMethods(); i++ { + m := named.Method(i) + if m.Pkg() == nil { + t.Errorf("no pkg for %s", m) + } + } + } + } +} + +// Smoke test to ensure that imported methods get the correct package. +func TestCorrectMethodPackage(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + imports := make(map[string]*types.Package) + fset := token.NewFileSet() + _, err := Import(fset, imports, "net/http", ".", nil) + if err != nil { + t.Fatal(err) + } + + mutex := imports["sync"].Scope().Lookup("Mutex").(*types.TypeName).Type() + mset := types.NewMethodSet(types.NewPointer(mutex)) // methods of *sync.Mutex + sel := mset.Lookup(nil, "Lock") + lock := sel.Obj().(*types.Func) + if got, want := lock.Pkg().Path(), "sync"; got != want { + t.Errorf("got package path %q; want %q", got, want) + } +} + +func TestIssue13566(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + testoutdir := filepath.Join(tmpdir, "testdata") + + // b.go needs to be compiled from the output directory so that the compiler can + // find the compiled package a. We pass the full path to compile() so that we + // don't have to copy the file to that directory. + bpath, err := filepath.Abs(filepath.Join("testdata", "b.go")) + if err != nil { + t.Fatal(err) + } + compile(t, "testdata", "a.go", testoutdir) + compile(t, testoutdir, bpath, testoutdir) + + // import must succeed (test for issue at hand) + pkg := importPkg(t, "./testdata/b", tmpdir) + + // make sure all indirectly imported packages have names + for _, imp := range pkg.Imports() { + if imp.Name() == "" { + t.Errorf("no name for %s package", imp.Path()) + } + } +} + +func TestIssue13898(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // import go/internal/gcimporter which imports go/types partially + fset := token.NewFileSet() + imports := make(map[string]*types.Package) + _, err := Import(fset, imports, "go/internal/gcimporter", ".", nil) + if err != nil { + t.Fatal(err) + } + + // look for go/types package + var goTypesPkg *types.Package + for path, pkg := range imports { + if path == "go/types" { + goTypesPkg = pkg + break + } + } + if goTypesPkg == nil { + t.Fatal("go/types not found") + } + + // look for go/types.Object type + obj := lookupObj(t, goTypesPkg.Scope(), "Object") + typ, ok := obj.Type().(*types.Named) + if !ok { + t.Fatalf("go/types.Object type is %v; wanted named type", typ) + } + + // lookup go/types.Object.Pkg method + m, index, indirect := types.LookupFieldOrMethod(typ, false, nil, "Pkg") + if m == nil { + t.Fatalf("go/types.Object.Pkg not found (index = %v, indirect = %v)", index, indirect) + } + + // the method must belong to go/types + if m.Pkg().Path() != "go/types" { + t.Fatalf("found %v; want go/types", m.Pkg()) + } +} + +func TestIssue15517(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + + compile(t, "testdata", "p.go", filepath.Join(tmpdir, "testdata")) + + // Multiple imports of p must succeed without redeclaration errors. + // We use an import path that's not cleaned up so that the eventual + // file path for the package is different from the package path; this + // will expose the error if it is present. + // + // (Issue: Both the textual and the binary importer used the file path + // of the package to be imported as key into the shared packages map. + // However, the binary importer then used the package path to identify + // the imported package to mark it as complete; effectively marking the + // wrong package as complete. By using an "unclean" package path, the + // file and package path are different, exposing the problem if present. + // The same issue occurs with vendoring.) + imports := make(map[string]*types.Package) + fset := token.NewFileSet() + for i := 0; i < 3; i++ { + if _, err := Import(fset, imports, "./././testdata/p", tmpdir, nil); err != nil { + t.Fatal(err) + } + } +} + +func TestIssue15920(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + compileAndImportPkg(t, "issue15920") +} + +func TestIssue20046(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + // "./issue20046".V.M must exist + pkg := compileAndImportPkg(t, "issue20046") + obj := lookupObj(t, pkg.Scope(), "V") + if m, index, indirect := types.LookupFieldOrMethod(obj.Type(), false, nil, "M"); m == nil { + t.Fatalf("V.M not found (index = %v, indirect = %v)", index, indirect) + } +} +func TestIssue25301(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + compileAndImportPkg(t, "issue25301") +} + +func TestIssue25596(t *testing.T) { + skipSpecialPlatforms(t) + + // This package only handles gc export data. + if runtime.Compiler != "gc" { + t.Skipf("gc-built packages not available (compiler = %s)", runtime.Compiler) + } + + // On windows, we have to set the -D option for the compiler to avoid having a drive + // letter and an illegal ':' in the import path - just skip it (see also issue #3483). + if runtime.GOOS == "windows" { + t.Skip("avoid dealing with relative paths/drive letters on windows") + } + + compileAndImportPkg(t, "issue25596") +} + +func importPkg(t *testing.T, path, srcDir string) *types.Package { + fset := token.NewFileSet() + pkg, err := Import(fset, make(map[string]*types.Package), path, srcDir, nil) + if err != nil { + t.Fatal(err) + } + return pkg +} + +func compileAndImportPkg(t *testing.T, name string) *types.Package { + tmpdir := mktmpdir(t) + defer os.RemoveAll(tmpdir) + compile(t, "testdata", name+".go", filepath.Join(tmpdir, "testdata")) + return importPkg(t, "./testdata/"+name, tmpdir) +} + +func lookupObj(t *testing.T, scope *types.Scope, name string) types.Object { + if obj := scope.Lookup(name); obj != nil { + return obj + } + t.Fatalf("%s not found", name) + return nil +} diff --git a/src/go/internal/gcimporter/iimport.go b/src/go/internal/gcimporter/iimport.go new file mode 100644 index 0000000..76d47d0 --- /dev/null +++ b/src/go/internal/gcimporter/iimport.go @@ -0,0 +1,607 @@ +// Copyright 2018 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. + +// Indexed package import. +// See cmd/compile/internal/gc/iexport.go for the export data format. + +package gcimporter + +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "go/constant" + "go/token" + "go/types" + "io" + "math/big" + "sort" +) + +type intReader struct { + *bufio.Reader + path string +} + +func (r *intReader) int64() int64 { + i, err := binary.ReadVarint(r.Reader) + if err != nil { + errorf("import %q: read varint error: %v", r.path, err) + } + return i +} + +func (r *intReader) uint64() uint64 { + i, err := binary.ReadUvarint(r.Reader) + if err != nil { + errorf("import %q: read varint error: %v", r.path, err) + } + return i +} + +const predeclReserved = 32 + +type itag uint64 + +const ( + // Types + definedType itag = iota + pointerType + sliceType + arrayType + chanType + mapType + signatureType + structType + interfaceType +) + +// iImportData imports a package from the serialized package data +// and returns the number of bytes consumed and a reference to the package. +// If the export data version is not recognized or the format is otherwise +// compromised, an error is returned. +func iImportData(fset *token.FileSet, imports map[string]*types.Package, dataReader *bufio.Reader, path string) (pkg *types.Package, err error) { + const currentVersion = 1 + version := int64(-1) + defer func() { + if e := recover(); e != nil { + if version > currentVersion { + err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e) + } else { + err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e) + } + } + }() + + r := &intReader{dataReader, path} + + version = int64(r.uint64()) + switch version { + case currentVersion, 0: + default: + errorf("unknown iexport format version %d", version) + } + + sLen := int64(r.uint64()) + dLen := int64(r.uint64()) + + data := make([]byte, sLen+dLen) + if _, err := io.ReadFull(r, data); err != nil { + errorf("cannot read %d bytes of stringData and declData: %s", len(data), err) + } + stringData := data[:sLen] + declData := data[sLen:] + + p := iimporter{ + ipath: path, + version: int(version), + + stringData: stringData, + stringCache: make(map[uint64]string), + pkgCache: make(map[uint64]*types.Package), + + declData: declData, + pkgIndex: make(map[*types.Package]map[string]uint64), + typCache: make(map[uint64]types.Type), + + fake: fakeFileSet{ + fset: fset, + files: make(map[string]*token.File), + }, + } + + for i, pt := range predeclared { + p.typCache[uint64(i)] = pt + } + + pkgList := make([]*types.Package, r.uint64()) + for i := range pkgList { + pkgPathOff := r.uint64() + pkgPath := p.stringAt(pkgPathOff) + pkgName := p.stringAt(r.uint64()) + _ = r.uint64() // package height; unused by go/types + + if pkgPath == "" { + pkgPath = path + } + pkg := imports[pkgPath] + if pkg == nil { + pkg = types.NewPackage(pkgPath, pkgName) + imports[pkgPath] = pkg + } else if pkg.Name() != pkgName { + errorf("conflicting names %s and %s for package %q", pkg.Name(), pkgName, path) + } + + p.pkgCache[pkgPathOff] = pkg + + nameIndex := make(map[string]uint64) + for nSyms := r.uint64(); nSyms > 0; nSyms-- { + name := p.stringAt(r.uint64()) + nameIndex[name] = r.uint64() + } + + p.pkgIndex[pkg] = nameIndex + pkgList[i] = pkg + } + + localpkg := pkgList[0] + + names := make([]string, 0, len(p.pkgIndex[localpkg])) + for name := range p.pkgIndex[localpkg] { + names = append(names, name) + } + sort.Strings(names) + for _, name := range names { + p.doDecl(localpkg, name) + } + + for _, typ := range p.interfaceList { + typ.Complete() + } + + // record all referenced packages as imports + list := append(([]*types.Package)(nil), pkgList[1:]...) + sort.Sort(byPath(list)) + localpkg.SetImports(list) + + // package was imported completely and without errors + localpkg.MarkComplete() + return localpkg, nil +} + +type iimporter struct { + ipath string + version int + + stringData []byte + stringCache map[uint64]string + pkgCache map[uint64]*types.Package + + declData []byte + pkgIndex map[*types.Package]map[string]uint64 + typCache map[uint64]types.Type + + fake fakeFileSet + interfaceList []*types.Interface +} + +func (p *iimporter) doDecl(pkg *types.Package, name string) { + // See if we've already imported this declaration. + if obj := pkg.Scope().Lookup(name); obj != nil { + return + } + + off, ok := p.pkgIndex[pkg][name] + if !ok { + errorf("%v.%v not in index", pkg, name) + } + + r := &importReader{p: p, currPkg: pkg} + r.declReader.Reset(p.declData[off:]) + + r.obj(name) +} + +func (p *iimporter) stringAt(off uint64) string { + if s, ok := p.stringCache[off]; ok { + return s + } + + slen, n := binary.Uvarint(p.stringData[off:]) + if n <= 0 { + errorf("varint failed") + } + spos := off + uint64(n) + s := string(p.stringData[spos : spos+slen]) + p.stringCache[off] = s + return s +} + +func (p *iimporter) pkgAt(off uint64) *types.Package { + if pkg, ok := p.pkgCache[off]; ok { + return pkg + } + path := p.stringAt(off) + errorf("missing package %q in %q", path, p.ipath) + return nil +} + +func (p *iimporter) typAt(off uint64, base *types.Named) types.Type { + if t, ok := p.typCache[off]; ok && (base == nil || !isInterface(t)) { + return t + } + + if off < predeclReserved { + errorf("predeclared type missing from cache: %v", off) + } + + r := &importReader{p: p} + r.declReader.Reset(p.declData[off-predeclReserved:]) + t := r.doType(base) + + if base == nil || !isInterface(t) { + p.typCache[off] = t + } + return t +} + +type importReader struct { + p *iimporter + declReader bytes.Reader + currPkg *types.Package + prevFile string + prevLine int64 + prevColumn int64 +} + +func (r *importReader) obj(name string) { + tag := r.byte() + pos := r.pos() + + switch tag { + case 'A': + typ := r.typ() + + r.declare(types.NewTypeName(pos, r.currPkg, name, typ)) + + case 'C': + typ, val := r.value() + + r.declare(types.NewConst(pos, r.currPkg, name, typ, val)) + + case 'F': + sig := r.signature(nil) + + r.declare(types.NewFunc(pos, r.currPkg, name, sig)) + + case 'T': + // Types can be recursive. We need to setup a stub + // declaration before recursing. + obj := types.NewTypeName(pos, r.currPkg, name, nil) + named := types.NewNamed(obj, nil, nil) + r.declare(obj) + + underlying := r.p.typAt(r.uint64(), named).Underlying() + named.SetUnderlying(underlying) + + if !isInterface(underlying) { + for n := r.uint64(); n > 0; n-- { + mpos := r.pos() + mname := r.ident() + recv := r.param() + msig := r.signature(recv) + + named.AddMethod(types.NewFunc(mpos, r.currPkg, mname, msig)) + } + } + + case 'V': + typ := r.typ() + + r.declare(types.NewVar(pos, r.currPkg, name, typ)) + + default: + errorf("unexpected tag: %v", tag) + } +} + +func (r *importReader) declare(obj types.Object) { + obj.Pkg().Scope().Insert(obj) +} + +func (r *importReader) value() (typ types.Type, val constant.Value) { + typ = r.typ() + + switch b := typ.Underlying().(*types.Basic); b.Info() & types.IsConstType { + case types.IsBoolean: + val = constant.MakeBool(r.bool()) + + case types.IsString: + val = constant.MakeString(r.string()) + + case types.IsInteger: + var x big.Int + r.mpint(&x, b) + val = constant.Make(&x) + + case types.IsFloat: + val = r.mpfloat(b) + + case types.IsComplex: + re := r.mpfloat(b) + im := r.mpfloat(b) + val = constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) + + default: + errorf("unexpected type %v", typ) // panics + panic("unreachable") + } + + return +} + +func intSize(b *types.Basic) (signed bool, maxBytes uint) { + if (b.Info() & types.IsUntyped) != 0 { + return true, 64 + } + + switch b.Kind() { + case types.Float32, types.Complex64: + return true, 3 + case types.Float64, types.Complex128: + return true, 7 + } + + signed = (b.Info() & types.IsUnsigned) == 0 + switch b.Kind() { + case types.Int8, types.Uint8: + maxBytes = 1 + case types.Int16, types.Uint16: + maxBytes = 2 + case types.Int32, types.Uint32: + maxBytes = 4 + default: + maxBytes = 8 + } + + return +} + +func (r *importReader) mpint(x *big.Int, typ *types.Basic) { + signed, maxBytes := intSize(typ) + + maxSmall := 256 - maxBytes + if signed { + maxSmall = 256 - 2*maxBytes + } + if maxBytes == 1 { + maxSmall = 256 + } + + n, _ := r.declReader.ReadByte() + if uint(n) < maxSmall { + v := int64(n) + if signed { + v >>= 1 + if n&1 != 0 { + v = ^v + } + } + x.SetInt64(v) + return + } + + v := -n + if signed { + v = -(n &^ 1) >> 1 + } + if v < 1 || uint(v) > maxBytes { + errorf("weird decoding: %v, %v => %v", n, signed, v) + } + b := make([]byte, v) + io.ReadFull(&r.declReader, b) + x.SetBytes(b) + if signed && n&1 != 0 { + x.Neg(x) + } +} + +func (r *importReader) mpfloat(typ *types.Basic) constant.Value { + var mant big.Int + r.mpint(&mant, typ) + var f big.Float + f.SetInt(&mant) + if f.Sign() != 0 { + f.SetMantExp(&f, int(r.int64())) + } + return constant.Make(&f) +} + +func (r *importReader) ident() string { + return r.string() +} + +func (r *importReader) qualifiedIdent() (*types.Package, string) { + name := r.string() + pkg := r.pkg() + return pkg, name +} + +func (r *importReader) pos() token.Pos { + if r.p.version >= 1 { + r.posv1() + } else { + r.posv0() + } + + if r.prevFile == "" && r.prevLine == 0 && r.prevColumn == 0 { + return token.NoPos + } + return r.p.fake.pos(r.prevFile, int(r.prevLine), int(r.prevColumn)) +} + +func (r *importReader) posv0() { + delta := r.int64() + if delta != deltaNewFile { + r.prevLine += delta + } else if l := r.int64(); l == -1 { + r.prevLine += deltaNewFile + } else { + r.prevFile = r.string() + r.prevLine = l + } +} + +func (r *importReader) posv1() { + delta := r.int64() + r.prevColumn += delta >> 1 + if delta&1 != 0 { + delta = r.int64() + r.prevLine += delta >> 1 + if delta&1 != 0 { + r.prevFile = r.string() + } + } +} + +func (r *importReader) typ() types.Type { + return r.p.typAt(r.uint64(), nil) +} + +func isInterface(t types.Type) bool { + _, ok := t.(*types.Interface) + return ok +} + +func (r *importReader) pkg() *types.Package { return r.p.pkgAt(r.uint64()) } +func (r *importReader) string() string { return r.p.stringAt(r.uint64()) } + +func (r *importReader) doType(base *types.Named) types.Type { + switch k := r.kind(); k { + default: + errorf("unexpected kind tag in %q: %v", r.p.ipath, k) + return nil + + case definedType: + pkg, name := r.qualifiedIdent() + r.p.doDecl(pkg, name) + return pkg.Scope().Lookup(name).(*types.TypeName).Type() + case pointerType: + return types.NewPointer(r.typ()) + case sliceType: + return types.NewSlice(r.typ()) + case arrayType: + n := r.uint64() + return types.NewArray(r.typ(), int64(n)) + case chanType: + dir := chanDir(int(r.uint64())) + return types.NewChan(dir, r.typ()) + case mapType: + return types.NewMap(r.typ(), r.typ()) + case signatureType: + r.currPkg = r.pkg() + return r.signature(nil) + + case structType: + r.currPkg = r.pkg() + + fields := make([]*types.Var, r.uint64()) + tags := make([]string, len(fields)) + for i := range fields { + fpos := r.pos() + fname := r.ident() + ftyp := r.typ() + emb := r.bool() + tag := r.string() + + fields[i] = types.NewField(fpos, r.currPkg, fname, ftyp, emb) + tags[i] = tag + } + return types.NewStruct(fields, tags) + + case interfaceType: + r.currPkg = r.pkg() + + embeddeds := make([]types.Type, r.uint64()) + for i := range embeddeds { + _ = r.pos() + embeddeds[i] = r.typ() + } + + methods := make([]*types.Func, r.uint64()) + for i := range methods { + mpos := r.pos() + mname := r.ident() + + // TODO(mdempsky): Matches bimport.go, but I + // don't agree with this. + var recv *types.Var + if base != nil { + recv = types.NewVar(token.NoPos, r.currPkg, "", base) + } + + msig := r.signature(recv) + methods[i] = types.NewFunc(mpos, r.currPkg, mname, msig) + } + + typ := types.NewInterfaceType(methods, embeddeds) + r.p.interfaceList = append(r.p.interfaceList, typ) + return typ + } +} + +func (r *importReader) kind() itag { + return itag(r.uint64()) +} + +func (r *importReader) signature(recv *types.Var) *types.Signature { + params := r.paramList() + results := r.paramList() + variadic := params.Len() > 0 && r.bool() + return types.NewSignature(recv, params, results, variadic) +} + +func (r *importReader) paramList() *types.Tuple { + xs := make([]*types.Var, r.uint64()) + for i := range xs { + xs[i] = r.param() + } + return types.NewTuple(xs...) +} + +func (r *importReader) param() *types.Var { + pos := r.pos() + name := r.ident() + typ := r.typ() + return types.NewParam(pos, r.currPkg, name, typ) +} + +func (r *importReader) bool() bool { + return r.uint64() != 0 +} + +func (r *importReader) int64() int64 { + n, err := binary.ReadVarint(&r.declReader) + if err != nil { + errorf("readVarint: %v", err) + } + return n +} + +func (r *importReader) uint64() uint64 { + n, err := binary.ReadUvarint(&r.declReader) + if err != nil { + errorf("readUvarint: %v", err) + } + return n +} + +func (r *importReader) byte() byte { + x, err := r.declReader.ReadByte() + if err != nil { + errorf("declReader.ReadByte: %v", err) + } + return x +} diff --git a/src/go/internal/gcimporter/support.go b/src/go/internal/gcimporter/support.go new file mode 100644 index 0000000..b8bb14d --- /dev/null +++ b/src/go/internal/gcimporter/support.go @@ -0,0 +1,130 @@ +// Copyright 2015 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 support functionality for iimport.go. + +package gcimporter + +import ( + "fmt" + "go/token" + "go/types" + "sync" +) + +func errorf(format string, args ...interface{}) { + panic(fmt.Sprintf(format, args...)) +} + +// deltaNewFile is a magic line delta offset indicating a new file. +// We use -64 because it is rare; see issue 20080 and CL 41619. +// -64 is the smallest int that fits in a single byte as a varint. +const deltaNewFile = -64 + +// Synthesize a token.Pos +type fakeFileSet struct { + fset *token.FileSet + files map[string]*token.File +} + +func (s *fakeFileSet) pos(file string, line, column int) token.Pos { + // TODO(mdempsky): Make use of column. + + // Since we don't know the set of needed file positions, we + // reserve maxlines positions per file. + const maxlines = 64 * 1024 + f := s.files[file] + if f == nil { + f = s.fset.AddFile(file, -1, maxlines) + s.files[file] = f + // Allocate the fake linebreak indices on first use. + // TODO(adonovan): opt: save ~512KB using a more complex scheme? + fakeLinesOnce.Do(func() { + fakeLines = make([]int, maxlines) + for i := range fakeLines { + fakeLines[i] = i + } + }) + f.SetLines(fakeLines) + } + + if line > maxlines { + line = 1 + } + + // Treat the file as if it contained only newlines + // and column=1: use the line number as the offset. + return f.Pos(line - 1) +} + +var ( + fakeLines []int + fakeLinesOnce sync.Once +) + +func chanDir(d int) types.ChanDir { + // tag values must match the constants in cmd/compile/internal/gc/go.go + switch d { + case 1 /* Crecv */ : + return types.RecvOnly + case 2 /* Csend */ : + return types.SendOnly + case 3 /* Cboth */ : + return types.SendRecv + default: + errorf("unexpected channel dir %d", d) + return 0 + } +} + +var predeclared = []types.Type{ + // basic types + types.Typ[types.Bool], + types.Typ[types.Int], + types.Typ[types.Int8], + types.Typ[types.Int16], + types.Typ[types.Int32], + types.Typ[types.Int64], + types.Typ[types.Uint], + types.Typ[types.Uint8], + types.Typ[types.Uint16], + types.Typ[types.Uint32], + types.Typ[types.Uint64], + types.Typ[types.Uintptr], + types.Typ[types.Float32], + types.Typ[types.Float64], + types.Typ[types.Complex64], + types.Typ[types.Complex128], + types.Typ[types.String], + + // basic type aliases + types.Universe.Lookup("byte").Type(), + types.Universe.Lookup("rune").Type(), + + // error + types.Universe.Lookup("error").Type(), + + // untyped types + types.Typ[types.UntypedBool], + types.Typ[types.UntypedInt], + types.Typ[types.UntypedRune], + types.Typ[types.UntypedFloat], + types.Typ[types.UntypedComplex], + types.Typ[types.UntypedString], + types.Typ[types.UntypedNil], + + // package unsafe + types.Typ[types.UnsafePointer], + + // invalid type + types.Typ[types.Invalid], // only appears in packages with errors + + // used internally by gc; never used by this package or in .a files + anyType{}, +} + +type anyType struct{} + +func (t anyType) Underlying() types.Type { return t } +func (t anyType) String() string { return "any" } diff --git a/src/go/internal/gcimporter/testdata/a.go b/src/go/internal/gcimporter/testdata/a.go new file mode 100644 index 0000000..56e4292 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/a.go @@ -0,0 +1,14 @@ +// Copyright 2016 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. + +// Input for TestIssue13566 + +package a + +import "encoding/json" + +type A struct { + a *A + json json.RawMessage +} diff --git a/src/go/internal/gcimporter/testdata/b.go b/src/go/internal/gcimporter/testdata/b.go new file mode 100644 index 0000000..4196678 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/b.go @@ -0,0 +1,11 @@ +// Copyright 2016 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. + +// Input for TestIssue13566 + +package b + +import "./a" + +type A a.A diff --git a/src/go/internal/gcimporter/testdata/exports.go b/src/go/internal/gcimporter/testdata/exports.go new file mode 100644 index 0000000..8ba3242 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/exports.go @@ -0,0 +1,88 @@ +// 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 + V2 float32 = 1.2 +) + +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() diff --git a/src/go/internal/gcimporter/testdata/issue15920.go b/src/go/internal/gcimporter/testdata/issue15920.go new file mode 100644 index 0000000..c70f7d8 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/issue15920.go @@ -0,0 +1,11 @@ +// Copyright 2016 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 p + +// The underlying type of Error is the underlying type of error. +// Make sure we can import this again without problems. +type Error error + +func F() Error { return nil } diff --git a/src/go/internal/gcimporter/testdata/issue20046.go b/src/go/internal/gcimporter/testdata/issue20046.go new file mode 100644 index 0000000..c63ee82 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/issue20046.go @@ -0,0 +1,9 @@ +// Copyright 2017 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 p + +var V interface { + M() +} diff --git a/src/go/internal/gcimporter/testdata/issue25301.go b/src/go/internal/gcimporter/testdata/issue25301.go new file mode 100644 index 0000000..e3dc98b --- /dev/null +++ b/src/go/internal/gcimporter/testdata/issue25301.go @@ -0,0 +1,17 @@ +// Copyright 2018 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 issue25301 + +type ( + A = interface { + M() + } + T interface { + A + } + S struct{} +) + +func (S) M() { println("m") } diff --git a/src/go/internal/gcimporter/testdata/issue25596.go b/src/go/internal/gcimporter/testdata/issue25596.go new file mode 100644 index 0000000..8923373 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/issue25596.go @@ -0,0 +1,13 @@ +// Copyright 2018 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 issue25596 + +type E interface { + M() T +} + +type T interface { + E +} diff --git a/src/go/internal/gcimporter/testdata/p.go b/src/go/internal/gcimporter/testdata/p.go new file mode 100644 index 0000000..9e2e705 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/p.go @@ -0,0 +1,13 @@ +// Copyright 2016 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. + +// Input for TestIssue15517 + +package p + +const C = 0 + +var V int + +func F() {} diff --git a/src/go/internal/gcimporter/testdata/versions/test.go b/src/go/internal/gcimporter/testdata/versions/test.go new file mode 100644 index 0000000..227fc09 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/versions/test.go @@ -0,0 +1,28 @@ +// Copyright 2016 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. + +// To create a test case for a new export format version, +// build this package with the latest compiler and store +// the resulting .a file appropriately named in the versions +// directory. The VersionHandling test will pick it up. +// +// In the testdata/versions: +// +// go build -o test_go1.$X_$Y.a test.go +// +// with $X = Go version and $Y = export format version +// (add 'b' or 'i' to distinguish between binary and +// indexed format starting with 1.11 as long as both +// formats are supported). +// +// Make sure this source is extended such that it exercises +// whatever export format change has taken place. + +package test + +// Any release before and including Go 1.7 didn't encode +// the package for a blank struct field. +type BlankField struct { + _ int +} diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.11_0i.a b/src/go/internal/gcimporter/testdata/versions/test_go1.11_0i.a Binary files differnew file mode 100644 index 0000000..b00fefe --- /dev/null +++ b/src/go/internal/gcimporter/testdata/versions/test_go1.11_0i.a diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.11_6b.a b/src/go/internal/gcimporter/testdata/versions/test_go1.11_6b.a Binary files differnew file mode 100644 index 0000000..c0a211e --- /dev/null +++ b/src/go/internal/gcimporter/testdata/versions/test_go1.11_6b.a diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.11_999b.a b/src/go/internal/gcimporter/testdata/versions/test_go1.11_999b.a Binary files differnew file mode 100644 index 0000000..c35d22d --- /dev/null +++ b/src/go/internal/gcimporter/testdata/versions/test_go1.11_999b.a diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.11_999i.a b/src/go/internal/gcimporter/testdata/versions/test_go1.11_999i.a Binary files differnew file mode 100644 index 0000000..99401d7 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/versions/test_go1.11_999i.a diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.7_0.a b/src/go/internal/gcimporter/testdata/versions/test_go1.7_0.a Binary files differnew file mode 100644 index 0000000..edb6c3f --- /dev/null +++ b/src/go/internal/gcimporter/testdata/versions/test_go1.7_0.a diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.7_1.a b/src/go/internal/gcimporter/testdata/versions/test_go1.7_1.a Binary files differnew file mode 100644 index 0000000..554d04a --- /dev/null +++ b/src/go/internal/gcimporter/testdata/versions/test_go1.7_1.a diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.8_4.a b/src/go/internal/gcimporter/testdata/versions/test_go1.8_4.a Binary files differnew file mode 100644 index 0000000..26b8531 --- /dev/null +++ b/src/go/internal/gcimporter/testdata/versions/test_go1.8_4.a diff --git a/src/go/internal/gcimporter/testdata/versions/test_go1.8_5.a b/src/go/internal/gcimporter/testdata/versions/test_go1.8_5.a Binary files differnew file mode 100644 index 0000000..60e52ef --- /dev/null +++ b/src/go/internal/gcimporter/testdata/versions/test_go1.8_5.a diff --git a/src/go/internal/srcimporter/srcimporter.go b/src/go/internal/srcimporter/srcimporter.go new file mode 100644 index 0000000..e4225eb --- /dev/null +++ b/src/go/internal/srcimporter/srcimporter.go @@ -0,0 +1,265 @@ +// Copyright 2017 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 srcimporter implements importing directly +// from source files rather than installed packages. +package srcimporter // import "go/internal/srcimporter" + +import ( + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "go/types" + exec "internal/execabs" + "io" + "os" + "path/filepath" + "strings" + "sync" + _ "unsafe" // for go:linkname +) + +// An Importer provides the context for importing packages from source code. +type Importer struct { + ctxt *build.Context + fset *token.FileSet + sizes types.Sizes + packages map[string]*types.Package +} + +// New returns a new Importer for the given context, file set, and map +// of packages. The context is used to resolve import paths to package paths, +// and identifying the files belonging to the package. If the context provides +// non-nil file system functions, they are used instead of the regular package +// os functions. The file set is used to track position information of package +// files; and imported packages are added to the packages map. +func New(ctxt *build.Context, fset *token.FileSet, packages map[string]*types.Package) *Importer { + return &Importer{ + ctxt: ctxt, + fset: fset, + sizes: types.SizesFor(ctxt.Compiler, ctxt.GOARCH), // uses go/types default if GOARCH not found + packages: packages, + } +} + +// Importing is a sentinel taking the place in Importer.packages +// for a package that is in the process of being imported. +var importing types.Package + +// Import(path) is a shortcut for ImportFrom(path, ".", 0). +func (p *Importer) Import(path string) (*types.Package, error) { + return p.ImportFrom(path, ".", 0) // use "." rather than "" (see issue #24441) +} + +// ImportFrom imports the package with the given import path resolved from the given srcDir, +// adds the new package to the set of packages maintained by the importer, and returns the +// package. Package path resolution and file system operations are controlled by the context +// maintained with the importer. The import mode must be zero but is otherwise ignored. +// Packages that are not comprised entirely of pure Go files may fail to import because the +// type checker may not be able to determine all exported entities (e.g. due to cgo dependencies). +func (p *Importer) ImportFrom(path, srcDir string, mode types.ImportMode) (*types.Package, error) { + if mode != 0 { + panic("non-zero import mode") + } + + if abs, err := p.absPath(srcDir); err == nil { // see issue #14282 + srcDir = abs + } + bp, err := p.ctxt.Import(path, srcDir, 0) + if err != nil { + return nil, err // err may be *build.NoGoError - return as is + } + + // package unsafe is known to the type checker + if bp.ImportPath == "unsafe" { + return types.Unsafe, nil + } + + // no need to re-import if the package was imported completely before + pkg := p.packages[bp.ImportPath] + if pkg != nil { + if pkg == &importing { + return nil, fmt.Errorf("import cycle through package %q", bp.ImportPath) + } + if !pkg.Complete() { + // Package exists but is not complete - we cannot handle this + // at the moment since the source importer replaces the package + // wholesale rather than augmenting it (see #19337 for details). + // Return incomplete package with error (see #16088). + return pkg, fmt.Errorf("reimported partially imported package %q", bp.ImportPath) + } + return pkg, nil + } + + p.packages[bp.ImportPath] = &importing + defer func() { + // clean up in case of error + // TODO(gri) Eventually we may want to leave a (possibly empty) + // package in the map in all cases (and use that package to + // identify cycles). See also issue 16088. + if p.packages[bp.ImportPath] == &importing { + p.packages[bp.ImportPath] = nil + } + }() + + var filenames []string + filenames = append(filenames, bp.GoFiles...) + filenames = append(filenames, bp.CgoFiles...) + + files, err := p.parseFiles(bp.Dir, filenames) + if err != nil { + return nil, err + } + + // type-check package files + var firstHardErr error + conf := types.Config{ + IgnoreFuncBodies: true, + // continue type-checking after the first error + Error: func(err error) { + if firstHardErr == nil && !err.(types.Error).Soft { + firstHardErr = err + } + }, + Importer: p, + Sizes: p.sizes, + } + if len(bp.CgoFiles) > 0 { + if p.ctxt.OpenFile != nil { + // cgo, gcc, pkg-config, etc. do not support + // build.Context's VFS. + conf.FakeImportC = true + } else { + setUsesCgo(&conf) + file, err := p.cgo(bp) + if err != nil { + return nil, err + } + files = append(files, file) + } + } + + pkg, err = conf.Check(bp.ImportPath, p.fset, files, nil) + if err != nil { + // If there was a hard error it is possibly unsafe + // to use the package as it may not be fully populated. + // Do not return it (see also #20837, #20855). + if firstHardErr != nil { + pkg = nil + err = firstHardErr // give preference to first hard error over any soft error + } + return pkg, fmt.Errorf("type-checking package %q failed (%v)", bp.ImportPath, err) + } + if firstHardErr != nil { + // this can only happen if we have a bug in go/types + panic("package is not safe yet no error was returned") + } + + p.packages[bp.ImportPath] = pkg + return pkg, nil +} + +func (p *Importer) parseFiles(dir string, filenames []string) ([]*ast.File, error) { + // use build.Context's OpenFile if there is one + open := p.ctxt.OpenFile + if open == nil { + open = func(name string) (io.ReadCloser, error) { return os.Open(name) } + } + + files := make([]*ast.File, len(filenames)) + errors := make([]error, len(filenames)) + + var wg sync.WaitGroup + wg.Add(len(filenames)) + for i, filename := range filenames { + go func(i int, filepath string) { + defer wg.Done() + src, err := open(filepath) + if err != nil { + errors[i] = err // open provides operation and filename in error + return + } + files[i], errors[i] = parser.ParseFile(p.fset, filepath, src, 0) + src.Close() // ignore Close error - parsing may have succeeded which is all we need + }(i, p.joinPath(dir, filename)) + } + wg.Wait() + + // if there are errors, return the first one for deterministic results + for _, err := range errors { + if err != nil { + return nil, err + } + } + + return files, nil +} + +func (p *Importer) cgo(bp *build.Package) (*ast.File, error) { + tmpdir, err := os.MkdirTemp("", "srcimporter") + if err != nil { + return nil, err + } + defer os.RemoveAll(tmpdir) + + args := []string{"go", "tool", "cgo", "-objdir", tmpdir} + if bp.Goroot { + switch bp.ImportPath { + case "runtime/cgo": + args = append(args, "-import_runtime_cgo=false", "-import_syscall=false") + case "runtime/race": + args = append(args, "-import_syscall=false") + } + } + args = append(args, "--") + args = append(args, strings.Fields(os.Getenv("CGO_CPPFLAGS"))...) + args = append(args, bp.CgoCPPFLAGS...) + if len(bp.CgoPkgConfig) > 0 { + cmd := exec.Command("pkg-config", append([]string{"--cflags"}, bp.CgoPkgConfig...)...) + out, err := cmd.CombinedOutput() + if err != nil { + return nil, err + } + args = append(args, strings.Fields(string(out))...) + } + args = append(args, "-I", tmpdir) + args = append(args, strings.Fields(os.Getenv("CGO_CFLAGS"))...) + args = append(args, bp.CgoCFLAGS...) + args = append(args, bp.CgoFiles...) + + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = bp.Dir + if err := cmd.Run(); err != nil { + return nil, err + } + + return parser.ParseFile(p.fset, filepath.Join(tmpdir, "_cgo_gotypes.go"), nil, 0) +} + +// context-controlled file system operations + +func (p *Importer) absPath(path string) (string, error) { + // TODO(gri) This should be using p.ctxt.AbsPath which doesn't + // exist but probably should. See also issue #14282. + return filepath.Abs(path) +} + +func (p *Importer) isAbsPath(path string) bool { + if f := p.ctxt.IsAbsPath; f != nil { + return f(path) + } + return filepath.IsAbs(path) +} + +func (p *Importer) joinPath(elem ...string) string { + if f := p.ctxt.JoinPath; f != nil { + return f(elem...) + } + return filepath.Join(elem...) +} + +//go:linkname setUsesCgo go/types.srcimporter_setUsesCgo +func setUsesCgo(conf *types.Config) diff --git a/src/go/internal/srcimporter/srcimporter_test.go b/src/go/internal/srcimporter/srcimporter_test.go new file mode 100644 index 0000000..05b12f1 --- /dev/null +++ b/src/go/internal/srcimporter/srcimporter_test.go @@ -0,0 +1,254 @@ +// Copyright 2017 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 srcimporter + +import ( + "flag" + "go/build" + "go/token" + "go/types" + "internal/testenv" + "os" + "path" + "path/filepath" + "runtime" + "strings" + "testing" + "time" +) + +func TestMain(m *testing.M) { + flag.Parse() + if goTool, err := testenv.GoTool(); err == nil { + os.Setenv("PATH", filepath.Dir(goTool)+string(os.PathListSeparator)+os.Getenv("PATH")) + } + os.Exit(m.Run()) +} + +const maxTime = 2 * time.Second + +var importer = New(&build.Default, token.NewFileSet(), make(map[string]*types.Package)) + +func doImport(t *testing.T, path, srcDir string) { + t0 := time.Now() + if _, err := importer.ImportFrom(path, srcDir, 0); err != nil { + // don't report an error if there's no buildable Go files + if _, nogo := err.(*build.NoGoError); !nogo { + t.Errorf("import %q failed (%v)", path, err) + } + return + } + t.Logf("import %q: %v", path, time.Since(t0)) +} + +// walkDir imports the all the packages with the given path +// prefix recursively. It returns the number of packages +// imported and whether importing was aborted because time +// has passed endTime. +func walkDir(t *testing.T, path string, endTime time.Time) (int, bool) { + if time.Now().After(endTime) { + t.Log("testing time used up") + return 0, true + } + + // ignore fake packages and testdata directories + if path == "builtin" || path == "unsafe" || strings.HasSuffix(path, "testdata") { + return 0, false + } + + list, err := os.ReadDir(filepath.Join(runtime.GOROOT(), "src", path)) + if err != nil { + t.Fatalf("walkDir %s failed (%v)", path, err) + } + + nimports := 0 + hasGoFiles := false + for _, f := range list { + if f.IsDir() { + n, abort := walkDir(t, filepath.Join(path, f.Name()), endTime) + nimports += n + if abort { + return nimports, true + } + } else if strings.HasSuffix(f.Name(), ".go") { + hasGoFiles = true + } + } + + if hasGoFiles { + doImport(t, path, "") + nimports++ + } + + return nimports, false +} + +func TestImportStdLib(t *testing.T) { + if !testenv.HasSrc() { + t.Skip("no source code available") + } + + if testing.Short() && testenv.Builder() == "" { + t.Skip("skipping in -short mode") + } + dt := maxTime + nimports, _ := walkDir(t, "", time.Now().Add(dt)) // installed packages + t.Logf("tested %d imports", nimports) +} + +var importedObjectTests = []struct { + name string + want string +}{ + {"flag.Bool", "func Bool(name string, value bool, usage string) *bool"}, + {"io.Reader", "type Reader interface{Read(p []byte) (n int, err error)}"}, + {"io.ReadWriter", "type ReadWriter interface{Reader; Writer}"}, // go/types.gcCompatibilityMode is off => interface not flattened + {"math.Pi", "const Pi untyped float"}, + {"math.Sin", "func Sin(x float64) float64"}, + {"math/big.Int", "type Int struct{neg bool; abs nat}"}, + {"golang.org/x/text/unicode/norm.MaxSegmentSize", "const MaxSegmentSize untyped int"}, +} + +func TestImportedTypes(t *testing.T) { + if !testenv.HasSrc() { + t.Skip("no source code available") + } + + for _, test := range importedObjectTests { + i := strings.LastIndex(test.name, ".") + if i < 0 { + t.Fatal("invalid test data format") + } + importPath := test.name[:i] + objName := test.name[i+1:] + + pkg, err := importer.ImportFrom(importPath, ".", 0) + if err != nil { + t.Error(err) + continue + } + + obj := pkg.Scope().Lookup(objName) + if obj == nil { + t.Errorf("%s: object not found", test.name) + continue + } + + got := types.ObjectString(obj, types.RelativeTo(pkg)) + if got != test.want { + t.Errorf("%s: got %q; want %q", test.name, got, test.want) + } + + if named, _ := obj.Type().(*types.Named); named != nil { + verifyInterfaceMethodRecvs(t, named, 0) + } + } +} + +// verifyInterfaceMethodRecvs verifies that method receiver types +// are named if the methods belong to a named interface type. +func verifyInterfaceMethodRecvs(t *testing.T, named *types.Named, level int) { + // avoid endless recursion in case of an embedding bug that lead to a cycle + if level > 10 { + t.Errorf("%s: embeds itself", named) + return + } + + iface, _ := named.Underlying().(*types.Interface) + if iface == nil { + return // not an interface + } + + // check explicitly declared methods + for i := 0; i < iface.NumExplicitMethods(); i++ { + m := iface.ExplicitMethod(i) + recv := m.Type().(*types.Signature).Recv() + if recv == nil { + t.Errorf("%s: missing receiver type", m) + continue + } + if recv.Type() != named { + t.Errorf("%s: got recv type %s; want %s", m, recv.Type(), named) + } + } + + // check embedded interfaces (they are named, too) + for i := 0; i < iface.NumEmbeddeds(); i++ { + // embedding of interfaces cannot have cycles; recursion will terminate + verifyInterfaceMethodRecvs(t, iface.Embedded(i), level+1) + } +} + +func TestReimport(t *testing.T) { + if !testenv.HasSrc() { + t.Skip("no source code available") + } + + // Reimporting a partially imported (incomplete) package is not supported (see issue #19337). + // Make sure we recognize the situation and report an error. + + mathPkg := types.NewPackage("math", "math") // incomplete package + importer := New(&build.Default, token.NewFileSet(), map[string]*types.Package{mathPkg.Path(): mathPkg}) + _, err := importer.ImportFrom("math", ".", 0) + if err == nil || !strings.HasPrefix(err.Error(), "reimport") { + t.Errorf("got %v; want reimport error", err) + } +} + +func TestIssue20855(t *testing.T) { + if !testenv.HasSrc() { + t.Skip("no source code available") + } + + pkg, err := importer.ImportFrom("go/internal/srcimporter/testdata/issue20855", ".", 0) + if err == nil || !strings.Contains(err.Error(), "missing function body") { + t.Fatalf("got unexpected or no error: %v", err) + } + if pkg == nil { + t.Error("got no package despite no hard errors") + } +} + +func testImportPath(t *testing.T, pkgPath string) { + if !testenv.HasSrc() { + t.Skip("no source code available") + } + + pkgName := path.Base(pkgPath) + + pkg, err := importer.Import(pkgPath) + if err != nil { + t.Fatal(err) + } + + if pkg.Name() != pkgName { + t.Errorf("got %q; want %q", pkg.Name(), pkgName) + } + + if pkg.Path() != pkgPath { + t.Errorf("got %q; want %q", pkg.Path(), pkgPath) + } +} + +// TestIssue23092 tests relative imports. +func TestIssue23092(t *testing.T) { + testImportPath(t, "./testdata/issue23092") +} + +// TestIssue24392 tests imports against a path containing 'testdata'. +func TestIssue24392(t *testing.T) { + testImportPath(t, "go/internal/srcimporter/testdata/issue24392") +} + +func TestCgo(t *testing.T) { + testenv.MustHaveGoBuild(t) + testenv.MustHaveCGO(t) + + importer := New(&build.Default, token.NewFileSet(), make(map[string]*types.Package)) + _, err := importer.ImportFrom("./misc/cgo/test", runtime.GOROOT(), 0) + if err != nil { + t.Fatalf("Import failed: %v", err) + } +} diff --git a/src/go/internal/srcimporter/testdata/issue20855/issue20855.go b/src/go/internal/srcimporter/testdata/issue20855/issue20855.go new file mode 100644 index 0000000..d55448b --- /dev/null +++ b/src/go/internal/srcimporter/testdata/issue20855/issue20855.go @@ -0,0 +1,7 @@ +// Copyright 2017 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 issue20855 + +func init() // "missing function body" is a soft error diff --git a/src/go/internal/srcimporter/testdata/issue23092/issue23092.go b/src/go/internal/srcimporter/testdata/issue23092/issue23092.go new file mode 100644 index 0000000..608698b --- /dev/null +++ b/src/go/internal/srcimporter/testdata/issue23092/issue23092.go @@ -0,0 +1,5 @@ +// Copyright 2018 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 issue23092 diff --git a/src/go/internal/srcimporter/testdata/issue24392/issue24392.go b/src/go/internal/srcimporter/testdata/issue24392/issue24392.go new file mode 100644 index 0000000..8ad5221 --- /dev/null +++ b/src/go/internal/srcimporter/testdata/issue24392/issue24392.go @@ -0,0 +1,5 @@ +// Copyright 2018 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 issue24392 diff --git a/src/go/internal/typeparams/common.go b/src/go/internal/typeparams/common.go new file mode 100644 index 0000000..47b8f7c --- /dev/null +++ b/src/go/internal/typeparams/common.go @@ -0,0 +1,13 @@ +// Copyright 2021 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 typeparams provides functions to work with type parameter data +// stored in the AST, while these AST changes are guarded by a build +// constraint. +package typeparams + +// DisallowParsing is the numeric value of a parsing mode that disallows type +// parameters. This only matters if the typeparams experiment is active, and +// may be used for running tests that disallow generics. +const DisallowParsing = 1 << 30 diff --git a/src/go/internal/typeparams/notypeparams.go b/src/go/internal/typeparams/notypeparams.go new file mode 100644 index 0000000..2ceafaa --- /dev/null +++ b/src/go/internal/typeparams/notypeparams.go @@ -0,0 +1,40 @@ +// Copyright 2021 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. + +//go:build !typeparams +// +build !typeparams + +package typeparams + +import ( + "go/ast" +) + +const Enabled = false + +func PackExpr(list []ast.Expr) ast.Expr { + switch len(list) { + case 1: + return list[0] + default: + // The parser should not attempt to pack multiple expressions into an + // IndexExpr if type params are disabled. + panic("multiple index expressions are unsupported without type params") + } +} + +func UnpackExpr(expr ast.Expr) []ast.Expr { + return []ast.Expr{expr} +} + +func IsListExpr(n ast.Node) bool { + return false +} + +func Get(ast.Node) *ast.FieldList { + return nil +} + +func Set(node ast.Node, params *ast.FieldList) { +} diff --git a/src/go/internal/typeparams/typeparams.go b/src/go/internal/typeparams/typeparams.go new file mode 100644 index 0000000..871e95d --- /dev/null +++ b/src/go/internal/typeparams/typeparams.go @@ -0,0 +1,69 @@ +// Copyright 2021 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. + +//go:build typeparams +// +build typeparams + +package typeparams + +import ( + "fmt" + "go/ast" +) + +const Enabled = true + +func PackExpr(list []ast.Expr) ast.Expr { + switch len(list) { + case 0: + // Return an empty ListExpr here, rather than nil, as IndexExpr.Index must + // never be nil. + // TODO(rFindley) would a BadExpr be more appropriate here? + return &ast.ListExpr{} + case 1: + return list[0] + default: + return &ast.ListExpr{ElemList: list} + } +} + +// TODO(gri) Should find a more efficient solution that doesn't +// require introduction of a new slice for simple +// expressions. +func UnpackExpr(x ast.Expr) []ast.Expr { + if x, _ := x.(*ast.ListExpr); x != nil { + return x.ElemList + } + if x != nil { + return []ast.Expr{x} + } + return nil +} + +func IsListExpr(n ast.Node) bool { + _, ok := n.(*ast.ListExpr) + return ok +} + +func Get(n ast.Node) *ast.FieldList { + switch n := n.(type) { + case *ast.TypeSpec: + return n.TParams + case *ast.FuncType: + return n.TParams + default: + panic(fmt.Sprintf("node type %T has no type parameters", n)) + } +} + +func Set(n ast.Node, params *ast.FieldList) { + switch n := n.(type) { + case *ast.TypeSpec: + n.TParams = params + case *ast.FuncType: + n.TParams = params + default: + panic(fmt.Sprintf("node type %T has no type parameters", n)) + } +} |