diff options
Diffstat (limited to 'src/cmd/compile/internal/noder/import.go')
| -rw-r--r-- | src/cmd/compile/internal/noder/import.go | 480 |
1 files changed, 480 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/noder/import.go b/src/cmd/compile/internal/noder/import.go new file mode 100644 index 0000000000..747c30e6ff --- /dev/null +++ b/src/cmd/compile/internal/noder/import.go @@ -0,0 +1,480 @@ +// Copyright 2009 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 noder + +import ( + "errors" + "fmt" + "os" + pathpkg "path" + "runtime" + "sort" + "strconv" + "strings" + "unicode" + "unicode/utf8" + + "cmd/compile/internal/base" + "cmd/compile/internal/ir" + "cmd/compile/internal/syntax" + "cmd/compile/internal/typecheck" + "cmd/compile/internal/types" + "cmd/internal/archive" + "cmd/internal/bio" + "cmd/internal/goobj" + "cmd/internal/objabi" + "cmd/internal/src" +) + +func isDriveLetter(b byte) bool { + return 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z' +} + +// is this path a local name? begins with ./ or ../ or / +func islocalname(name string) bool { + return strings.HasPrefix(name, "/") || + runtime.GOOS == "windows" && len(name) >= 3 && isDriveLetter(name[0]) && name[1] == ':' && name[2] == '/' || + strings.HasPrefix(name, "./") || name == "." || + strings.HasPrefix(name, "../") || name == ".." +} + +func openPackage(path string) (*os.File, error) { + if islocalname(path) { + if base.Flag.NoLocalImports { + return nil, errors.New("local imports disallowed") + } + + if base.Flag.Cfg.PackageFile != nil { + return os.Open(base.Flag.Cfg.PackageFile[path]) + } + + // try .a before .o. important for building libraries: + // if there is an array.o in the array.a library, + // want to find all of array.a, not just array.o. + if file, err := os.Open(fmt.Sprintf("%s.a", path)); err == nil { + return file, nil + } + if file, err := os.Open(fmt.Sprintf("%s.o", path)); err == nil { + return file, nil + } + return nil, errors.New("file not found") + } + + // local imports should be canonicalized already. + // don't want to see "encoding/../encoding/base64" + // as different from "encoding/base64". + if q := pathpkg.Clean(path); q != path { + return nil, fmt.Errorf("non-canonical import path %q (should be %q)", path, q) + } + + if base.Flag.Cfg.PackageFile != nil { + return os.Open(base.Flag.Cfg.PackageFile[path]) + } + + for _, dir := range base.Flag.Cfg.ImportDirs { + if file, err := os.Open(fmt.Sprintf("%s/%s.a", dir, path)); err == nil { + return file, nil + } + if file, err := os.Open(fmt.Sprintf("%s/%s.o", dir, path)); err == nil { + return file, nil + } + } + + if objabi.GOROOT != "" { + suffix := "" + if base.Flag.InstallSuffix != "" { + suffix = "_" + base.Flag.InstallSuffix + } else if base.Flag.Race { + suffix = "_race" + } else if base.Flag.MSan { + suffix = "_msan" + } + + if file, err := os.Open(fmt.Sprintf("%s/pkg/%s_%s%s/%s.a", objabi.GOROOT, objabi.GOOS, objabi.GOARCH, suffix, path)); err == nil { + return file, nil + } + if file, err := os.Open(fmt.Sprintf("%s/pkg/%s_%s%s/%s.o", objabi.GOROOT, objabi.GOOS, objabi.GOARCH, suffix, path)); err == nil { + return file, nil + } + } + return nil, errors.New("file not found") +} + +// myheight tracks the local package's height based on packages +// imported so far. +var myheight int + +// resolveImportPath resolves an import path as it appears in a Go +// source file to the package's full path. +func resolveImportPath(path string) (string, error) { + // The package name main is no longer reserved, + // but we reserve the import path "main" to identify + // the main package, just as we reserve the import + // path "math" to identify the standard math package. + if path == "main" { + return "", errors.New("cannot import \"main\"") + } + + if base.Ctxt.Pkgpath != "" && path == base.Ctxt.Pkgpath { + return "", fmt.Errorf("import %q while compiling that package (import cycle)", path) + } + + if mapped, ok := base.Flag.Cfg.ImportMap[path]; ok { + path = mapped + } + + if islocalname(path) { + if path[0] == '/' { + return "", errors.New("import path cannot be absolute path") + } + + prefix := base.Flag.D + if prefix == "" { + // Questionable, but when -D isn't specified, historically we + // resolve local import paths relative to the directory the + // compiler's current directory, not the respective source + // file's directory. + prefix = base.Ctxt.Pathname + } + path = pathpkg.Join(prefix, path) + + if err := checkImportPath(path, true); err != nil { + return "", err + } + } + + return path, nil +} + +// TODO(mdempsky): Return an error instead. +func importfile(decl *syntax.ImportDecl) *types.Pkg { + if decl.Path.Kind != syntax.StringLit { + base.Errorf("import path must be a string") + return nil + } + + path, err := strconv.Unquote(decl.Path.Value) + if err != nil { + base.Errorf("import path must be a string") + return nil + } + + if err := checkImportPath(path, false); err != nil { + base.Errorf("%s", err.Error()) + return nil + } + + path, err = resolveImportPath(path) + if err != nil { + base.Errorf("%s", err) + return nil + } + + importpkg := types.NewPkg(path, "") + if importpkg.Direct { + return importpkg // already fully loaded + } + importpkg.Direct = true + typecheck.Target.Imports = append(typecheck.Target.Imports, importpkg) + + if path == "unsafe" { + return importpkg // initialized with universe + } + + f, err := openPackage(path) + if err != nil { + base.Errorf("could not import %q: %v", path, err) + base.ErrorExit() + } + imp := bio.NewReader(f) + defer imp.Close() + file := f.Name() + + // check object header + p, err := imp.ReadString('\n') + if err != nil { + base.Errorf("import %s: reading input: %v", file, err) + base.ErrorExit() + } + + if p == "!<arch>\n" { // package archive + // package export block should be first + sz := archive.ReadHeader(imp.Reader, "__.PKGDEF") + if sz <= 0 { + base.Errorf("import %s: not a package file", file) + base.ErrorExit() + } + p, err = imp.ReadString('\n') + if err != nil { + base.Errorf("import %s: reading input: %v", file, err) + base.ErrorExit() + } + } + + if !strings.HasPrefix(p, "go object ") { + base.Errorf("import %s: not a go object file: %s", file, p) + base.ErrorExit() + } + q := fmt.Sprintf("%s %s %s %s\n", objabi.GOOS, objabi.GOARCH, objabi.Version, objabi.Expstring()) + if p[10:] != q { + base.Errorf("import %s: object is [%s] expected [%s]", file, p[10:], q) + base.ErrorExit() + } + + // process header lines + for { + p, err = imp.ReadString('\n') + if err != nil { + base.Errorf("import %s: reading input: %v", file, err) + base.ErrorExit() + } + if p == "\n" { + break // header ends with blank line + } + } + + // Expect $$B\n to signal binary import format. + + // look for $$ + var c byte + for { + c, err = imp.ReadByte() + if err != nil { + break + } + if c == '$' { + c, err = imp.ReadByte() + if c == '$' || err != nil { + break + } + } + } + + // get character after $$ + if err == nil { + c, _ = imp.ReadByte() + } + + var fingerprint goobj.FingerprintType + switch c { + case '\n': + base.Errorf("cannot import %s: old export format no longer supported (recompile library)", path) + return nil + + case 'B': + if base.Debug.Export != 0 { + fmt.Printf("importing %s (%s)\n", path, file) + } + imp.ReadByte() // skip \n after $$B + + c, err = imp.ReadByte() + if err != nil { + base.Errorf("import %s: reading input: %v", file, err) + base.ErrorExit() + } + + // Indexed format is distinguished by an 'i' byte, + // whereas previous export formats started with 'c', 'd', or 'v'. + if c != 'i' { + base.Errorf("import %s: unexpected package format byte: %v", file, c) + base.ErrorExit() + } + fingerprint = typecheck.ReadImports(importpkg, imp) + + default: + base.Errorf("no import in %q", path) + base.ErrorExit() + } + + // assume files move (get installed) so don't record the full path + if base.Flag.Cfg.PackageFile != nil { + // If using a packageFile map, assume path_ can be recorded directly. + base.Ctxt.AddImport(path, fingerprint) + } else { + // For file "/Users/foo/go/pkg/darwin_amd64/math.a" record "math.a". + base.Ctxt.AddImport(file[len(file)-len(path)-len(".a"):], fingerprint) + } + + if importpkg.Height >= myheight { + myheight = importpkg.Height + 1 + } + + return importpkg +} + +// The linker uses the magic symbol prefixes "go." and "type." +// Avoid potential confusion between import paths and symbols +// by rejecting these reserved imports for now. Also, people +// "can do weird things in GOPATH and we'd prefer they didn't +// do _that_ weird thing" (per rsc). See also #4257. +var reservedimports = []string{ + "go", + "type", +} + +func checkImportPath(path string, allowSpace bool) error { + if path == "" { + return errors.New("import path is empty") + } + + if strings.Contains(path, "\x00") { + return errors.New("import path contains NUL") + } + + for _, ri := range reservedimports { + if path == ri { + return fmt.Errorf("import path %q is reserved and cannot be used", path) + } + } + + for _, r := range path { + switch { + case r == utf8.RuneError: + return fmt.Errorf("import path contains invalid UTF-8 sequence: %q", path) + case r < 0x20 || r == 0x7f: + return fmt.Errorf("import path contains control character: %q", path) + case r == '\\': + return fmt.Errorf("import path contains backslash; use slash: %q", path) + case !allowSpace && unicode.IsSpace(r): + return fmt.Errorf("import path contains space character: %q", path) + case strings.ContainsRune("!\"#$%&'()*,:;<=>?[]^`{|}", r): + return fmt.Errorf("import path contains invalid character '%c': %q", r, path) + } + } + + return nil +} + +func pkgnotused(lineno src.XPos, path string, name string) { + // If the package was imported with a name other than the final + // import path element, show it explicitly in the error message. + // Note that this handles both renamed imports and imports of + // packages containing unconventional package declarations. + // Note that this uses / always, even on Windows, because Go import + // paths always use forward slashes. + elem := path + if i := strings.LastIndex(elem, "/"); i >= 0 { + elem = elem[i+1:] + } + if name == "" || elem == name { + base.ErrorfAt(lineno, "imported and not used: %q", path) + } else { + base.ErrorfAt(lineno, "imported and not used: %q as %s", path, name) + } +} + +func mkpackage(pkgname string) { + if types.LocalPkg.Name == "" { + if pkgname == "_" { + base.Errorf("invalid package name _") + } + types.LocalPkg.Name = pkgname + } else { + if pkgname != types.LocalPkg.Name { + base.Errorf("package %s; expected %s", pkgname, types.LocalPkg.Name) + } + } +} + +func clearImports() { + type importedPkg struct { + pos src.XPos + path string + name string + } + var unused []importedPkg + + for _, s := range types.LocalPkg.Syms { + n := ir.AsNode(s.Def) + if n == nil { + continue + } + if n.Op() == ir.OPACK { + // throw away top-level package name left over + // from previous file. + // leave s->block set to cause redeclaration + // errors if a conflicting top-level name is + // introduced by a different file. + p := n.(*ir.PkgName) + if !p.Used && base.SyntaxErrors() == 0 { + unused = append(unused, importedPkg{p.Pos(), p.Pkg.Path, s.Name}) + } + s.Def = nil + continue + } + if types.IsDotAlias(s) { + // throw away top-level name left over + // from previous import . "x" + // We'll report errors after type checking in CheckDotImports. + s.Def = nil + continue + } + } + + sort.Slice(unused, func(i, j int) bool { return unused[i].pos.Before(unused[j].pos) }) + for _, pkg := range unused { + pkgnotused(pkg.pos, pkg.path, pkg.name) + } +} + +// CheckDotImports reports errors for any unused dot imports. +func CheckDotImports() { + for _, pack := range dotImports { + if !pack.Used { + base.ErrorfAt(pack.Pos(), "imported and not used: %q", pack.Pkg.Path) + } + } + + // No longer needed; release memory. + dotImports = nil + typecheck.DotImportRefs = nil +} + +// dotImports tracks all PkgNames that have been dot-imported. +var dotImports []*ir.PkgName + +// find all the exported symbols in package referenced by PkgName, +// and make them available in the current package +func importDot(pack *ir.PkgName) { + if typecheck.DotImportRefs == nil { + typecheck.DotImportRefs = make(map[*ir.Ident]*ir.PkgName) + } + + opkg := pack.Pkg + for _, s := range opkg.Syms { + if s.Def == nil { + if _, ok := typecheck.DeclImporter[s]; !ok { + continue + } + } + if !types.IsExported(s.Name) || strings.ContainsRune(s.Name, 0xb7) { // 0xb7 = center dot + continue + } + s1 := typecheck.Lookup(s.Name) + if s1.Def != nil { + pkgerror := fmt.Sprintf("during import %q", opkg.Path) + typecheck.Redeclared(base.Pos, s1, pkgerror) + continue + } + + id := ir.NewIdent(src.NoXPos, s) + typecheck.DotImportRefs[id] = pack + s1.Def = id + s1.Block = 1 + } + + dotImports = append(dotImports, pack) +} + +// importName is like oldname, +// but it reports an error if sym is from another package and not exported. +func importName(sym *types.Sym) ir.Node { + n := oldname(sym) + if !types.IsExported(sym.Name) && sym.Pkg != types.LocalPkg { + n.SetDiag(true) + base.Errorf("cannot refer to unexported name %s.%s", sym.Pkg.Name, sym.Name) + } + return n +} |
