• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2009 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
5package noder
6
7import (
8	"errors"
9	"fmt"
10	"internal/buildcfg"
11	"internal/pkgbits"
12	"os"
13	pathpkg "path"
14	"runtime"
15	"strings"
16	"unicode"
17	"unicode/utf8"
18
19	"cmd/compile/internal/base"
20	"cmd/compile/internal/importer"
21	"cmd/compile/internal/ir"
22	"cmd/compile/internal/typecheck"
23	"cmd/compile/internal/types"
24	"cmd/compile/internal/types2"
25	"cmd/internal/archive"
26	"cmd/internal/bio"
27	"cmd/internal/goobj"
28	"cmd/internal/objabi"
29)
30
31type gcimports struct {
32	ctxt     *types2.Context
33	packages map[string]*types2.Package
34}
35
36func (m *gcimports) Import(path string) (*types2.Package, error) {
37	return m.ImportFrom(path, "" /* no vendoring */, 0)
38}
39
40func (m *gcimports) ImportFrom(path, srcDir string, mode types2.ImportMode) (*types2.Package, error) {
41	if mode != 0 {
42		panic("mode must be 0")
43	}
44
45	_, pkg, err := readImportFile(path, typecheck.Target, m.ctxt, m.packages)
46	return pkg, err
47}
48
49func isDriveLetter(b byte) bool {
50	return 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z'
51}
52
53// is this path a local name? begins with ./ or ../ or /
54func islocalname(name string) bool {
55	return strings.HasPrefix(name, "/") ||
56		runtime.GOOS == "windows" && len(name) >= 3 && isDriveLetter(name[0]) && name[1] == ':' && name[2] == '/' ||
57		strings.HasPrefix(name, "./") || name == "." ||
58		strings.HasPrefix(name, "../") || name == ".."
59}
60
61func openPackage(path string) (*os.File, error) {
62	if islocalname(path) {
63		if base.Flag.NoLocalImports {
64			return nil, errors.New("local imports disallowed")
65		}
66
67		if base.Flag.Cfg.PackageFile != nil {
68			return os.Open(base.Flag.Cfg.PackageFile[path])
69		}
70
71		// try .a before .o.  important for building libraries:
72		// if there is an array.o in the array.a library,
73		// want to find all of array.a, not just array.o.
74		if file, err := os.Open(fmt.Sprintf("%s.a", path)); err == nil {
75			return file, nil
76		}
77		if file, err := os.Open(fmt.Sprintf("%s.o", path)); err == nil {
78			return file, nil
79		}
80		return nil, errors.New("file not found")
81	}
82
83	// local imports should be canonicalized already.
84	// don't want to see "encoding/../encoding/base64"
85	// as different from "encoding/base64".
86	if q := pathpkg.Clean(path); q != path {
87		return nil, fmt.Errorf("non-canonical import path %q (should be %q)", path, q)
88	}
89
90	if base.Flag.Cfg.PackageFile != nil {
91		return os.Open(base.Flag.Cfg.PackageFile[path])
92	}
93
94	for _, dir := range base.Flag.Cfg.ImportDirs {
95		if file, err := os.Open(fmt.Sprintf("%s/%s.a", dir, path)); err == nil {
96			return file, nil
97		}
98		if file, err := os.Open(fmt.Sprintf("%s/%s.o", dir, path)); err == nil {
99			return file, nil
100		}
101	}
102
103	if buildcfg.GOROOT != "" {
104		suffix := ""
105		if base.Flag.InstallSuffix != "" {
106			suffix = "_" + base.Flag.InstallSuffix
107		} else if base.Flag.Race {
108			suffix = "_race"
109		} else if base.Flag.MSan {
110			suffix = "_msan"
111		} else if base.Flag.ASan {
112			suffix = "_asan"
113		}
114
115		if file, err := os.Open(fmt.Sprintf("%s/pkg/%s_%s%s/%s.a", buildcfg.GOROOT, buildcfg.GOOS, buildcfg.GOARCH, suffix, path)); err == nil {
116			return file, nil
117		}
118		if file, err := os.Open(fmt.Sprintf("%s/pkg/%s_%s%s/%s.o", buildcfg.GOROOT, buildcfg.GOOS, buildcfg.GOARCH, suffix, path)); err == nil {
119			return file, nil
120		}
121	}
122	return nil, errors.New("file not found")
123}
124
125// resolveImportPath resolves an import path as it appears in a Go
126// source file to the package's full path.
127func resolveImportPath(path string) (string, error) {
128	// The package name main is no longer reserved,
129	// but we reserve the import path "main" to identify
130	// the main package, just as we reserve the import
131	// path "math" to identify the standard math package.
132	if path == "main" {
133		return "", errors.New("cannot import \"main\"")
134	}
135
136	if base.Ctxt.Pkgpath == "" {
137		panic("missing pkgpath")
138	}
139	if path == base.Ctxt.Pkgpath {
140		return "", fmt.Errorf("import %q while compiling that package (import cycle)", path)
141	}
142
143	if mapped, ok := base.Flag.Cfg.ImportMap[path]; ok {
144		path = mapped
145	}
146
147	if islocalname(path) {
148		if path[0] == '/' {
149			return "", errors.New("import path cannot be absolute path")
150		}
151
152		prefix := base.Flag.D
153		if prefix == "" {
154			// Questionable, but when -D isn't specified, historically we
155			// resolve local import paths relative to the directory the
156			// compiler's current directory, not the respective source
157			// file's directory.
158			prefix = base.Ctxt.Pathname
159		}
160		path = pathpkg.Join(prefix, path)
161
162		if err := checkImportPath(path, true); err != nil {
163			return "", err
164		}
165	}
166
167	return path, nil
168}
169
170// readImportFile reads the import file for the given package path and
171// returns its types.Pkg representation. If packages is non-nil, the
172// types2.Package representation is also returned.
173func readImportFile(path string, target *ir.Package, env *types2.Context, packages map[string]*types2.Package) (pkg1 *types.Pkg, pkg2 *types2.Package, err error) {
174	path, err = resolveImportPath(path)
175	if err != nil {
176		return
177	}
178
179	if path == "unsafe" {
180		pkg1, pkg2 = types.UnsafePkg, types2.Unsafe
181
182		// TODO(mdempsky): Investigate if this actually matters. Why would
183		// the linker or runtime care whether a package imported unsafe?
184		if !pkg1.Direct {
185			pkg1.Direct = true
186			target.Imports = append(target.Imports, pkg1)
187		}
188
189		return
190	}
191
192	pkg1 = types.NewPkg(path, "")
193	if packages != nil {
194		pkg2 = packages[path]
195		assert(pkg1.Direct == (pkg2 != nil && pkg2.Complete()))
196	}
197
198	if pkg1.Direct {
199		return
200	}
201	pkg1.Direct = true
202	target.Imports = append(target.Imports, pkg1)
203
204	f, err := openPackage(path)
205	if err != nil {
206		return
207	}
208	defer f.Close()
209
210	r, end, err := findExportData(f)
211	if err != nil {
212		return
213	}
214
215	if base.Debug.Export != 0 {
216		fmt.Printf("importing %s (%s)\n", path, f.Name())
217	}
218
219	c, err := r.ReadByte()
220	if err != nil {
221		return
222	}
223
224	pos := r.Offset()
225
226	// Map export data section into memory as a single large
227	// string. This reduces heap fragmentation and allows returning
228	// individual substrings very efficiently.
229	var data string
230	data, err = base.MapFile(r.File(), pos, end-pos)
231	if err != nil {
232		return
233	}
234
235	switch c {
236	case 'u':
237		// TODO(mdempsky): This seems a bit clunky.
238		data = strings.TrimSuffix(data, "\n$$\n")
239
240		pr := pkgbits.NewPkgDecoder(pkg1.Path, data)
241
242		// Read package descriptors for both types2 and compiler backend.
243		readPackage(newPkgReader(pr), pkg1, false)
244		pkg2 = importer.ReadPackage(env, packages, pr)
245
246	default:
247		// Indexed format is distinguished by an 'i' byte,
248		// whereas previous export formats started with 'c', 'd', or 'v'.
249		err = fmt.Errorf("unexpected package format byte: %v", c)
250		return
251	}
252
253	err = addFingerprint(path, f, end)
254	return
255}
256
257// findExportData returns a *bio.Reader positioned at the start of the
258// binary export data section, and a file offset for where to stop
259// reading.
260func findExportData(f *os.File) (r *bio.Reader, end int64, err error) {
261	r = bio.NewReader(f)
262
263	// check object header
264	line, err := r.ReadString('\n')
265	if err != nil {
266		return
267	}
268
269	if line == "!<arch>\n" { // package archive
270		// package export block should be first
271		sz := int64(archive.ReadHeader(r.Reader, "__.PKGDEF"))
272		if sz <= 0 {
273			err = errors.New("not a package file")
274			return
275		}
276		end = r.Offset() + sz
277		line, err = r.ReadString('\n')
278		if err != nil {
279			return
280		}
281	} else {
282		// Not an archive; provide end of file instead.
283		// TODO(mdempsky): I don't think this happens anymore.
284		var fi os.FileInfo
285		fi, err = f.Stat()
286		if err != nil {
287			return
288		}
289		end = fi.Size()
290	}
291
292	if !strings.HasPrefix(line, "go object ") {
293		err = fmt.Errorf("not a go object file: %s", line)
294		return
295	}
296	if expect := objabi.HeaderString(); line != expect {
297		err = fmt.Errorf("object is [%s] expected [%s]", line, expect)
298		return
299	}
300
301	// process header lines
302	for !strings.HasPrefix(line, "$$") {
303		line, err = r.ReadString('\n')
304		if err != nil {
305			return
306		}
307	}
308
309	// Expect $$B\n to signal binary import format.
310	if line != "$$B\n" {
311		err = errors.New("old export format no longer supported (recompile library)")
312		return
313	}
314
315	return
316}
317
318// addFingerprint reads the linker fingerprint included at the end of
319// the exportdata.
320func addFingerprint(path string, f *os.File, end int64) error {
321	const eom = "\n$$\n"
322	var fingerprint goobj.FingerprintType
323
324	var buf [len(fingerprint) + len(eom)]byte
325	if _, err := f.ReadAt(buf[:], end-int64(len(buf))); err != nil {
326		return err
327	}
328
329	// Caller should have given us the end position of the export data,
330	// which should end with the "\n$$\n" marker. As a consistency check
331	// to make sure we're reading at the right offset, make sure we
332	// found the marker.
333	if s := string(buf[len(fingerprint):]); s != eom {
334		return fmt.Errorf("expected $$ marker, but found %q", s)
335	}
336
337	copy(fingerprint[:], buf[:])
338	base.Ctxt.AddImport(path, fingerprint)
339
340	return nil
341}
342
343func checkImportPath(path string, allowSpace bool) error {
344	if path == "" {
345		return errors.New("import path is empty")
346	}
347
348	if strings.Contains(path, "\x00") {
349		return errors.New("import path contains NUL")
350	}
351
352	for ri := range base.ReservedImports {
353		if path == ri {
354			return fmt.Errorf("import path %q is reserved and cannot be used", path)
355		}
356	}
357
358	for _, r := range path {
359		switch {
360		case r == utf8.RuneError:
361			return fmt.Errorf("import path contains invalid UTF-8 sequence: %q", path)
362		case r < 0x20 || r == 0x7f:
363			return fmt.Errorf("import path contains control character: %q", path)
364		case r == '\\':
365			return fmt.Errorf("import path contains backslash; use slash: %q", path)
366		case !allowSpace && unicode.IsSpace(r):
367			return fmt.Errorf("import path contains space character: %q", path)
368		case strings.ContainsRune("!\"#$%&'()*,:;<=>?[]^`{|}", r):
369			return fmt.Errorf("import path contains invalid character '%c': %q", r, path)
370		}
371	}
372
373	return nil
374}
375