diff --git a/go/gccgoexportdata/gccgoexportdata.go b/go/gccgoexportdata/gccgoexportdata.go new file mode 100644 index 00000000..d850f15a --- /dev/null +++ b/go/gccgoexportdata/gccgoexportdata.go @@ -0,0 +1,122 @@ +// 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 gccgoexportdata provides functions for reading export data +// files containing type information produced by the gccgo compiler. +// +// This package is a stop-gap until gccgo uses the same export data +// format as gc. Once that occurs, this package will be deprecated and +// eventually deleted. +package gccgoexportdata + +// TODO(adonovan): add Find, Write, Importer to the API, +// for symmetry with gcexportdata. + +import ( + "bytes" + "debug/elf" + "fmt" + "go/token" + "go/types" + "io" + "io/ioutil" + "strconv" + "strings" + + "golang.org/x/tools/go/internal/gccgoimporter" +) + +// CompilerInfo executes the specified gccgo compiler and returns +// information about it: its version (e.g. "4.8.0"), its target triple +// (e.g. "x86_64-unknown-linux-gnu"), and the list of directories it +// searches to find standard packages. +func CompilerInfo(gccgo string) (version, triple string, dirs []string, err error) { + var inst gccgoimporter.GccgoInstallation + err = inst.InitFromDriver(gccgo) + if err == nil { + version = inst.GccVersion + triple = inst.TargetTriple + dirs = inst.SearchPaths() + } + return +} + +// NewReader returns a reader for the export data section of an object +// (.o) or archive (.a) file read from r. +func NewReader(r io.Reader) (io.Reader, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + + // If the file is an archive, extract the first section. + const archiveMagic = "!\n" + if bytes.HasPrefix(data, []byte(archiveMagic)) { + section, err := firstSection(data[len(archiveMagic):]) + if err != nil { + return nil, err + } + data = section + } + + // Data contains an ELF file with a .go_export section. + // ELF magic number is "\x7fELF". + ef, err := elf.NewFile(bytes.NewReader(data)) + if err != nil { + return nil, err + } + sec := ef.Section(".go_export") + if sec == nil { + return nil, fmt.Errorf("no .go_export section") + } + return sec.Open(), nil +} + +// firstSection returns the contents of the first non-empty section of the archive file. +func firstSection(a []byte) ([]byte, error) { + for len(a) >= 60 { + var hdr []byte + hdr, a = a[:60], a[60:] + + modeStr := string(string(hdr[40:48])) + mode, err := strconv.Atoi(strings.TrimSpace(modeStr)) + if err != nil { + return nil, fmt.Errorf("invalid mode: %q", modeStr) + } + + sizeStr := string(hdr[48:58]) + size, err := strconv.Atoi(strings.TrimSpace(sizeStr)) + if err != nil { + return nil, fmt.Errorf("invalid size: %q", sizeStr) + } + + var payload []byte + payload, a = a[:size], a[size:] + + if mode == 0 { + continue // skip "/" + } + + return payload, nil + } + return nil, fmt.Errorf("archive has no non-empty sections") +} + +// Read reads export data from in, decodes it, and returns type +// information for the package. +// The package name is specified by path. +// +// The FileSet parameter is currently unused but exists for symmetry +// with gcexportdata. +// +// Read may inspect and add to the imports map to ensure that references +// within the export data to other packages are consistent. The caller +// must ensure that imports[path] does not exist, or exists but is +// incomplete (see types.Package.Complete), and Read inserts the +// resulting package into this map entry. +// +// On return, the state of the reader is undefined. +func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) { + return gccgoimporter.Parse(in, imports, path) +} diff --git a/go/gccgoexportdata/gccgoexportdata_test.go b/go/gccgoexportdata/gccgoexportdata_test.go new file mode 100644 index 00000000..131f4135 --- /dev/null +++ b/go/gccgoexportdata/gccgoexportdata_test.go @@ -0,0 +1,36 @@ +package gccgoexportdata_test + +import ( + "go/types" + "os" + "testing" + + "golang.org/x/tools/go/gccgoexportdata" +) + +// Test ensures this package can read gccgo export data from the +// .go_export section of an ELF file. +func Test(t *testing.T) { + f, err := os.Open("testdata/errors.gox") + if err != nil { + t.Fatal(err) + } + defer f.Close() + r, err := gccgoexportdata.NewReader(f) + if err != nil { + t.Fatal(err) + } + imports := make(map[string]*types.Package) + pkg, err := gccgoexportdata.Read(r, nil, imports, "errors") + if err != nil { + + t.Fatal(err) + } + + // Check type of errors.New. + got := pkg.Scope().Lookup("New").Type().String() + want := "func(text string) error" + if got != want { + t.Errorf("New.Type = %s, want %s", got, want) + } +} diff --git a/go/gccgoexportdata/testdata/errors.gox b/go/gccgoexportdata/testdata/errors.gox new file mode 100644 index 00000000..c6ff16bc Binary files /dev/null and b/go/gccgoexportdata/testdata/errors.gox differ diff --git a/go/internal/gccgoimporter/backdoor.go b/go/internal/gccgoimporter/backdoor.go new file mode 100644 index 00000000..4feb0dc6 --- /dev/null +++ b/go/internal/gccgoimporter/backdoor.go @@ -0,0 +1,28 @@ +package gccgoimporter + +// This file opens a back door to the parser for golang.org/x/tools/go/gccgoexportdata. + +import ( + "go/types" + "io" +) + +// Parse reads and parses gccgo export data from in and constructs a +// Package, inserting it into the imports map. +func Parse(in io.Reader, imports map[string]*types.Package, path string) (_ *types.Package, err error) { + var p parser + p.init(path, in, imports) + defer func() { + switch x := recover().(type) { + case nil: + // success + case importError: + err = x + default: + panic(x) // resume unexpected panic + } + }() + pkg := p.parsePackage() + imports[path] = pkg + return pkg, err +}