src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

exportdata.go (12356B)


      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 }