Source file src/cmd/compile/internal/noder/import.go

     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  
     5  package noder
     6  
     7  import (
     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  
    31  type gcimports struct {
    32  	ctxt     *types2.Context
    33  	packages map[string]*types2.Package
    34  }
    35  
    36  func (m *gcimports) Import(path string) (*types2.Package, error) {
    37  	return m.ImportFrom(path, "" /* no vendoring */, 0)
    38  }
    39  
    40  func (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  
    49  func 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 /
    54  func 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  
    61  func 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.
   127  func 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.
   173  func 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.
   260  func 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  	// Is the first line an archive file signature?
   270  	if line != "!<arch>\n" {
   271  		err = fmt.Errorf("not the start of an archive file (%q)", line)
   272  		return
   273  	}
   274  
   275  	// package export block should be first
   276  	sz := int64(archive.ReadHeader(r.Reader, "__.PKGDEF"))
   277  	if sz <= 0 {
   278  		err = errors.New("not a package file")
   279  		return
   280  	}
   281  	end = r.Offset() + sz
   282  	line, err = r.ReadString('\n')
   283  	if err != nil {
   284  		return
   285  	}
   286  
   287  	if !strings.HasPrefix(line, "go object ") {
   288  		err = fmt.Errorf("not a go object file: %s", line)
   289  		return
   290  	}
   291  	if expect := objabi.HeaderString(); line != expect {
   292  		err = fmt.Errorf("object is [%s] expected [%s]", line, expect)
   293  		return
   294  	}
   295  
   296  	// process header lines
   297  	for !strings.HasPrefix(line, "$$") {
   298  		line, err = r.ReadString('\n')
   299  		if err != nil {
   300  			return
   301  		}
   302  	}
   303  
   304  	// Expect $$B\n to signal binary import format.
   305  	if line != "$$B\n" {
   306  		err = errors.New("old export format no longer supported (recompile package)")
   307  		return
   308  	}
   309  
   310  	return
   311  }
   312  
   313  // addFingerprint reads the linker fingerprint included at the end of
   314  // the exportdata.
   315  func addFingerprint(path string, f *os.File, end int64) error {
   316  	const eom = "\n$$\n"
   317  	var fingerprint goobj.FingerprintType
   318  
   319  	var buf [len(fingerprint) + len(eom)]byte
   320  	if _, err := f.ReadAt(buf[:], end-int64(len(buf))); err != nil {
   321  		return err
   322  	}
   323  
   324  	// Caller should have given us the end position of the export data,
   325  	// which should end with the "\n$$\n" marker. As a consistency check
   326  	// to make sure we're reading at the right offset, make sure we
   327  	// found the marker.
   328  	if s := string(buf[len(fingerprint):]); s != eom {
   329  		return fmt.Errorf("expected $$ marker, but found %q", s)
   330  	}
   331  
   332  	copy(fingerprint[:], buf[:])
   333  	base.Ctxt.AddImport(path, fingerprint)
   334  
   335  	return nil
   336  }
   337  
   338  func checkImportPath(path string, allowSpace bool) error {
   339  	if path == "" {
   340  		return errors.New("import path is empty")
   341  	}
   342  
   343  	if strings.Contains(path, "\x00") {
   344  		return errors.New("import path contains NUL")
   345  	}
   346  
   347  	for ri := range base.ReservedImports {
   348  		if path == ri {
   349  			return fmt.Errorf("import path %q is reserved and cannot be used", path)
   350  		}
   351  	}
   352  
   353  	for _, r := range path {
   354  		switch {
   355  		case r == utf8.RuneError:
   356  			return fmt.Errorf("import path contains invalid UTF-8 sequence: %q", path)
   357  		case r < 0x20 || r == 0x7f:
   358  			return fmt.Errorf("import path contains control character: %q", path)
   359  		case r == '\\':
   360  			return fmt.Errorf("import path contains backslash; use slash: %q", path)
   361  		case !allowSpace && unicode.IsSpace(r):
   362  			return fmt.Errorf("import path contains space character: %q", path)
   363  		case strings.ContainsRune("!\"#$%&'()*,:;<=>?[]^`{|}", r):
   364  			return fmt.Errorf("import path contains invalid character '%c': %q", r, path)
   365  		}
   366  	}
   367  
   368  	return nil
   369  }
   370  

View as plain text