exportdata.go raw

   1  // Copyright 2011 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  // This file should be kept in sync with $GOROOT/src/internal/exportdata/exportdata.go.
   6  // This file also additionally implements FindExportData for gcexportdata.NewReader.
   7  
   8  package gcimporter
   9  
  10  import (
  11  	"bufio"
  12  	"bytes"
  13  	"errors"
  14  	"fmt"
  15  	"go/build"
  16  	"io"
  17  	"os"
  18  	"os/exec"
  19  	"path/filepath"
  20  	"strings"
  21  	"sync"
  22  )
  23  
  24  // FindExportData positions the reader r at the beginning of the
  25  // export data section of an underlying cmd/compile created archive
  26  // file by reading from it. The reader must be positioned at the
  27  // start of the file before calling this function.
  28  // This returns the length of the export data in bytes.
  29  //
  30  // This function is needed by [gcexportdata.Read], which must
  31  // accept inputs produced by the last two releases of cmd/compile,
  32  // plus tip.
  33  func FindExportData(r *bufio.Reader) (size int64, err error) {
  34  	arsize, err := FindPackageDefinition(r)
  35  	if err != nil {
  36  		return
  37  	}
  38  	size = int64(arsize)
  39  
  40  	objapi, headers, err := ReadObjectHeaders(r)
  41  	if err != nil {
  42  		return
  43  	}
  44  	size -= int64(len(objapi))
  45  	for _, h := range headers {
  46  		size -= int64(len(h))
  47  	}
  48  
  49  	// Check for the binary export data section header "$$B\n".
  50  	// TODO(taking): Unify with ReadExportDataHeader so that it stops at the 'u' instead of reading
  51  	line, err := r.ReadSlice('\n')
  52  	if err != nil {
  53  		return
  54  	}
  55  	hdr := string(line)
  56  	if hdr != "$$B\n" {
  57  		err = fmt.Errorf("unknown export data header: %q", hdr)
  58  		return
  59  	}
  60  	size -= int64(len(hdr))
  61  
  62  	// For files with a binary export data header "$$B\n",
  63  	// these are always terminated by an end-of-section marker "\n$$\n".
  64  	// So the last bytes must always be this constant.
  65  	//
  66  	// The end-of-section marker is not a part of the export data itself.
  67  	// Do not include these in size.
  68  	//
  69  	// It would be nice to have sanity check that the final bytes after
  70  	// the export data are indeed the end-of-section marker. The split
  71  	// of gcexportdata.NewReader and gcexportdata.Read make checking this
  72  	// ugly so gcimporter gives up enforcing this. The compiler and go/types
  73  	// importer do enforce this, which seems good enough.
  74  	const endofsection = "\n$$\n"
  75  	size -= int64(len(endofsection))
  76  
  77  	if size < 0 {
  78  		err = fmt.Errorf("invalid size (%d) in the archive file: %d bytes remain without section headers (recompile package)", arsize, size)
  79  		return
  80  	}
  81  
  82  	return
  83  }
  84  
  85  // ReadUnified reads the contents of the unified export data from a reader r
  86  // that contains the contents of a GC-created archive file.
  87  //
  88  // On success, the reader will be positioned after the end-of-section marker "\n$$\n".
  89  //
  90  // Supported GC-created archive files have 4 layers of nesting:
  91  //   - An archive file containing a package definition file.
  92  //   - The package definition file contains headers followed by a data section.
  93  //     Headers are lines (≤ 4kb) that do not start with "$$".
  94  //   - The data section starts with "$$B\n" followed by export data followed
  95  //     by an end of section marker "\n$$\n". (The section start "$$\n" is no
  96  //     longer supported.)
  97  //   - The export data starts with a format byte ('u') followed by the <data> in
  98  //     the given format. (See ReadExportDataHeader for older formats.)
  99  //
 100  // Putting this together, the bytes in a GC-created archive files are expected
 101  // to look like the following.
 102  // See cmd/internal/archive for more details on ar file headers.
 103  //
 104  // | <!arch>\n             | ar file signature
 105  // | __.PKGDEF...size...\n | ar header for __.PKGDEF including size.
 106  // | go object <...>\n     | objabi header
 107  // | <optional headers>\n  | other headers such as build id
 108  // | $$B\n                 | binary format marker
 109  // | u<data>\n             | unified export <data>
 110  // | $$\n                  | end-of-section marker
 111  // | [optional padding]    | padding byte (0x0A) if size is odd
 112  // | [ar file header]      | other ar files
 113  // | [ar file data]        |
 114  func ReadUnified(r *bufio.Reader) (data []byte, err error) {
 115  	// We historically guaranteed headers at the default buffer size (4096) work.
 116  	// This ensures we can use ReadSlice throughout.
 117  	const minBufferSize = 4096
 118  	r = bufio.NewReaderSize(r, minBufferSize)
 119  
 120  	size, err := FindPackageDefinition(r)
 121  	if err != nil {
 122  		return
 123  	}
 124  	n := size
 125  
 126  	objapi, headers, err := ReadObjectHeaders(r)
 127  	if err != nil {
 128  		return
 129  	}
 130  	n -= len(objapi)
 131  	for _, h := range headers {
 132  		n -= len(h)
 133  	}
 134  
 135  	hdrlen, err := ReadExportDataHeader(r)
 136  	if err != nil {
 137  		return
 138  	}
 139  	n -= hdrlen
 140  
 141  	// size also includes the end of section marker. Remove that many bytes from the end.
 142  	const marker = "\n$$\n"
 143  	n -= len(marker)
 144  
 145  	if n < 0 {
 146  		err = fmt.Errorf("invalid size (%d) in the archive file: %d bytes remain without section headers (recompile package)", size, n)
 147  		return
 148  	}
 149  
 150  	// Read n bytes from buf.
 151  	data = make([]byte, n)
 152  	_, err = io.ReadFull(r, data)
 153  	if err != nil {
 154  		return
 155  	}
 156  
 157  	// Check for marker at the end.
 158  	var suffix [len(marker)]byte
 159  	_, err = io.ReadFull(r, suffix[:])
 160  	if err != nil {
 161  		return
 162  	}
 163  	if s := string(suffix[:]); s != marker {
 164  		err = fmt.Errorf("read %q instead of end-of-section marker (%q)", s, marker)
 165  		return
 166  	}
 167  
 168  	return
 169  }
 170  
 171  // FindPackageDefinition positions the reader r at the beginning of a package
 172  // definition file ("__.PKGDEF") within a GC-created archive by reading
 173  // from it, and returns the size of the package definition file in the archive.
 174  //
 175  // The reader must be positioned at the start of the archive file before calling
 176  // this function, and "__.PKGDEF" is assumed to be the first file in the archive.
 177  //
 178  // See cmd/internal/archive for details on the archive format.
 179  func FindPackageDefinition(r *bufio.Reader) (size int, err error) {
 180  	// Uses ReadSlice to limit risk of malformed inputs.
 181  
 182  	// Read first line to make sure this is an object file.
 183  	line, err := r.ReadSlice('\n')
 184  	if err != nil {
 185  		err = fmt.Errorf("can't find export data (%v)", err)
 186  		return
 187  	}
 188  
 189  	// Is the first line an archive file signature?
 190  	if string(line) != "!<arch>\n" {
 191  		err = fmt.Errorf("not the start of an archive file (%q)", line)
 192  		return
 193  	}
 194  
 195  	// package export block should be first
 196  	size = readArchiveHeader(r, "__.PKGDEF")
 197  	if size <= 0 {
 198  		err = fmt.Errorf("not a package file")
 199  		return
 200  	}
 201  
 202  	return
 203  }
 204  
 205  // ReadObjectHeaders reads object headers from the reader. Object headers are
 206  // lines that do not start with an end-of-section marker "$$". The first header
 207  // is the objabi header. On success, the reader will be positioned at the beginning
 208  // of the end-of-section marker.
 209  //
 210  // It returns an error if any header does not fit in r.Size() bytes.
 211  func ReadObjectHeaders(r *bufio.Reader) (objapi string, headers []string, err error) {
 212  	// line is a temporary buffer for headers.
 213  	// Use bounded reads (ReadSlice, Peek) to limit risk of malformed inputs.
 214  	var line []byte
 215  
 216  	// objapi header should be the first line
 217  	if line, err = r.ReadSlice('\n'); err != nil {
 218  		err = fmt.Errorf("can't find export data (%v)", err)
 219  		return
 220  	}
 221  	objapi = string(line)
 222  
 223  	// objapi header begins with "go object ".
 224  	if !strings.HasPrefix(objapi, "go object ") {
 225  		err = fmt.Errorf("not a go object file: %s", objapi)
 226  		return
 227  	}
 228  
 229  	// process remaining object header lines
 230  	for {
 231  		// check for an end of section marker "$$"
 232  		line, err = r.Peek(2)
 233  		if err != nil {
 234  			return
 235  		}
 236  		if string(line) == "$$" {
 237  			return // stop
 238  		}
 239  
 240  		// read next header
 241  		line, err = r.ReadSlice('\n')
 242  		if err != nil {
 243  			return
 244  		}
 245  		headers = append(headers, string(line))
 246  	}
 247  }
 248  
 249  // ReadExportDataHeader reads the export data header and format from r.
 250  // It returns the number of bytes read, or an error if the format is no longer
 251  // supported or it failed to read.
 252  //
 253  // The only currently supported format is binary export data in the
 254  // unified export format.
 255  func ReadExportDataHeader(r *bufio.Reader) (n int, err error) {
 256  	// Read export data header.
 257  	line, err := r.ReadSlice('\n')
 258  	if err != nil {
 259  		return
 260  	}
 261  
 262  	hdr := string(line)
 263  	switch hdr {
 264  	case "$$\n":
 265  		err = fmt.Errorf("old textual export format no longer supported (recompile package)")
 266  		return
 267  
 268  	case "$$B\n":
 269  		var format byte
 270  		format, err = r.ReadByte()
 271  		if err != nil {
 272  			return
 273  		}
 274  		// The unified export format starts with a 'u'.
 275  		switch format {
 276  		case 'u':
 277  		default:
 278  			// Older no longer supported export formats include:
 279  			// indexed export format which started with an 'i'; and
 280  			// the older binary export format which started with a 'c',
 281  			// 'd', or 'v' (from "version").
 282  			err = fmt.Errorf("binary export format %q is no longer supported (recompile package)", format)
 283  			return
 284  		}
 285  
 286  	default:
 287  		err = fmt.Errorf("unknown export data header: %q", hdr)
 288  		return
 289  	}
 290  
 291  	n = len(hdr) + 1 // + 1 is for 'u'
 292  	return
 293  }
 294  
 295  // FindPkg returns the filename and unique package id for an import
 296  // path based on package information provided by build.Import (using
 297  // the build.Default build.Context). A relative srcDir is interpreted
 298  // relative to the current working directory.
 299  //
 300  // FindPkg is only used in tests within x/tools.
 301  func FindPkg(path, srcDir string) (filename, id string, err error) {
 302  	// TODO(taking): Move internal/exportdata.FindPkg into its own file,
 303  	// and then this copy into a _test package.
 304  	if path == "" {
 305  		return "", "", errors.New("path is empty")
 306  	}
 307  
 308  	var noext string
 309  	switch {
 310  	default:
 311  		// "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
 312  		// Don't require the source files to be present.
 313  		if abs, err := filepath.Abs(srcDir); err == nil { // see issue 14282
 314  			srcDir = abs
 315  		}
 316  		var bp *build.Package
 317  		bp, err = build.Import(path, srcDir, build.FindOnly|build.AllowBinary)
 318  		if bp.PkgObj == "" {
 319  			if bp.Goroot && bp.Dir != "" {
 320  				filename, err = lookupGorootExport(bp.Dir)
 321  				if err == nil {
 322  					_, err = os.Stat(filename)
 323  				}
 324  				if err == nil {
 325  					return filename, bp.ImportPath, nil
 326  				}
 327  			}
 328  			goto notfound
 329  		} else {
 330  			noext = strings.TrimSuffix(bp.PkgObj, ".a")
 331  		}
 332  		id = bp.ImportPath
 333  
 334  	case build.IsLocalImport(path):
 335  		// "./x" -> "/this/directory/x.ext", "/this/directory/x"
 336  		noext = filepath.Join(srcDir, path)
 337  		id = noext
 338  
 339  	case filepath.IsAbs(path):
 340  		// for completeness only - go/build.Import
 341  		// does not support absolute imports
 342  		// "/x" -> "/x.ext", "/x"
 343  		noext = path
 344  		id = path
 345  	}
 346  
 347  	if false { // for debugging
 348  		if path != id {
 349  			fmt.Printf("%s -> %s\n", path, id)
 350  		}
 351  	}
 352  
 353  	// try extensions
 354  	for _, ext := range pkgExts {
 355  		filename = noext + ext
 356  		f, statErr := os.Stat(filename)
 357  		if statErr == nil && !f.IsDir() {
 358  			return filename, id, nil
 359  		}
 360  		if err == nil {
 361  			err = statErr
 362  		}
 363  	}
 364  
 365  notfound:
 366  	if err == nil {
 367  		return "", path, fmt.Errorf("can't find import: %q", path)
 368  	}
 369  	return "", path, fmt.Errorf("can't find import: %q: %w", path, err)
 370  }
 371  
 372  var pkgExts = [...]string{".a", ".o"} // a file from the build cache will have no extension
 373  
 374  var exportMap sync.Map // package dir → func() (string, error)
 375  
 376  // lookupGorootExport returns the location of the export data
 377  // (normally found in the build cache, but located in GOROOT/pkg
 378  // in prior Go releases) for the package located in pkgDir.
 379  //
 380  // (We use the package's directory instead of its import path
 381  // mainly to simplify handling of the packages in src/vendor
 382  // and cmd/vendor.)
 383  //
 384  // lookupGorootExport is only used in tests within x/tools.
 385  func lookupGorootExport(pkgDir string) (string, error) {
 386  	f, ok := exportMap.Load(pkgDir)
 387  	if !ok {
 388  		var (
 389  			listOnce   sync.Once
 390  			exportPath string
 391  			err        error
 392  		)
 393  		f, _ = exportMap.LoadOrStore(pkgDir, func() (string, error) {
 394  			listOnce.Do(func() {
 395  				cmd := exec.Command(filepath.Join(build.Default.GOROOT, "bin", "go"), "list", "-export", "-f", "{{.Export}}", pkgDir)
 396  				cmd.Dir = build.Default.GOROOT
 397  				cmd.Env = append(os.Environ(), "PWD="+cmd.Dir, "GOROOT="+build.Default.GOROOT)
 398  				var output []byte
 399  				output, err = cmd.Output()
 400  				if err != nil {
 401  					if ee, ok := err.(*exec.ExitError); ok && len(ee.Stderr) > 0 {
 402  						err = errors.New(string(ee.Stderr))
 403  					}
 404  					return
 405  				}
 406  
 407  				exports := strings.Split(string(bytes.TrimSpace(output)), "\n")
 408  				if len(exports) != 1 {
 409  					err = fmt.Errorf("go list reported %d exports; expected 1", len(exports))
 410  					return
 411  				}
 412  
 413  				exportPath = exports[0]
 414  			})
 415  
 416  			return exportPath, err
 417  		})
 418  	}
 419  
 420  	return f.(func() (string, error))()
 421  }
 422