// 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" "internal/buildcfg" "internal/exportdata" "internal/pkgbits" "os" pathpkg "path" "runtime" "strings" "unicode" "unicode/utf8" "cmd/compile/internal/base" "cmd/compile/internal/importer" "cmd/compile/internal/ir" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "cmd/compile/internal/types2" "cmd/internal/bio" "cmd/internal/goobj" "cmd/internal/objabi" ) type gcimports struct { ctxt *types2.Context packages map[string]*types2.Package } func (m *gcimports) Import(path string) (*types2.Package, error) { return m.ImportFrom(path, "" /* no vendoring */, 0) } func (m *gcimports) ImportFrom(path, srcDir string, mode types2.ImportMode) (*types2.Package, error) { if mode != 0 { panic("mode must be 0") } _, pkg, err := readImportFile(path, typecheck.Target, m.ctxt, m.packages) return pkg, err } 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 buildcfg.GOROOT != "" { suffix := "" if base.Flag.InstallSuffix != "" { suffix = "_" + base.Flag.InstallSuffix } else if base.Flag.Race { suffix = "_race" } else if base.Flag.MSan { suffix = "_msan" } else if base.Flag.ASan { suffix = "_asan" } if file, err := os.Open(fmt.Sprintf("%s/pkg/%s_%s%s/%s.a", buildcfg.GOROOT, buildcfg.GOOS, buildcfg.GOARCH, suffix, path)); err == nil { return file, nil } if file, err := os.Open(fmt.Sprintf("%s/pkg/%s_%s%s/%s.o", buildcfg.GOROOT, buildcfg.GOOS, buildcfg.GOARCH, suffix, path)); err == nil { return file, nil } } return nil, errors.New("file not found") } // 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 == "" { panic("missing pkgpath") } if 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 } // readImportFile reads the import file for the given package path and // returns its types.Pkg representation. If packages is non-nil, the // types2.Package representation is also returned. func readImportFile(path string, target *ir.Package, env *types2.Context, packages map[string]*types2.Package) (pkg1 *types.Pkg, pkg2 *types2.Package, err error) { path, err = resolveImportPath(path) if err != nil { return } if path == "unsafe" { pkg1, pkg2 = types.UnsafePkg, types2.Unsafe // TODO(mdempsky): Investigate if this actually matters. Why would // the linker or runtime care whether a package imported unsafe? if !pkg1.Direct { pkg1.Direct = true target.Imports = append(target.Imports, pkg1) } return } pkg1 = types.NewPkg(path, "") if packages != nil { pkg2 = packages[path] assert(pkg1.Direct == (pkg2 != nil && pkg2.Complete())) } if pkg1.Direct { return } pkg1.Direct = true target.Imports = append(target.Imports, pkg1) f, err := openPackage(path) if err != nil { return } defer f.Close() data, err := readExportData(f) if err != nil { return } if base.Debug.Export != 0 { fmt.Printf("importing %s (%s)\n", path, f.Name()) } pr := pkgbits.NewPkgDecoder(pkg1.Path, data) // Read package descriptors for both types2 and compiler backend. readPackage(newPkgReader(pr), pkg1, false) pkg2 = importer.ReadPackage(env, packages, pr) err = addFingerprint(path, data) return } // readExportData returns the contents of GC-created unified export data. func readExportData(f *os.File) (data string, err error) { r := bio.NewReader(f) sz, err := exportdata.FindPackageDefinition(r.Reader) if err != nil { return } end := r.Offset() + int64(sz) abihdr, _, err := exportdata.ReadObjectHeaders(r.Reader) if err != nil { return } if expect := objabi.HeaderString(); abihdr != expect { err = fmt.Errorf("object is [%s] expected [%s]", abihdr, expect) return } _, err = exportdata.ReadExportDataHeader(r.Reader) if err != nil { return } pos := r.Offset() // Map export data section (+ end-of-section marker) into memory // as a single large string. This reduces heap fragmentation and // allows returning individual substrings very efficiently. var mapped string mapped, err = base.MapFile(r.File(), pos, end-pos) if err != nil { return } // check for end-of-section marker "\n$$\n" and remove it const marker = "\n$$\n" var ok bool data, ok = strings.CutSuffix(mapped, marker) if !ok { cutoff := data // include last 10 bytes in error message if len(cutoff) >= 10 { cutoff = cutoff[len(cutoff)-10:] } err = fmt.Errorf("expected $$ marker, but found %q (recompile package)", cutoff) return } return } // addFingerprint reads the linker fingerprint included at the end of // the exportdata. func addFingerprint(path string, data string) error { var fingerprint goobj.FingerprintType pos := len(data) - len(fingerprint) if pos < 0 { return fmt.Errorf("missing linker fingerprint in exportdata, but found %q", data) } buf := []byte(data[pos:]) copy(fingerprint[:], buf) base.Ctxt.AddImport(path, fingerprint) return nil } 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 base.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 }