1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Except for this comment and the import path, this file is a verbatim copy of the file
6// with the same name in $GOROOT/src/go/internal/gccgoimporter.
7
8// Package gccgoimporter implements Import for gccgo-generated object files.
9package gccgoimporter // import "golang.org/x/tools/go/internal/gccgoimporter"
10
11import (
12	"debug/elf"
13	"fmt"
14	"go/types"
15	"io"
16	"os"
17	"path/filepath"
18	"strings"
19)
20
21// A PackageInit describes an imported package that needs initialization.
22type PackageInit struct {
23	Name     string // short package name
24	InitFunc string // name of init function
25	Priority int    // priority of init function, see InitData.Priority
26}
27
28// The gccgo-specific init data for a package.
29type InitData struct {
30	// Initialization priority of this package relative to other packages.
31	// This is based on the maximum depth of the package's dependency graph;
32	// it is guaranteed to be greater than that of its dependencies.
33	Priority int
34
35	// The list of packages which this package depends on to be initialized,
36	// including itself if needed. This is the subset of the transitive closure of
37	// the package's dependencies that need initialization.
38	Inits []PackageInit
39}
40
41// Locate the file from which to read export data.
42// This is intended to replicate the logic in gofrontend.
43func findExportFile(searchpaths []string, pkgpath string) (string, error) {
44	for _, spath := range searchpaths {
45		pkgfullpath := filepath.Join(spath, pkgpath)
46		pkgdir, name := filepath.Split(pkgfullpath)
47
48		for _, filepath := range [...]string{
49			pkgfullpath,
50			pkgfullpath + ".gox",
51			pkgdir + "lib" + name + ".so",
52			pkgdir + "lib" + name + ".a",
53			pkgfullpath + ".o",
54		} {
55			fi, err := os.Stat(filepath)
56			if err == nil && !fi.IsDir() {
57				return filepath, nil
58			}
59		}
60	}
61
62	return "", fmt.Errorf("%s: could not find export data (tried %s)", pkgpath, strings.Join(searchpaths, ":"))
63}
64
65const (
66	gccgov1Magic    = "v1;\n"
67	gccgov2Magic    = "v2;\n"
68	goimporterMagic = "\n$$ "
69	archiveMagic    = "!<ar"
70)
71
72// Opens the export data file at the given path. If this is an ELF file,
73// searches for and opens the .go_export section. If this is an archive,
74// reads the export data from the first member, which is assumed to be an ELF file.
75// This is intended to replicate the logic in gofrontend.
76func openExportFile(fpath string) (reader io.ReadSeeker, closer io.Closer, err error) {
77	f, err := os.Open(fpath)
78	if err != nil {
79		return
80	}
81	closer = f
82	defer func() {
83		if err != nil && closer != nil {
84			f.Close()
85		}
86	}()
87
88	var magic [4]byte
89	_, err = f.ReadAt(magic[:], 0)
90	if err != nil {
91		return
92	}
93
94	var elfreader io.ReaderAt
95	switch string(magic[:]) {
96	case gccgov1Magic, gccgov2Magic, goimporterMagic:
97		// Raw export data.
98		reader = f
99		return
100
101	case archiveMagic:
102		reader, err = arExportData(f)
103		return
104
105	default:
106		elfreader = f
107	}
108
109	ef, err := elf.NewFile(elfreader)
110	if err != nil {
111		return
112	}
113
114	sec := ef.Section(".go_export")
115	if sec == nil {
116		err = fmt.Errorf("%s: .go_export section not found", fpath)
117		return
118	}
119
120	reader = sec.Open()
121	return
122}
123
124// An Importer resolves import paths to Packages. The imports map records
125// packages already known, indexed by package path.
126// An importer must determine the canonical package path and check imports
127// to see if it is already present in the map. If so, the Importer can return
128// the map entry. Otherwise, the importer must load the package data for the
129// given path into a new *Package, record it in imports map, and return the
130// package.
131type Importer func(imports map[string]*types.Package, path, srcDir string, lookup func(string) (io.ReadCloser, error)) (*types.Package, error)
132
133func GetImporter(searchpaths []string, initmap map[*types.Package]InitData) Importer {
134	return func(imports map[string]*types.Package, pkgpath, srcDir string, lookup func(string) (io.ReadCloser, error)) (pkg *types.Package, err error) {
135		// TODO(gri): Use srcDir.
136		// Or not. It's possible that srcDir will fade in importance as
137		// the go command and other tools provide a translation table
138		// for relative imports (like ./foo or vendored imports).
139		if pkgpath == "unsafe" {
140			return types.Unsafe, nil
141		}
142
143		var reader io.ReadSeeker
144		var fpath string
145		var rc io.ReadCloser
146		if lookup != nil {
147			if p := imports[pkgpath]; p != nil && p.Complete() {
148				return p, nil
149			}
150			rc, err = lookup(pkgpath)
151			if err != nil {
152				return nil, err
153			}
154		}
155		if rc != nil {
156			defer rc.Close()
157			rs, ok := rc.(io.ReadSeeker)
158			if !ok {
159				return nil, fmt.Errorf("gccgo importer requires lookup to return an io.ReadSeeker, have %T", rc)
160			}
161			reader = rs
162			fpath = "<lookup " + pkgpath + ">"
163			// Take name from Name method (like on os.File) if present.
164			if n, ok := rc.(interface{ Name() string }); ok {
165				fpath = n.Name()
166			}
167		} else {
168			fpath, err = findExportFile(searchpaths, pkgpath)
169			if err != nil {
170				return nil, err
171			}
172
173			r, closer, err := openExportFile(fpath)
174			if err != nil {
175				return nil, err
176			}
177			if closer != nil {
178				defer closer.Close()
179			}
180			reader = r
181		}
182
183		var magics string
184		magics, err = readMagic(reader)
185		if err != nil {
186			return
187		}
188
189		if magics == archiveMagic {
190			reader, err = arExportData(reader)
191			if err != nil {
192				return
193			}
194			magics, err = readMagic(reader)
195			if err != nil {
196				return
197			}
198		}
199
200		switch magics {
201		case gccgov1Magic, gccgov2Magic:
202			var p parser
203			p.init(fpath, reader, imports)
204			pkg = p.parsePackage()
205			if initmap != nil {
206				initmap[pkg] = p.initdata
207			}
208
209		// Excluded for now: Standard gccgo doesn't support this import format currently.
210		// case goimporterMagic:
211		// 	var data []byte
212		// 	data, err = ioutil.ReadAll(reader)
213		// 	if err != nil {
214		// 		return
215		// 	}
216		// 	var n int
217		// 	n, pkg, err = importer.ImportData(imports, data)
218		// 	if err != nil {
219		// 		return
220		// 	}
221
222		// 	if initmap != nil {
223		// 		suffixreader := bytes.NewReader(data[n:])
224		// 		var p parser
225		// 		p.init(fpath, suffixreader, nil)
226		// 		p.parseInitData()
227		// 		initmap[pkg] = p.initdata
228		// 	}
229
230		default:
231			err = fmt.Errorf("unrecognized magic string: %q", magics)
232		}
233
234		return
235	}
236}
237
238// readMagic reads the four bytes at the start of a ReadSeeker and
239// returns them as a string.
240func readMagic(reader io.ReadSeeker) (string, error) {
241	var magic [4]byte
242	if _, err := reader.Read(magic[:]); err != nil {
243		return "", err
244	}
245	if _, err := reader.Seek(0, io.SeekStart); err != nil {
246		return "", err
247	}
248	return string(magic[:]), nil
249}
250