blob: abe64fe61a1f8bec1a5e040058386f15d035aee9 [file] [log] [blame]
// Copyright 2013 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.
// Package loader loads, parses and type-checks packages of Go code
// plus their transitive closure, and retains both the ASTs and the
// derived facts.
//
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
//
// The package defines two primary types: Config, which specifies a
// set of initial packages to load and various other options; and
// Program, which is the result of successfully loading the packages
// specified by a configuration.
//
// The configuration can be set directly, but *Config provides various
// convenience methods to simplify the common cases, each of which can
// be called any number of times. Finally, these are followed by a
// call to Load() to actually load and type-check the program.
//
// var conf loader.Config
//
// // Use the command-line arguments to specify
// // a set of initial packages to load from source.
// // See FromArgsUsage for help.
// rest, err := conf.FromArgs(os.Args[1:], wantTests)
//
// // Parse the specified files and create an ad-hoc package with path "foo".
// // All files must have the same 'package' declaration.
// conf.CreateFromFilenames("foo", "foo.go", "bar.go")
//
// // Create an ad-hoc package with path "foo" from
// // the specified already-parsed files.
// // All ASTs must have the same 'package' declaration.
// conf.CreateFromFiles("foo", parsedFiles)
//
// // Add "runtime" to the set of packages to be loaded.
// conf.Import("runtime")
//
// // Adds "fmt" and "fmt_test" to the set of packages
// // to be loaded. "fmt" will include *_test.go files.
// conf.ImportWithTests("fmt")
//
// // Finally, load all the packages specified by the configuration.
// prog, err := conf.Load()
//
//
// CONCEPTS AND TERMINOLOGY
//
// An AD-HOC package is one specified as a set of source files on the
// command line. In the simplest case, it may consist of a single file
// such as $GOROOT/src/net/http/triv.go.
//
// EXTERNAL TEST packages are those comprised of a set of *_test.go
// files all with the same 'package foo_test' declaration, all in the
// same directory. (go/build.Package calls these files XTestFiles.)
//
// An IMPORTABLE package is one that can be referred to by some import
// spec. The Path() of each importable package is unique within a
// Program.
//
// Ad-hoc packages and external test packages are NON-IMPORTABLE. The
// Path() of an ad-hoc package is inferred from the package
// declarations of its files and is therefore not a unique package key.
// For example, Config.CreatePkgs may specify two initial ad-hoc
// packages both called "main".
//
// An AUGMENTED package is an importable package P plus all the
// *_test.go files with same 'package foo' declaration as P.
// (go/build.Package calls these files TestFiles.)
//
// The INITIAL packages are those specified in the configuration. A
// DEPENDENCY is a package loaded to satisfy an import in an initial
// package or another dependency.
//
package loader
// 'go test', in-package test files, and import cycles
// ---------------------------------------------------
//
// An external test package may depend upon members of the augmented
// package that are not in the unaugmented package, such as functions
// that expose internals. (See bufio/export_test.go for an example.)
// So, the loader must ensure that for each external test package
// it loads, it also augments the corresponding non-test package.
//
// The import graph over n unaugmented packages must be acyclic; the
// import graph over n-1 unaugmented packages plus one augmented
// package must also be acyclic. ('go test' relies on this.) But the
// import graph over n augmented packages may contain cycles.
//
// First, all the (unaugmented) non-test packages and their
// dependencies are imported in the usual way; the loader reports an
// error if it detects an import cycle.
//
// Then, each package P for which testing is desired is augmented by
// the list P' of its in-package test files, by calling
// (*types.Checker).Files. This arrangement ensures that P' may
// reference definitions within P, but P may not reference definitions
// within P'. Furthermore, P' may import any other package, including
// ones that depend upon P, without an import cycle error.
//
// Consider two packages A and B, both of which have lists of
// in-package test files we'll call A' and B', and which have the
// following import graph edges:
// B imports A
// B' imports A
// A' imports B
// This last edge would be expected to create an error were it not
// for the special type-checking discipline above.
// Cycles of size greater than two are possible. For example:
// compress/bzip2/bzip2_test.go (package bzip2) imports "io/ioutil"
// io/ioutil/tempfile_test.go (package ioutil) imports "regexp"
// regexp/exec_test.go (package regexp) imports "compress/bzip2"
//
//
// Concurrency
// -----------
//
// Let us define the import dependency graph as follows. Each node is a
// list of files passed to (Checker).Files at once. Many of these lists
// are the production code of an importable Go package, so those nodes
// are labelled by the package's import path. The remaining nodes are
// ad-hoc packages and lists of in-package *_test.go files that augment
// an importable package; those nodes have no label.
//
// The edges of the graph represent import statements appearing within a
// file. An edge connects a node (a list of files) to the node it
// imports, which is importable and thus always labelled.
//
// Loading is controlled by this dependency graph.
//
// To reduce I/O latency, we start loading a package's dependencies
// asynchronously as soon as we've parsed its files and enumerated its
// imports (scanImports). This performs a preorder traversal of the
// import dependency graph.
//
// To exploit hardware parallelism, we type-check unrelated packages in
// parallel, where "unrelated" means not ordered by the partial order of
// the import dependency graph.
//
// We use a concurrency-safe blocking cache (importer.imported) to
// record the results of type-checking, whether success or failure. An
// entry is created in this cache by startLoad the first time the
// package is imported. The first goroutine to request an entry becomes
// responsible for completing the task and broadcasting completion to
// subsequent requestors, which block until then.
//
// Type checking occurs in (parallel) postorder: we cannot type-check a
// set of files until we have loaded and type-checked all of their
// immediate dependencies (and thus all of their transitive
// dependencies). If the input were guaranteed free of import cycles,
// this would be trivial: we could simply wait for completion of the
// dependencies and then invoke the typechecker.
//
// But as we saw in the 'go test' section above, some cycles in the
// import graph over packages are actually legal, so long as the
// cycle-forming edge originates in the in-package test files that
// augment the package. This explains why the nodes of the import
// dependency graph are not packages, but lists of files: the unlabelled
// nodes avoid the cycles. Consider packages A and B where B imports A
// and A's in-package tests AT import B. The naively constructed import
// graph over packages would contain a cycle (A+AT) --> B --> (A+AT) but
// the graph over lists of files is AT --> B --> A, where AT is an
// unlabelled node.
//
// Awaiting completion of the dependencies in a cyclic graph would
// deadlock, so we must materialize the import dependency graph (as
// importer.graph) and check whether each import edge forms a cycle. If
// x imports y, and the graph already contains a path from y to x, then
// there is an import cycle, in which case the processing of x must not
// wait for the completion of processing of y.
//
// When the type-checker makes a callback (doImport) to the loader for a
// given import edge, there are two possible cases. In the normal case,
// the dependency has already been completely type-checked; doImport
// does a cache lookup and returns it. In the cyclic case, the entry in
// the cache is still necessarily incomplete, indicating a cycle. We
// perform the cycle check again to obtain the error message, and return
// the error.
//
// The result of using concurrency is about a 2.5x speedup for stdlib_test.
// TODO(adonovan):
// - cache the calls to build.Import so we don't do it three times per
// test package.
// - Thorough overhaul of package documentation.
import (
"errors"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"os"
"sort"
"strings"
"sync"
"time"
"llvm.org/llgo/third_party/gotools/go/ast/astutil"
"llvm.org/llgo/third_party/gotools/go/gcimporter"
"llvm.org/llgo/third_party/gotools/go/types"
)
const trace = false // show timing info for type-checking
// Config specifies the configuration for a program to load.
// The zero value for Config is a ready-to-use default configuration.
type Config struct {
// Fset is the file set for the parser to use when loading the
// program. If nil, it may be lazily initialized by any
// method of Config.
Fset *token.FileSet
// ParserMode specifies the mode to be used by the parser when
// loading source packages.
ParserMode parser.Mode
// TypeChecker contains options relating to the type checker.
//
// The supplied IgnoreFuncBodies is not used; the effective
// value comes from the TypeCheckFuncBodies func below.
//
// TypeChecker.Packages is lazily initialized during Load.
TypeChecker types.Config
// TypeCheckFuncBodies is a predicate over package import
// paths. A package for which the predicate is false will
// have its package-level declarations type checked, but not
// its function bodies; this can be used to quickly load
// dependencies from source. If nil, all func bodies are type
// checked.
TypeCheckFuncBodies func(string) bool
// ImportFromBinary determines whether to satisfy dependencies by
// loading gc export data instead of Go source code.
//
// If false, the entire program---the initial packages and their
// transitive closure of dependencies---will be loaded from
// source, parsed, and type-checked. This is required for
// whole-program analyses such as pointer analysis.
//
// If true, the go/gcimporter mechanism is used instead to read
// the binary export-data files written by the gc toolchain.
// They supply only the types of package-level declarations and
// values of constants, but no code, this option will not yield
// a whole program. It is intended for analyses that perform
// modular analysis of a single package, e.g. traditional
// compilation.
//
// No check is made that the export data files are up-to-date.
//
// The initial packages (CreatePkgs and ImportPkgs) are always
// loaded from Go source, regardless of this flag's setting.
//
// NB: there is a bug when loading multiple initial packages with
// this flag enabled: https://github.com/golang/go/issues/9955.
ImportFromBinary bool
// If Build is non-nil, it is used to locate source packages.
// Otherwise &build.Default is used.
//
// By default, cgo is invoked to preprocess Go files that
// import the fake package "C". This behaviour can be
// disabled by setting CGO_ENABLED=0 in the environment prior
// to startup, or by setting Build.CgoEnabled=false.
Build *build.Context
// The current directory, used for resolving relative package
// references such as "./go/loader". If empty, os.Getwd will be
// used instead.
Cwd string
// If DisplayPath is non-nil, it is used to transform each
// file name obtained from Build.Import(). This can be used
// to prevent a virtualized build.Config's file names from
// leaking into the user interface.
DisplayPath func(path string) string
// If AllowErrors is true, Load will return a Program even
// if some of the its packages contained I/O, parser or type
// errors; such errors are accessible via PackageInfo.Errors. If
// false, Load will fail if any package had an error.
AllowErrors bool
// CreatePkgs specifies a list of non-importable initial
// packages to create. The resulting packages will appear in
// the corresponding elements of the Program.Created slice.
CreatePkgs []PkgSpec
// ImportPkgs specifies a set of initial packages to load from
// source. The map keys are package import paths, used to
// locate the package relative to $GOROOT.
//
// The map value indicates whether to load tests. If true, Load
// will add and type-check two lists of files to the package:
// non-test files followed by in-package *_test.go files. In
// addition, it will append the external test package (if any)
// to Program.Created.
ImportPkgs map[string]bool
// FindPackage is called during Load to create the build.Package
// for a given import path. If nil, a default implementation
// based on ctxt.Import is used. A client may use this hook to
// adapt to a proprietary build system that does not follow the
// "go build" layout conventions, for example.
//
// It must be safe to call concurrently from multiple goroutines.
FindPackage func(ctxt *build.Context, importPath string) (*build.Package, error)
// PackageCreated is a hook called when a types.Package
// is created but before it has been populated.
//
// The package's import Path() and Scope() are defined,
// but not its Name() since no package declaration has
// been seen yet.
//
// Clients may use this to insert synthetic items into
// the package scope, for example.
//
// It must be safe to call concurrently from multiple goroutines.
PackageCreated func(*types.Package)
}
// A PkgSpec specifies a non-importable package to be created by Load.
// Files are processed first, but typically only one of Files and
// Filenames is provided. The path needn't be globally unique.
//
type PkgSpec struct {
Path string // import path ("" => use package declaration)
Files []*ast.File // ASTs of already-parsed files
Filenames []string // names of files to be parsed
}
// A Program is a Go program loaded from source or binary
// as specified by a Config.
type Program struct {
Fset *token.FileSet // the file set for this program
// Created[i] contains the initial package whose ASTs or
// filenames were supplied by Config.CreatePkgs[i], followed by
// the external test package, if any, of each package in
// Config.ImportPkgs ordered by ImportPath.
Created []*PackageInfo
// Imported contains the initially imported packages,
// as specified by Config.ImportPkgs.
Imported map[string]*PackageInfo
// ImportMap is the canonical mapping of import paths to
// packages used by the type-checker (Config.TypeChecker.Packages).
// It contains all Imported initial packages, but not Created
// ones, and all imported dependencies.
ImportMap map[string]*types.Package
// AllPackages contains the PackageInfo of every package
// encountered by Load: all initial packages and all
// dependencies, including incomplete ones.
AllPackages map[*types.Package]*PackageInfo
}
// PackageInfo holds the ASTs and facts derived by the type-checker
// for a single package.
//
// Not mutated once exposed via the API.
//
type PackageInfo struct {
Pkg *types.Package
Importable bool // true if 'import "Pkg.Path()"' would resolve to this
TransitivelyErrorFree bool // true if Pkg and all its dependencies are free of errors
Files []*ast.File // syntax trees for the package's files
Errors []error // non-nil if the package had errors
types.Info // type-checker deductions.
checker *types.Checker // transient type-checker state
errorFunc func(error)
}
func (info *PackageInfo) String() string { return info.Pkg.Path() }
func (info *PackageInfo) appendError(err error) {
if info.errorFunc != nil {
info.errorFunc(err)
} else {
fmt.Fprintln(os.Stderr, err)
}
info.Errors = append(info.Errors, err)
}
func (conf *Config) fset() *token.FileSet {
if conf.Fset == nil {
conf.Fset = token.NewFileSet()
}
return conf.Fset
}
// ParseFile is a convenience function (intended for testing) that invokes
// the parser using the Config's FileSet, which is initialized if nil.
//
// src specifies the parser input as a string, []byte, or io.Reader, and
// filename is its apparent name. If src is nil, the contents of
// filename are read from the file system.
//
func (conf *Config) ParseFile(filename string, src interface{}) (*ast.File, error) {
// TODO(adonovan): use conf.build() etc like parseFiles does.
return parser.ParseFile(conf.fset(), filename, src, conf.ParserMode)
}
// FromArgsUsage is a partial usage message that applications calling
// FromArgs may wish to include in their -help output.
const FromArgsUsage = `
<args> is a list of arguments denoting a set of initial packages.
It may take one of two forms:
1. A list of *.go source files.
All of the specified files are loaded, parsed and type-checked
as a single package. All the files must belong to the same directory.
2. A list of import paths, each denoting a package.
The package's directory is found relative to the $GOROOT and
$GOPATH using similar logic to 'go build', and the *.go files in
that directory are loaded, parsed and type-checked as a single
package.
In addition, all *_test.go files in the directory are then loaded
and parsed. Those files whose package declaration equals that of
the non-*_test.go files are included in the primary package. Test
files whose package declaration ends with "_test" are type-checked
as another package, the 'external' test package, so that a single
import path may denote two packages. (Whether this behaviour is
enabled is tool-specific, and may depend on additional flags.)
A '--' argument terminates the list of packages.
`
// FromArgs interprets args as a set of initial packages to load from
// source and updates the configuration. It returns the list of
// unconsumed arguments.
//
// It is intended for use in command-line interfaces that require a
// set of initial packages to be specified; see FromArgsUsage message
// for details.
//
// Only superficial errors are reported at this stage; errors dependent
// on I/O are detected during Load.
//
func (conf *Config) FromArgs(args []string, xtest bool) ([]string, error) {
var rest []string
for i, arg := range args {
if arg == "--" {
rest = args[i+1:]
args = args[:i]
break // consume "--" and return the remaining args
}
}
if len(args) > 0 && strings.HasSuffix(args[0], ".go") {
// Assume args is a list of a *.go files
// denoting a single ad-hoc package.
for _, arg := range args {
if !strings.HasSuffix(arg, ".go") {
return nil, fmt.Errorf("named files must be .go files: %s", arg)
}
}
conf.CreateFromFilenames("", args...)
} else {
// Assume args are directories each denoting a
// package and (perhaps) an external test, iff xtest.
for _, arg := range args {
if xtest {
conf.ImportWithTests(arg)
} else {
conf.Import(arg)
}
}
}
return rest, nil
}
// CreateFromFilenames is a convenience function that adds
// a conf.CreatePkgs entry to create a package of the specified *.go
// files.
//
func (conf *Config) CreateFromFilenames(path string, filenames ...string) {
conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Filenames: filenames})
}
// CreateFromFiles is a convenience function that adds a conf.CreatePkgs
// entry to create package of the specified path and parsed files.
//
func (conf *Config) CreateFromFiles(path string, files ...*ast.File) {
conf.CreatePkgs = append(conf.CreatePkgs, PkgSpec{Path: path, Files: files})
}
// ImportWithTests is a convenience function that adds path to
// ImportPkgs, the set of initial source packages located relative to
// $GOPATH. The package will be augmented by any *_test.go files in
// its directory that contain a "package x" (not "package x_test")
// declaration.
//
// In addition, if any *_test.go files contain a "package x_test"
// declaration, an additional package comprising just those files will
// be added to CreatePkgs.
//
func (conf *Config) ImportWithTests(path string) { conf.addImport(path, true) }
// Import is a convenience function that adds path to ImportPkgs, the
// set of initial packages that will be imported from source.
//
func (conf *Config) Import(path string) { conf.addImport(path, false) }
func (conf *Config) addImport(path string, tests bool) {
if path == "unsafe" {
return // ignore; not a real package
}
if conf.ImportPkgs == nil {
conf.ImportPkgs = make(map[string]bool)
}
conf.ImportPkgs[path] = conf.ImportPkgs[path] || tests
}
// PathEnclosingInterval returns the PackageInfo and ast.Node that
// contain source interval [start, end), and all the node's ancestors
// up to the AST root. It searches all ast.Files of all packages in prog.
// exact is defined as for astutil.PathEnclosingInterval.
//
// The zero value is returned if not found.
//
func (prog *Program) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) {
for _, info := range prog.AllPackages {
for _, f := range info.Files {
if !tokenFileContainsPos(prog.Fset.File(f.Pos()), start) {
continue
}
if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil {
return info, path, exact
}
}
}
return nil, nil, false
}
// InitialPackages returns a new slice containing the set of initial
// packages (Created + Imported) in unspecified order.
//
func (prog *Program) InitialPackages() []*PackageInfo {
infos := make([]*PackageInfo, 0, len(prog.Created)+len(prog.Imported))
infos = append(infos, prog.Created...)
for _, info := range prog.Imported {
infos = append(infos, info)
}
return infos
}
// ---------- Implementation ----------
// importer holds the working state of the algorithm.
type importer struct {
conf *Config // the client configuration
prog *Program // resulting program
start time.Time // for logging
// This mutex serializes access to prog.ImportMap (aka
// TypeChecker.Packages); we also use it for AllPackages.
//
// The TypeChecker.Packages map is not really used by this
// package, but may be used by the client's Import function,
// and by clients of the returned Program.
typecheckerMu sync.Mutex
importedMu sync.Mutex
imported map[string]*importInfo // all imported packages (incl. failures) by import path
// import dependency graph: graph[x][y] => x imports y
//
// Since non-importable packages cannot be cyclic, we ignore
// their imports, thus we only need the subgraph over importable
// packages. Nodes are identified by their import paths.
graphMu sync.Mutex
graph map[string]map[string]bool
}
// importInfo tracks the success or failure of a single import.
//
// Upon completion, exactly one of info and err is non-nil:
// info on successful creation of a package, err otherwise.
// A successful package may still contain type errors.
//
type importInfo struct {
path string // import path
mu sync.Mutex // guards the following fields prior to completion
info *PackageInfo // results of typechecking (including errors)
err error // reason for failure to create a package
complete sync.Cond // complete condition is that one of info, err is non-nil.
}
// awaitCompletion blocks until ii is complete,
// i.e. the info and err fields are safe to inspect without a lock.
// It is concurrency-safe and idempotent.
func (ii *importInfo) awaitCompletion() {
ii.mu.Lock()
for ii.info == nil && ii.err == nil {
ii.complete.Wait()
}
ii.mu.Unlock()
}
// Complete marks ii as complete.
// Its info and err fields will not be subsequently updated.
func (ii *importInfo) Complete(info *PackageInfo, err error) {
ii.mu.Lock()
ii.info = info
ii.err = err
ii.complete.Broadcast()
ii.mu.Unlock()
}
// Load creates the initial packages specified by conf.{Create,Import}Pkgs,
// loading their dependencies packages as needed.
//
// On success, Load returns a Program containing a PackageInfo for
// each package. On failure, it returns an error.
//
// If AllowErrors is true, Load will return a Program even if some
// packages contained I/O, parser or type errors, or if dependencies
// were missing. (Such errors are accessible via PackageInfo.Errors. If
// false, Load will fail if any package had an error.
//
// It is an error if no packages were loaded.
//
func (conf *Config) Load() (*Program, error) {
// Initialize by setting the conf's copy, so all copies of
// TypeChecker agree on the identity of the map.
if conf.TypeChecker.Packages == nil {
conf.TypeChecker.Packages = make(map[string]*types.Package)
}
// Create a simple default error handler for parse/type errors.
if conf.TypeChecker.Error == nil {
conf.TypeChecker.Error = func(e error) { fmt.Fprintln(os.Stderr, e) }
}
// Set default working directory for relative package references.
if conf.Cwd == "" {
var err error
conf.Cwd, err = os.Getwd()
if err != nil {
return nil, err
}
}
// Install default FindPackage hook using go/build logic.
if conf.FindPackage == nil {
conf.FindPackage = func(ctxt *build.Context, path string) (*build.Package, error) {
bp, err := ctxt.Import(path, conf.Cwd, 0)
if _, ok := err.(*build.NoGoError); ok {
return bp, nil // empty directory is not an error
}
return bp, err
}
}
prog := &Program{
Fset: conf.fset(),
Imported: make(map[string]*PackageInfo),
ImportMap: conf.TypeChecker.Packages,
AllPackages: make(map[*types.Package]*PackageInfo),
}
imp := importer{
conf: conf,
prog: prog,
imported: make(map[string]*importInfo),
start: time.Now(),
graph: make(map[string]map[string]bool),
}
// -- loading proper (concurrent phase) --------------------------------
var errpkgs []string // packages that contained errors
// Load the initially imported packages and their dependencies,
// in parallel.
for _, ii := range imp.loadAll("", conf.ImportPkgs) {
if ii.err != nil {
conf.TypeChecker.Error(ii.err) // failed to create package
errpkgs = append(errpkgs, ii.path)
continue
}
prog.Imported[ii.info.Pkg.Path()] = ii.info
}
// Augment the designated initial packages by their tests.
// Dependencies are loaded in parallel.
var xtestPkgs []*build.Package
for path, augment := range conf.ImportPkgs {
if !augment {
continue
}
bp, err := conf.FindPackage(conf.build(), path)
if err != nil {
// Package not found, or can't even parse package declaration.
// Already reported by previous loop; ignore it.
continue
}
// Needs external test package?
if len(bp.XTestGoFiles) > 0 {
xtestPkgs = append(xtestPkgs, bp)
}
imp.importedMu.Lock() // (unnecessary, we're sequential here)
info := imp.imported[path].info // must be non-nil, see above
imp.importedMu.Unlock()
// Parse the in-package test files.
files, errs := imp.conf.parsePackageFiles(bp, 't')
for _, err := range errs {
info.appendError(err)
}
// The test files augmenting package P cannot be imported,
// but may import packages that import P,
// so we must disable the cycle check.
imp.addFiles(info, files, false)
}
createPkg := func(path string, files []*ast.File, errs []error) {
info := imp.newPackageInfo(path)
for _, err := range errs {
info.appendError(err)
}
// Ad-hoc packages are non-importable,
// so no cycle check is needed.
// addFiles loads dependencies in parallel.
imp.addFiles(info, files, false)
prog.Created = append(prog.Created, info)
}
// Create packages specified by conf.CreatePkgs.
for _, cp := range conf.CreatePkgs {
files, errs := parseFiles(conf.fset(), conf.build(), nil, ".", cp.Filenames, conf.ParserMode)
files = append(files, cp.Files...)
path := cp.Path
if path == "" {
if len(files) > 0 {
path = files[0].Name.Name
} else {
path = "(unnamed)"
}
}
createPkg(path, files, errs)
}
// Create external test packages.
sort.Sort(byImportPath(xtestPkgs))
for _, bp := range xtestPkgs {
files, errs := imp.conf.parsePackageFiles(bp, 'x')
createPkg(bp.ImportPath+"_test", files, errs)
}
// -- finishing up (sequential) ----------------------------------------
if len(prog.Imported)+len(prog.Created) == 0 {
return nil, errors.New("no initial packages were loaded")
}
// Create infos for indirectly imported packages.
// e.g. incomplete packages without syntax, loaded from export data.
for _, obj := range prog.ImportMap {
info := prog.AllPackages[obj]
if info == nil {
prog.AllPackages[obj] = &PackageInfo{Pkg: obj, Importable: true}
} else {
// finished
info.checker = nil
info.errorFunc = nil
}
}
if !conf.AllowErrors {
// Report errors in indirectly imported packages.
for _, info := range prog.AllPackages {
if len(info.Errors) > 0 {
errpkgs = append(errpkgs, info.Pkg.Path())
}
}
if errpkgs != nil {
var more string
if len(errpkgs) > 3 {
more = fmt.Sprintf(" and %d more", len(errpkgs)-3)
errpkgs = errpkgs[:3]
}
return nil, fmt.Errorf("couldn't load packages due to errors: %s%s",
strings.Join(errpkgs, ", "), more)
}
}
markErrorFreePackages(prog.AllPackages)
return prog, nil
}
type byImportPath []*build.Package
func (b byImportPath) Len() int { return len(b) }
func (b byImportPath) Less(i, j int) bool { return b[i].ImportPath < b[j].ImportPath }
func (b byImportPath) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
// markErrorFreePackages sets the TransitivelyErrorFree flag on all
// applicable packages.
func markErrorFreePackages(allPackages map[*types.Package]*PackageInfo) {
// Build the transpose of the import graph.
importedBy := make(map[*types.Package]map[*types.Package]bool)
for P := range allPackages {
for _, Q := range P.Imports() {
clients, ok := importedBy[Q]
if !ok {
clients = make(map[*types.Package]bool)
importedBy[Q] = clients
}
clients[P] = true
}
}
// Find all packages reachable from some error package.
reachable := make(map[*types.Package]bool)
var visit func(*types.Package)
visit = func(p *types.Package) {
if !reachable[p] {
reachable[p] = true
for q := range importedBy[p] {
visit(q)
}
}
}
for _, info := range allPackages {
if len(info.Errors) > 0 {
visit(info.Pkg)
}
}
// Mark the others as "transitively error-free".
for _, info := range allPackages {
if !reachable[info.Pkg] {
info.TransitivelyErrorFree = true
}
}
}
// build returns the effective build context.
func (conf *Config) build() *build.Context {
if conf.Build != nil {
return conf.Build
}
return &build.Default
}
// parsePackageFiles enumerates the files belonging to package path,
// then loads, parses and returns them, plus a list of I/O or parse
// errors that were encountered.
//
// 'which' indicates which files to include:
// 'g': include non-test *.go source files (GoFiles + processed CgoFiles)
// 't': include in-package *_test.go source files (TestGoFiles)
// 'x': include external *_test.go source files. (XTestGoFiles)
//
func (conf *Config) parsePackageFiles(bp *build.Package, which rune) ([]*ast.File, []error) {
var filenames []string
switch which {
case 'g':
filenames = bp.GoFiles
case 't':
filenames = bp.TestGoFiles
case 'x':
filenames = bp.XTestGoFiles
default:
panic(which)
}
files, errs := parseFiles(conf.fset(), conf.build(), conf.DisplayPath, bp.Dir, filenames, conf.ParserMode)
// Preprocess CgoFiles and parse the outputs (sequentially).
if which == 'g' && bp.CgoFiles != nil {
cgofiles, err := processCgoFiles(bp, conf.fset(), conf.DisplayPath, conf.ParserMode)
if err != nil {
errs = append(errs, err)
} else {
files = append(files, cgofiles...)
}
}
return files, errs
}
// doImport imports the package denoted by path.
// It implements the types.Importer signature.
//
// imports is the type-checker's package canonicalization map.
//
// It returns an error if a package could not be created
// (e.g. go/build or parse error), but type errors are reported via
// the types.Config.Error callback (the first of which is also saved
// in the package's PackageInfo).
//
// Idempotent.
//
func (imp *importer) doImport(from *PackageInfo, to string) (*types.Package, error) {
// Package unsafe is handled specially, and has no PackageInfo.
// TODO(adonovan): move this check into go/types?
if to == "unsafe" {
return types.Unsafe, nil
}
imp.importedMu.Lock()
ii := imp.imported[to]
imp.importedMu.Unlock()
if ii == nil {
panic("internal error: unexpected import: " + to)
}
if ii.err != nil {
return nil, ii.err
}
if ii.info != nil {
return ii.info.Pkg, nil
}
// Import of incomplete package: this indicates a cycle.
fromPath := from.Pkg.Path()
if cycle := imp.findPath(to, fromPath); cycle != nil {
cycle = append([]string{fromPath}, cycle...)
return nil, fmt.Errorf("import cycle: %s", strings.Join(cycle, " -> "))
}
panic("internal error: import of incomplete (yet acyclic) package: " + fromPath)
}
// loadAll loads, parses, and type-checks the specified packages in
// parallel and returns their completed importInfos in unspecified order.
//
// fromPath is the import path of the importing package, if it is
// importable, "" otherwise. It is used for cycle detection.
//
func (imp *importer) loadAll(fromPath string, paths map[string]bool) []*importInfo {
result := make([]*importInfo, 0, len(paths))
for path := range paths {
result = append(result, imp.startLoad(path))
}
if fromPath != "" {
// We're loading a set of imports.
//
// We must record graph edges from the importing package
// to its dependencies, and check for cycles.
imp.graphMu.Lock()
deps, ok := imp.graph[fromPath]
if !ok {
deps = make(map[string]bool)
imp.graph[fromPath] = deps
}
for path := range paths {
deps[path] = true
}
imp.graphMu.Unlock()
}
for _, ii := range result {
if fromPath != "" {
if cycle := imp.findPath(ii.path, fromPath); cycle != nil {
// Cycle-forming import: we must not await its
// completion since it would deadlock.
//
// We don't record the error in ii since
// the error is really associated with the
// cycle-forming edge, not the package itself.
// (Also it would complicate the
// invariants of importPath completion.)
if trace {
fmt.Fprintln(os.Stderr, "import cycle: %q", cycle)
}
continue
}
}
ii.awaitCompletion()
}
return result
}
// findPath returns an arbitrary path from 'from' to 'to' in the import
// graph, or nil if there was none.
func (imp *importer) findPath(from, to string) []string {
imp.graphMu.Lock()
defer imp.graphMu.Unlock()
seen := make(map[string]bool)
var search func(stack []string, importPath string) []string
search = func(stack []string, importPath string) []string {
if !seen[importPath] {
seen[importPath] = true
stack = append(stack, importPath)
if importPath == to {
return stack
}
for x := range imp.graph[importPath] {
if p := search(stack, x); p != nil {
return p
}
}
}
return nil
}
return search(make([]string, 0, 20), from)
}
// startLoad initiates the loading, parsing and type-checking of the
// specified package and its dependencies, if it has not already begun.
//
// It returns an importInfo, not necessarily in a completed state. The
// caller must call awaitCompletion() before accessing its info and err
// fields.
//
// startLoad is concurrency-safe and idempotent.
//
// Precondition: path != "unsafe".
//
func (imp *importer) startLoad(path string) *importInfo {
imp.importedMu.Lock()
ii, ok := imp.imported[path]
if !ok {
ii = &importInfo{path: path}
ii.complete.L = &ii.mu
imp.imported[path] = ii
go imp.load(path, ii)
}
imp.importedMu.Unlock()
return ii
}
func (imp *importer) load(path string, ii *importInfo) {
var info *PackageInfo
var err error
// Find and create the actual package.
if _, ok := imp.conf.ImportPkgs[path]; ok || !imp.conf.ImportFromBinary {
info, err = imp.loadFromSource(path)
} else {
info, err = imp.importFromBinary(path)
}
ii.Complete(info, err)
}
// importFromBinary implements package loading from the client-supplied
// external source, e.g. object files from the gc compiler.
//
func (imp *importer) importFromBinary(path string) (*PackageInfo, error) {
// Determine the caller's effective Import function.
importfn := imp.conf.TypeChecker.Import
if importfn == nil {
importfn = gcimporter.Import
}
imp.typecheckerMu.Lock()
pkg, err := importfn(imp.conf.TypeChecker.Packages, path)
if pkg != nil {
imp.conf.TypeChecker.Packages[path] = pkg
}
imp.typecheckerMu.Unlock()
if err != nil {
return nil, err
}
info := &PackageInfo{Pkg: pkg}
info.Importable = true
imp.typecheckerMu.Lock()
imp.prog.AllPackages[pkg] = info
imp.typecheckerMu.Unlock()
return info, nil
}
// loadFromSource implements package loading by parsing Go source files
// located by go/build.
// The returned PackageInfo's typeCheck function must be called.
//
func (imp *importer) loadFromSource(path string) (*PackageInfo, error) {
bp, err := imp.conf.FindPackage(imp.conf.build(), path)
if err != nil {
return nil, err // package not found
}
info := imp.newPackageInfo(bp.ImportPath)
info.Importable = true
files, errs := imp.conf.parsePackageFiles(bp, 'g')
for _, err := range errs {
info.appendError(err)
}
imp.addFiles(info, files, true)
imp.typecheckerMu.Lock()
imp.conf.TypeChecker.Packages[path] = info.Pkg
imp.typecheckerMu.Unlock()
return info, nil
}
// addFiles adds and type-checks the specified files to info, loading
// their dependencies if needed. The order of files determines the
// package initialization order. It may be called multiple times on the
// same package. Errors are appended to the info.Errors field.
//
// cycleCheck determines whether the imports within files create
// dependency edges that should be checked for potential cycles.
//
func (imp *importer) addFiles(info *PackageInfo, files []*ast.File, cycleCheck bool) {
info.Files = append(info.Files, files...)
// Ensure the dependencies are loaded, in parallel.
var fromPath string
if cycleCheck {
fromPath = info.Pkg.Path()
}
imp.loadAll(fromPath, scanImports(files))
if trace {
fmt.Fprintf(os.Stderr, "%s: start %q (%d)\n",
time.Since(imp.start), info.Pkg.Path(), len(files))
}
// Ignore the returned (first) error since we
// already collect them all in the PackageInfo.
info.checker.Files(files)
if trace {
fmt.Fprintf(os.Stderr, "%s: stop %q\n",
time.Since(imp.start), info.Pkg.Path())
}
}
func (imp *importer) newPackageInfo(path string) *PackageInfo {
pkg := types.NewPackage(path, "")
if imp.conf.PackageCreated != nil {
imp.conf.PackageCreated(pkg)
}
info := &PackageInfo{
Pkg: pkg,
Info: types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Implicits: make(map[ast.Node]types.Object),
Scopes: make(map[ast.Node]*types.Scope),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
},
errorFunc: imp.conf.TypeChecker.Error,
}
// Copy the types.Config so we can vary it across PackageInfos.
tc := imp.conf.TypeChecker
tc.IgnoreFuncBodies = false
if f := imp.conf.TypeCheckFuncBodies; f != nil {
tc.IgnoreFuncBodies = !f(path)
}
tc.Import = func(_ map[string]*types.Package, to string) (*types.Package, error) {
return imp.doImport(info, to)
}
tc.Error = info.appendError // appendError wraps the user's Error function
info.checker = types.NewChecker(&tc, imp.conf.fset(), pkg, &info.Info)
imp.typecheckerMu.Lock()
imp.prog.AllPackages[pkg] = info
imp.typecheckerMu.Unlock()
return info
}