gcexportdata.go raw

   1  // Copyright 2016 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 gcexportdata provides functions for reading and writing
   6  // export data, which is a serialized description of the API of a Go
   7  // package including the names, kinds, types, and locations of all
   8  // exported declarations.
   9  //
  10  // The standard Go compiler (cmd/compile) writes an export data file
  11  // for each package it compiles, which it later reads when compiling
  12  // packages that import the earlier one. The compiler must thus
  13  // contain logic to both write and read export data.
  14  // (See the "Export" section in the cmd/compile/README file.)
  15  //
  16  // The [Read] function in this package can read files produced by the
  17  // compiler, producing [go/types] data structures. As a matter of
  18  // policy, Read supports export data files produced by only the last
  19  // two Go releases plus tip; see https://go.dev/issue/68898. The
  20  // export data files produced by the compiler contain additional
  21  // details related to generics, inlining, and other optimizations that
  22  // cannot be decoded by the [Read] function.
  23  //
  24  // In files written by the compiler, the export data is not at the
  25  // start of the file. Before calling Read, use [NewReader] to locate
  26  // the desired portion of the file.
  27  //
  28  // The [Write] function in this package encodes the exported API of a
  29  // Go package ([types.Package]) as a file. Such files can be later
  30  // decoded by Read, but cannot be consumed by the compiler.
  31  //
  32  // # Future changes
  33  //
  34  // Although Read supports the formats written by both Write and the
  35  // compiler, the two are quite different, and there is an open
  36  // proposal (https://go.dev/issue/69491) to separate these APIs.
  37  //
  38  // Under that proposal, this package would ultimately provide only the
  39  // Read operation for compiler export data, which must be defined in
  40  // this module (golang.org/x/tools), not in the standard library, to
  41  // avoid version skew for developer tools that need to read compiler
  42  // export data both before and after a Go release, such as from Go
  43  // 1.23 to Go 1.24. Because this package lives in the tools module,
  44  // clients can update their version of the module some time before the
  45  // Go 1.24 release and rebuild and redeploy their tools, which will
  46  // then be able to consume both Go 1.23 and Go 1.24 export data files,
  47  // so they will work before and after the Go update. (See discussion
  48  // at https://go.dev/issue/15651.)
  49  //
  50  // The operations to import and export [go/types] data structures
  51  // would be defined in the go/types package as Import and Export.
  52  // [Write] would (eventually) delegate to Export,
  53  // and [Read], when it detects a file produced by Export,
  54  // would delegate to Import.
  55  //
  56  // # Deprecations
  57  //
  58  // The [NewImporter] and [Find] functions are deprecated and should
  59  // not be used in new code. The [WriteBundle] and [ReadBundle]
  60  // functions are experimental, and there is an open proposal to
  61  // deprecate them (https://go.dev/issue/69573).
  62  package gcexportdata
  63  
  64  import (
  65  	"bufio"
  66  	"bytes"
  67  	"encoding/json"
  68  	"fmt"
  69  	"go/token"
  70  	"go/types"
  71  	"io"
  72  	"os/exec"
  73  
  74  	"golang.org/x/tools/internal/gcimporter"
  75  )
  76  
  77  // Find returns the name of an object (.o) or archive (.a) file
  78  // containing type information for the specified import path,
  79  // using the go command.
  80  // If no file was found, an empty filename is returned.
  81  //
  82  // A relative srcDir is interpreted relative to the current working directory.
  83  //
  84  // Find also returns the package's resolved (canonical) import path,
  85  // reflecting the effects of srcDir and vendoring on importPath.
  86  //
  87  // Deprecated: Use the higher-level API in golang.org/x/tools/go/packages,
  88  // which is more efficient.
  89  func Find(importPath, srcDir string) (filename, path string) {
  90  	cmd := exec.Command("go", "list", "-json", "-export", "--", importPath)
  91  	cmd.Dir = srcDir
  92  	out, err := cmd.Output()
  93  	if err != nil {
  94  		return "", ""
  95  	}
  96  	var data struct {
  97  		ImportPath string
  98  		Export     string
  99  	}
 100  	json.Unmarshal(out, &data)
 101  	return data.Export, data.ImportPath
 102  }
 103  
 104  // NewReader returns a reader for the export data section of an object
 105  // (.o) or archive (.a) file read from r.  The new reader may provide
 106  // additional trailing data beyond the end of the export data.
 107  func NewReader(r io.Reader) (io.Reader, error) {
 108  	buf := bufio.NewReader(r)
 109  	size, err := gcimporter.FindExportData(buf)
 110  	if err != nil {
 111  		return nil, err
 112  	}
 113  
 114  	// We were given an archive and found the __.PKGDEF in it.
 115  	// This tells us the size of the export data, and we don't
 116  	// need to return the entire file.
 117  	return &io.LimitedReader{
 118  		R: buf,
 119  		N: size,
 120  	}, nil
 121  }
 122  
 123  // readAll works the same way as io.ReadAll, but avoids allocations and copies
 124  // by preallocating a byte slice of the necessary size if the size is known up
 125  // front. This is always possible when the input is an archive. In that case,
 126  // NewReader will return the known size using an io.LimitedReader.
 127  func readAll(r io.Reader) ([]byte, error) {
 128  	if lr, ok := r.(*io.LimitedReader); ok {
 129  		data := make([]byte, lr.N)
 130  		_, err := io.ReadFull(lr, data)
 131  		return data, err
 132  	}
 133  	return io.ReadAll(r)
 134  }
 135  
 136  // Read reads export data from in, decodes it, and returns type
 137  // information for the package.
 138  //
 139  // Read is capable of reading export data produced by [Write] at the
 140  // same source code version, or by the last two Go releases (plus tip)
 141  // of the standard Go compiler. Reading files from older compilers may
 142  // produce an error.
 143  //
 144  // The package path (effectively its linker symbol prefix) is
 145  // specified by path, since unlike the package name, this information
 146  // may not be recorded in the export data.
 147  //
 148  // File position information is added to fset.
 149  //
 150  // Read may inspect and add to the imports map to ensure that references
 151  // within the export data to other packages are consistent.  The caller
 152  // must ensure that imports[path] does not exist, or exists but is
 153  // incomplete (see types.Package.Complete), and Read inserts the
 154  // resulting package into this map entry.
 155  //
 156  // On return, the state of the reader is undefined.
 157  func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) {
 158  	data, err := readAll(in)
 159  	if err != nil {
 160  		return nil, fmt.Errorf("reading export data for %q: %v", path, err)
 161  	}
 162  
 163  	if bytes.HasPrefix(data, []byte("!<arch>")) {
 164  		return nil, fmt.Errorf("can't read export data for %q directly from an archive file (call gcexportdata.NewReader first to extract export data)", path)
 165  	}
 166  
 167  	// The indexed export format starts with an 'i'; the older
 168  	// binary export format starts with a 'c', 'd', or 'v'
 169  	// (from "version"). Select appropriate importer.
 170  	if len(data) > 0 {
 171  		switch data[0] {
 172  		case 'v', 'c', 'd':
 173  			// binary, produced by cmd/compile till go1.10
 174  			return nil, fmt.Errorf("binary (%c) import format is no longer supported", data[0])
 175  
 176  		case 'i':
 177  			// indexed, produced by cmd/compile till go1.19,
 178  			// and also by [Write].
 179  			//
 180  			// If proposal #69491 is accepted, go/types
 181  			// serialization will be implemented by
 182  			// types.Export, to which Write would eventually
 183  			// delegate (explicitly dropping any pretence at
 184  			// inter-version Write-Read compatibility).
 185  			// This [Read] function would delegate to types.Import
 186  			// when it detects that the file was produced by Export.
 187  			_, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path)
 188  			return pkg, err
 189  
 190  		case 'u':
 191  			// unified, produced by cmd/compile since go1.20
 192  			_, pkg, err := gcimporter.UImportData(fset, imports, data[1:], path)
 193  			return pkg, err
 194  
 195  		default:
 196  			l := min(len(data), 10)
 197  			return nil, fmt.Errorf("unexpected export data with prefix %q for path %s", string(data[:l]), path)
 198  		}
 199  	}
 200  	return nil, fmt.Errorf("empty export data for %s", path)
 201  }
 202  
 203  // Write writes encoded type information for the specified package to out.
 204  // The FileSet provides file position information for named objects.
 205  func Write(out io.Writer, fset *token.FileSet, pkg *types.Package) error {
 206  	if _, err := io.WriteString(out, "i"); err != nil {
 207  		return err
 208  	}
 209  	return gcimporter.IExportData(out, fset, pkg)
 210  }
 211  
 212  // ReadBundle reads an export bundle from in, decodes it, and returns type
 213  // information for the packages.
 214  // File position information is added to fset.
 215  //
 216  // ReadBundle may inspect and add to the imports map to ensure that references
 217  // within the export bundle to other packages are consistent.
 218  //
 219  // On return, the state of the reader is undefined.
 220  //
 221  // Experimental: This API is experimental and may change in the future.
 222  func ReadBundle(in io.Reader, fset *token.FileSet, imports map[string]*types.Package) ([]*types.Package, error) {
 223  	data, err := readAll(in)
 224  	if err != nil {
 225  		return nil, fmt.Errorf("reading export bundle: %v", err)
 226  	}
 227  	return gcimporter.IImportBundle(fset, imports, data)
 228  }
 229  
 230  // WriteBundle writes encoded type information for the specified packages to out.
 231  // The FileSet provides file position information for named objects.
 232  //
 233  // Experimental: This API is experimental and may change in the future.
 234  func WriteBundle(out io.Writer, fset *token.FileSet, pkgs []*types.Package) error {
 235  	return gcimporter.IExportBundle(out, fset, pkgs)
 236  }
 237