github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/noder/import.go (about)

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