Source File
exportdata.go
Belonging Package
golang.org/x/tools/internal/gcimporter
// Copyright 2011 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.
// This file should be kept in sync with $GOROOT/src/internal/exportdata/exportdata.go.
// This file also additionally implements FindExportData for gcexportdata.NewReader.
package gcimporter
import (
)
// FindExportData positions the reader r at the beginning of the
// export data section of an underlying cmd/compile created archive
// file by reading from it. The reader must be positioned at the
// start of the file before calling this function.
// This returns the length of the export data in bytes.
//
// This function is needed by [gcexportdata.Read], which must
// accept inputs produced by the last two releases of cmd/compile,
// plus tip.
func ( *bufio.Reader) ( int64, error) {
, := FindPackageDefinition()
if != nil {
return
}
= int64()
, , := ReadObjectHeaders()
if != nil {
return
}
-= int64(len())
for , := range {
-= int64(len())
}
// Check for the binary export data section header "$$B\n".
// TODO(taking): Unify with ReadExportDataHeader so that it stops at the 'u' instead of reading
, := .ReadSlice('\n')
if != nil {
return
}
:= string()
if != "$$B\n" {
= fmt.Errorf("unknown export data header: %q", )
return
}
-= int64(len())
// For files with a binary export data header "$$B\n",
// these are always terminated by an end-of-section marker "\n$$\n".
// So the last bytes must always be this constant.
//
// The end-of-section marker is not a part of the export data itself.
// Do not include these in size.
//
// It would be nice to have sanity check that the final bytes after
// the export data are indeed the end-of-section marker. The split
// of gcexportdata.NewReader and gcexportdata.Read make checking this
// ugly so gcimporter gives up enforcing this. The compiler and go/types
// importer do enforce this, which seems good enough.
const = "\n$$\n"
-= int64(len())
if < 0 {
= fmt.Errorf("invalid size (%d) in the archive file: %d bytes remain without section headers (recompile package)", , )
return
}
return
}
// ReadUnified reads the contents of the unified export data from a reader r
// that contains the contents of a GC-created archive file.
//
// On success, the reader will be positioned after the end-of-section marker "\n$$\n".
//
// Supported GC-created archive files have 4 layers of nesting:
// - An archive file containing a package definition file.
// - The package definition file contains headers followed by a data section.
// Headers are lines (≤ 4kb) that do not start with "$$".
// - The data section starts with "$$B\n" followed by export data followed
// by an end of section marker "\n$$\n". (The section start "$$\n" is no
// longer supported.)
// - The export data starts with a format byte ('u') followed by the <data> in
// the given format. (See ReadExportDataHeader for older formats.)
//
// Putting this together, the bytes in a GC-created archive files are expected
// to look like the following.
// See cmd/internal/archive for more details on ar file headers.
//
// | <!arch>\n | ar file signature
// | __.PKGDEF...size...\n | ar header for __.PKGDEF including size.
// | go object <...>\n | objabi header
// | <optional headers>\n | other headers such as build id
// | $$B\n | binary format marker
// | u<data>\n | unified export <data>
// | $$\n | end-of-section marker
// | [optional padding] | padding byte (0x0A) if size is odd
// | [ar file header] | other ar files
// | [ar file data] |
func ( *bufio.Reader) ( []byte, error) {
// We historically guaranteed headers at the default buffer size (4096) work.
// This ensures we can use ReadSlice throughout.
const = 4096
= bufio.NewReaderSize(, )
, := FindPackageDefinition()
if != nil {
return
}
:=
, , := ReadObjectHeaders()
if != nil {
return
}
-= len()
for , := range {
-= len()
}
, := ReadExportDataHeader()
if != nil {
return
}
-=
// size also includes the end of section marker. Remove that many bytes from the end.
const = "\n$$\n"
-= len()
if < 0 {
= fmt.Errorf("invalid size (%d) in the archive file: %d bytes remain without section headers (recompile package)", , )
return
}
// Read n bytes from buf.
= make([]byte, )
_, = io.ReadFull(, )
if != nil {
return
}
// Check for marker at the end.
var [len()]byte
_, = io.ReadFull(, [:])
if != nil {
return
}
if := string([:]); != {
= fmt.Errorf("read %q instead of end-of-section marker (%q)", , )
return
}
return
}
// FindPackageDefinition positions the reader r at the beginning of a package
// definition file ("__.PKGDEF") within a GC-created archive by reading
// from it, and returns the size of the package definition file in the archive.
//
// The reader must be positioned at the start of the archive file before calling
// this function, and "__.PKGDEF" is assumed to be the first file in the archive.
//
// See cmd/internal/archive for details on the archive format.
func ( *bufio.Reader) ( int, error) {
// Uses ReadSlice to limit risk of malformed inputs.
// Read first line to make sure this is an object file.
, := .ReadSlice('\n')
if != nil {
= fmt.Errorf("can't find export data (%v)", )
return
}
// Is the first line an archive file signature?
if string() != "!<arch>\n" {
= fmt.Errorf("not the start of an archive file (%q)", )
return
}
// package export block should be first
= readArchiveHeader(, "__.PKGDEF")
if <= 0 {
= fmt.Errorf("not a package file")
return
}
return
}
// ReadObjectHeaders reads object headers from the reader. Object headers are
// lines that do not start with an end-of-section marker "$$". The first header
// is the objabi header. On success, the reader will be positioned at the beginning
// of the end-of-section marker.
//
// It returns an error if any header does not fit in r.Size() bytes.
func ( *bufio.Reader) ( string, []string, error) {
// line is a temporary buffer for headers.
// Use bounded reads (ReadSlice, Peek) to limit risk of malformed inputs.
var []byte
// objapi header should be the first line
if , = .ReadSlice('\n'); != nil {
= fmt.Errorf("can't find export data (%v)", )
return
}
= string()
// objapi header begins with "go object ".
if !strings.HasPrefix(, "go object ") {
= fmt.Errorf("not a go object file: %s", )
return
}
// process remaining object header lines
for {
// check for an end of section marker "$$"
, = .Peek(2)
if != nil {
return
}
if string() == "$$" {
return // stop
}
// read next header
, = .ReadSlice('\n')
if != nil {
return
}
= append(, string())
}
}
// ReadExportDataHeader reads the export data header and format from r.
// It returns the number of bytes read, or an error if the format is no longer
// supported or it failed to read.
//
// The only currently supported format is binary export data in the
// unified export format.
func ( *bufio.Reader) ( int, error) {
// Read export data header.
, := .ReadSlice('\n')
if != nil {
return
}
:= string()
switch {
case "$$\n":
= fmt.Errorf("old textual export format no longer supported (recompile package)")
return
case "$$B\n":
var byte
, = .ReadByte()
if != nil {
return
}
// The unified export format starts with a 'u'.
switch {
case 'u':
default:
// Older no longer supported export formats include:
// indexed export format which started with an 'i'; and
// the older binary export format which started with a 'c',
// 'd', or 'v' (from "version").
= fmt.Errorf("binary export format %q is no longer supported (recompile package)", )
return
}
default:
= fmt.Errorf("unknown export data header: %q", )
return
}
= len() + 1 // + 1 is for 'u'
return
}
// FindPkg returns the filename and unique package id for an import
// path based on package information provided by build.Import (using
// the build.Default build.Context). A relative srcDir is interpreted
// relative to the current working directory.
//
// FindPkg is only used in tests within x/tools.
func (, string) (, string, error) {
// TODO(taking): Move internal/exportdata.FindPkg into its own file,
// and then this copy into a _test package.
if == "" {
return "", "", errors.New("path is empty")
}
var string
switch {
default:
// "x" -> "$GOPATH/pkg/$GOOS_$GOARCH/x.ext", "x"
// Don't require the source files to be present.
if , := filepath.Abs(); == nil { // see issue 14282
=
}
var *build.Package
, = build.Import(, , build.FindOnly|build.AllowBinary)
if .PkgObj == "" {
if .Goroot && .Dir != "" {
, = lookupGorootExport(.Dir)
if == nil {
_, = os.Stat()
}
if == nil {
return , .ImportPath, nil
}
}
goto
} else {
= strings.TrimSuffix(.PkgObj, ".a")
}
= .ImportPath
case build.IsLocalImport():
// "./x" -> "/this/directory/x.ext", "/this/directory/x"
= filepath.Join(, )
=
case filepath.IsAbs():
// for completeness only - go/build.Import
// does not support absolute imports
// "/x" -> "/x.ext", "/x"
=
=
}
if false { // for debugging
if != {
fmt.Printf("%s -> %s\n", , )
}
}
// try extensions
for , := range pkgExts {
= +
, := os.Stat()
if == nil && !.IsDir() {
return , , nil
}
if == nil {
=
}
}
:
if == nil {
return "", , fmt.Errorf("can't find import: %q", )
}
return "", , fmt.Errorf("can't find import: %q: %w", , )
}
var pkgExts = [...]string{".a", ".o"} // a file from the build cache will have no extension
var exportMap sync.Map // package dir → func() (string, error)
// lookupGorootExport returns the location of the export data
// (normally found in the build cache, but located in GOROOT/pkg
// in prior Go releases) for the package located in pkgDir.
//
// (We use the package's directory instead of its import path
// mainly to simplify handling of the packages in src/vendor
// and cmd/vendor.)
//
// lookupGorootExport is only used in tests within x/tools.
func ( string) (string, error) {
, := exportMap.Load()
if ! {
var (
sync.Once
string
error
)
, _ = exportMap.LoadOrStore(, func() (string, error) {
.Do(func() {
:= exec.Command(filepath.Join(build.Default.GOROOT, "bin", "go"), "list", "-export", "-f", "{{.Export}}", )
.Dir = build.Default.GOROOT
.Env = append(os.Environ(), "PWD="+.Dir, "GOROOT="+build.Default.GOROOT)
var []byte
, = .Output()
if != nil {
if , := .(*exec.ExitError); && len(.Stderr) > 0 {
= errors.New(string(.Stderr))
}
return
}
:= strings.Split(string(bytes.TrimSpace()), "\n")
if len() != 1 {
= fmt.Errorf("go list reported %d exports; expected 1", len())
return
}
= [0]
})
return ,
})
}
return .(func() (string, error))()
}
The pages are generated with Golds v0.7.6. (GOOS=linux GOARCH=amd64)