diff options
author | Kali Kaneko (leap communications) <kali@leap.se> | 2019-01-12 18:39:45 +0100 |
---|---|---|
committer | Ruben Pollan <meskio@sindominio.net> | 2019-01-17 12:30:32 +0100 |
commit | b1247d2d0d51108c910a73891ff3116e5f032ab1 (patch) | |
tree | e9948964f0bfb1ad2df3bc7bad02aa1f41ccfbd8 /vendor/golang.org/x/tools/go/loader | |
parent | efcb8312e31b5c2261b1a1e95ace55b322cfcc27 (diff) |
[pkg] all your deps are vendored to us
Diffstat (limited to 'vendor/golang.org/x/tools/go/loader')
-rw-r--r-- | vendor/golang.org/x/tools/go/loader/cgo.go | 207 | ||||
-rw-r--r-- | vendor/golang.org/x/tools/go/loader/cgo_pkgconfig.go | 39 | ||||
-rw-r--r-- | vendor/golang.org/x/tools/go/loader/doc.go | 205 | ||||
-rw-r--r-- | vendor/golang.org/x/tools/go/loader/example_test.go | 179 | ||||
-rw-r--r-- | vendor/golang.org/x/tools/go/loader/loader.go | 1077 | ||||
-rw-r--r-- | vendor/golang.org/x/tools/go/loader/loader_test.go | 816 | ||||
-rw-r--r-- | vendor/golang.org/x/tools/go/loader/stdlib_test.go | 201 | ||||
-rw-r--r-- | vendor/golang.org/x/tools/go/loader/testdata/a.go | 1 | ||||
-rw-r--r-- | vendor/golang.org/x/tools/go/loader/testdata/b.go | 1 | ||||
-rw-r--r-- | vendor/golang.org/x/tools/go/loader/testdata/badpkgdecl.go | 1 | ||||
-rw-r--r-- | vendor/golang.org/x/tools/go/loader/util.go | 124 |
11 files changed, 2851 insertions, 0 deletions
diff --git a/vendor/golang.org/x/tools/go/loader/cgo.go b/vendor/golang.org/x/tools/go/loader/cgo.go new file mode 100644 index 0000000..72c6f50 --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/cgo.go @@ -0,0 +1,207 @@ +// 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 + +// This file handles cgo preprocessing of files containing `import "C"`. +// +// DESIGN +// +// The approach taken is to run the cgo processor on the package's +// CgoFiles and parse the output, faking the filenames of the +// resulting ASTs so that the synthetic file containing the C types is +// called "C" (e.g. "~/go/src/net/C") and the preprocessed files +// have their original names (e.g. "~/go/src/net/cgo_unix.go"), +// not the names of the actual temporary files. +// +// The advantage of this approach is its fidelity to 'go build'. The +// downside is that the token.Position.Offset for each AST node is +// incorrect, being an offset within the temporary file. Line numbers +// should still be correct because of the //line comments. +// +// The logic of this file is mostly plundered from the 'go build' +// tool, which also invokes the cgo preprocessor. +// +// +// REJECTED ALTERNATIVE +// +// An alternative approach that we explored is to extend go/types' +// Importer mechanism to provide the identity of the importing package +// so that each time `import "C"` appears it resolves to a different +// synthetic package containing just the objects needed in that case. +// The loader would invoke cgo but parse only the cgo_types.go file +// defining the package-level objects, discarding the other files +// resulting from preprocessing. +// +// The benefit of this approach would have been that source-level +// syntax information would correspond exactly to the original cgo +// file, with no preprocessing involved, making source tools like +// godoc, guru, and eg happy. However, the approach was rejected +// due to the additional complexity it would impose on go/types. (It +// made for a beautiful demo, though.) +// +// cgo files, despite their *.go extension, are not legal Go source +// files per the specification since they may refer to unexported +// members of package "C" such as C.int. Also, a function such as +// C.getpwent has in effect two types, one matching its C type and one +// which additionally returns (errno C.int). The cgo preprocessor +// uses name mangling to distinguish these two functions in the +// processed code, but go/types would need to duplicate this logic in +// its handling of function calls, analogous to the treatment of map +// lookups in which y=m[k] and y,ok=m[k] are both legal. + +import ( + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" +) + +// processCgoFiles invokes the cgo preprocessor on bp.CgoFiles, parses +// the output and returns the resulting ASTs. +// +func processCgoFiles(bp *build.Package, fset *token.FileSet, DisplayPath func(path string) string, mode parser.Mode) ([]*ast.File, error) { + tmpdir, err := ioutil.TempDir("", strings.Replace(bp.ImportPath, "/", "_", -1)+"_C") + if err != nil { + return nil, err + } + defer os.RemoveAll(tmpdir) + + pkgdir := bp.Dir + if DisplayPath != nil { + pkgdir = DisplayPath(pkgdir) + } + + cgoFiles, cgoDisplayFiles, err := runCgo(bp, pkgdir, tmpdir) + if err != nil { + return nil, err + } + var files []*ast.File + for i := range cgoFiles { + rd, err := os.Open(cgoFiles[i]) + if err != nil { + return nil, err + } + display := filepath.Join(bp.Dir, cgoDisplayFiles[i]) + f, err := parser.ParseFile(fset, display, rd, mode) + rd.Close() + if err != nil { + return nil, err + } + files = append(files, f) + } + return files, nil +} + +var cgoRe = regexp.MustCompile(`[/\\:]`) + +// runCgo invokes the cgo preprocessor on bp.CgoFiles and returns two +// lists of files: the resulting processed files (in temporary +// directory tmpdir) and the corresponding names of the unprocessed files. +// +// runCgo is adapted from (*builder).cgo in +// $GOROOT/src/cmd/go/build.go, but these features are unsupported: +// Objective C, CGOPKGPATH, CGO_FLAGS. +// +func runCgo(bp *build.Package, pkgdir, tmpdir string) (files, displayFiles []string, err error) { + cgoCPPFLAGS, _, _, _ := cflags(bp, true) + _, cgoexeCFLAGS, _, _ := cflags(bp, false) + + if len(bp.CgoPkgConfig) > 0 { + pcCFLAGS, err := pkgConfigFlags(bp) + if err != nil { + return nil, nil, err + } + cgoCPPFLAGS = append(cgoCPPFLAGS, pcCFLAGS...) + } + + // Allows including _cgo_export.h from .[ch] files in the package. + cgoCPPFLAGS = append(cgoCPPFLAGS, "-I", tmpdir) + + // _cgo_gotypes.go (displayed "C") contains the type definitions. + files = append(files, filepath.Join(tmpdir, "_cgo_gotypes.go")) + displayFiles = append(displayFiles, "C") + for _, fn := range bp.CgoFiles { + // "foo.cgo1.go" (displayed "foo.go") is the processed Go source. + f := cgoRe.ReplaceAllString(fn[:len(fn)-len("go")], "_") + files = append(files, filepath.Join(tmpdir, f+"cgo1.go")) + displayFiles = append(displayFiles, fn) + } + + var cgoflags []string + if bp.Goroot && bp.ImportPath == "runtime/cgo" { + cgoflags = append(cgoflags, "-import_runtime_cgo=false") + } + if bp.Goroot && bp.ImportPath == "runtime/race" || bp.ImportPath == "runtime/cgo" { + cgoflags = append(cgoflags, "-import_syscall=false") + } + + args := stringList( + "go", "tool", "cgo", "-objdir", tmpdir, cgoflags, "--", + cgoCPPFLAGS, cgoexeCFLAGS, bp.CgoFiles, + ) + if false { + log.Printf("Running cgo for package %q: %s (dir=%s)", bp.ImportPath, args, pkgdir) + } + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = pkgdir + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return nil, nil, fmt.Errorf("cgo failed: %s: %s", args, err) + } + + return files, displayFiles, nil +} + +// -- unmodified from 'go build' --------------------------------------- + +// Return the flags to use when invoking the C or C++ compilers, or cgo. +func cflags(p *build.Package, def bool) (cppflags, cflags, cxxflags, ldflags []string) { + var defaults string + if def { + defaults = "-g -O2" + } + + cppflags = stringList(envList("CGO_CPPFLAGS", ""), p.CgoCPPFLAGS) + cflags = stringList(envList("CGO_CFLAGS", defaults), p.CgoCFLAGS) + cxxflags = stringList(envList("CGO_CXXFLAGS", defaults), p.CgoCXXFLAGS) + ldflags = stringList(envList("CGO_LDFLAGS", defaults), p.CgoLDFLAGS) + return +} + +// envList returns the value of the given environment variable broken +// into fields, using the default value when the variable is empty. +func envList(key, def string) []string { + v := os.Getenv(key) + if v == "" { + v = def + } + return strings.Fields(v) +} + +// stringList's arguments should be a sequence of string or []string values. +// stringList flattens them into a single []string. +func stringList(args ...interface{}) []string { + var x []string + for _, arg := range args { + switch arg := arg.(type) { + case []string: + x = append(x, arg...) + case string: + x = append(x, arg) + default: + panic("stringList: invalid argument") + } + } + return x +} diff --git a/vendor/golang.org/x/tools/go/loader/cgo_pkgconfig.go b/vendor/golang.org/x/tools/go/loader/cgo_pkgconfig.go new file mode 100644 index 0000000..de57422 --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/cgo_pkgconfig.go @@ -0,0 +1,39 @@ +// 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 + +import ( + "errors" + "fmt" + "go/build" + "os/exec" + "strings" +) + +// pkgConfig runs pkg-config with the specified arguments and returns the flags it prints. +func pkgConfig(mode string, pkgs []string) (flags []string, err error) { + cmd := exec.Command("pkg-config", append([]string{mode}, pkgs...)...) + out, err := cmd.CombinedOutput() + if err != nil { + s := fmt.Sprintf("%s failed: %v", strings.Join(cmd.Args, " "), err) + if len(out) > 0 { + s = fmt.Sprintf("%s: %s", s, out) + } + return nil, errors.New(s) + } + if len(out) > 0 { + flags = strings.Fields(string(out)) + } + return +} + +// pkgConfigFlags calls pkg-config if needed and returns the cflags +// needed to build the package. +func pkgConfigFlags(p *build.Package) (cflags []string, err error) { + if len(p.CgoPkgConfig) == 0 { + return nil, nil + } + return pkgConfig("--cflags", p.CgoPkgConfig) +} diff --git a/vendor/golang.org/x/tools/go/loader/doc.go b/vendor/golang.org/x/tools/go/loader/doc.go new file mode 100644 index 0000000..9b51c9e --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/doc.go @@ -0,0 +1,205 @@ +// Copyright 2015 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 a complete Go program from source code, parsing +// and type-checking the initial packages plus their transitive closure +// of dependencies. The ASTs and the derived facts are retained for +// later use. +// +// 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() +// +// See examples_test.go for examples of API usage. +// +// +// CONCEPTS AND TERMINOLOGY +// +// The WORKSPACE is the set of packages accessible to the loader. The +// workspace is defined by Config.Build, a *build.Context. The +// default context treats subdirectories of $GOROOT and $GOPATH as +// packages, but this behavior may be overridden. +// +// 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. Every importable package is uniquely identified by its +// PACKAGE PATH or just PATH, a string such as "fmt", "encoding/json", +// or "cmd/vendor/golang.org/x/arch/x86/x86asm". A package path +// typically denotes a subdirectory of the workspace. +// +// An import declaration uses an IMPORT PATH to refer to a package. +// Most import declarations use the package path as the import path. +// +// Due to VENDORING (https://golang.org/s/go15vendor), the +// interpretation of an import path may depend on the directory in which +// it appears. To resolve an import path to a package path, go/build +// must search the enclosing directories for a subdirectory named +// "vendor". +// +// 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 with path "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 + +// IMPLEMENTATION NOTES +// +// '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 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 non-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): overhaul the package documentation. diff --git a/vendor/golang.org/x/tools/go/loader/example_test.go b/vendor/golang.org/x/tools/go/loader/example_test.go new file mode 100644 index 0000000..88adde3 --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/example_test.go @@ -0,0 +1,179 @@ +// Copyright 2015 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. + +// +build go1.8,!go1.9 // TODO(adonovan) determine which versions we need to test here +// +build !windows + +package loader_test + +import ( + "fmt" + "go/token" + "log" + "path/filepath" + "runtime" + "sort" + "strings" + + "golang.org/x/tools/go/loader" +) + +func printProgram(prog *loader.Program) { + // Created packages are the initial packages specified by a call + // to CreateFromFilenames or CreateFromFiles. + var names []string + for _, info := range prog.Created { + names = append(names, info.Pkg.Path()) + } + fmt.Printf("created: %s\n", names) + + // Imported packages are the initial packages specified by a + // call to Import or ImportWithTests. + names = nil + for _, info := range prog.Imported { + if strings.Contains(info.Pkg.Path(), "internal") { + continue // skip, to reduce fragility + } + names = append(names, info.Pkg.Path()) + } + sort.Strings(names) + fmt.Printf("imported: %s\n", names) + + // InitialPackages contains the union of created and imported. + names = nil + for _, info := range prog.InitialPackages() { + names = append(names, info.Pkg.Path()) + } + sort.Strings(names) + fmt.Printf("initial: %s\n", names) + + // AllPackages contains all initial packages and their dependencies. + names = nil + for pkg := range prog.AllPackages { + names = append(names, pkg.Path()) + } + sort.Strings(names) + fmt.Printf("all: %s\n", names) +} + +func printFilenames(fset *token.FileSet, info *loader.PackageInfo) { + var names []string + for _, f := range info.Files { + names = append(names, filepath.Base(fset.File(f.Pos()).Name())) + } + fmt.Printf("%s.Files: %s\n", info.Pkg.Path(), names) +} + +// This example loads a set of packages and all of their dependencies +// from a typical command-line. FromArgs parses a command line and +// makes calls to the other methods of Config shown in the examples that +// follow. +func ExampleConfig_FromArgs() { + args := []string{"mytool", "unicode/utf8", "errors", "runtime", "--", "foo", "bar"} + const wantTests = false + + var conf loader.Config + rest, err := conf.FromArgs(args[1:], wantTests) + prog, err := conf.Load() + if err != nil { + log.Fatal(err) + } + + fmt.Printf("rest: %s\n", rest) + printProgram(prog) + // Output: + // rest: [foo bar] + // created: [] + // imported: [errors runtime unicode/utf8] + // initial: [errors runtime unicode/utf8] + // all: [errors runtime runtime/internal/atomic runtime/internal/sys unicode/utf8 unsafe] +} + +// This example creates and type-checks a single package (without tests) +// from a list of filenames, and loads all of its dependencies. +// (The input files are actually only a small part of the math/cmplx package.) +func ExampleConfig_CreateFromFilenames() { + var conf loader.Config + conf.CreateFromFilenames("math/cmplx", + filepath.Join(runtime.GOROOT(), "src/math/cmplx/abs.go"), + filepath.Join(runtime.GOROOT(), "src/math/cmplx/sin.go")) + prog, err := conf.Load() + if err != nil { + log.Fatal(err) + } + + printProgram(prog) + // Output: + // created: [math/cmplx] + // imported: [] + // initial: [math/cmplx] + // all: [math math/cmplx unsafe] +} + +// In the examples below, for stability, the chosen packages are +// relatively small, platform-independent, and low-level (and thus +// infrequently changing). +// The strconv package has internal and external tests. + +const hello = `package main + +import "fmt" + +func main() { + fmt.Println("Hello, world.") +} +` + +// This example creates and type-checks a package from a list of +// already-parsed files, and loads all its dependencies. +func ExampleConfig_CreateFromFiles() { + var conf loader.Config + f, err := conf.ParseFile("hello.go", hello) + if err != nil { + log.Fatal(err) + } + conf.CreateFromFiles("hello", f) + prog, err := conf.Load() + if err != nil { + log.Fatal(err) + } + + printProgram(prog) + printFilenames(prog.Fset, prog.Package("strconv")) + // Output: + // created: [hello] + // imported: [] + // initial: [hello] + // all: [errors fmt hello internal/race io math os reflect runtime runtime/internal/atomic runtime/internal/sys strconv sync sync/atomic syscall time unicode/utf8 unsafe] + // strconv.Files: [atob.go atof.go atoi.go decimal.go doc.go extfloat.go ftoa.go isprint.go itoa.go quote.go] +} + +// This example imports three packages, including the tests for one of +// them, and loads all their dependencies. +func ExampleConfig_Import() { + // ImportWithTest("strconv") causes strconv to include + // internal_test.go, and creates an external test package, + // strconv_test. + // (Compare with the example of CreateFromFiles.) + + var conf loader.Config + conf.Import("unicode/utf8") + conf.Import("errors") + conf.ImportWithTests("strconv") + prog, err := conf.Load() + if err != nil { + log.Fatal(err) + } + + printProgram(prog) + printFilenames(prog.Fset, prog.Package("strconv")) + printFilenames(prog.Fset, prog.Package("strconv_test")) + // Output: + // created: [strconv_test] + // imported: [errors strconv unicode/utf8] + // initial: [errors strconv strconv_test unicode/utf8] + // all: [bufio bytes errors flag fmt internal/race io log math math/rand os reflect runtime runtime/debug runtime/internal/atomic runtime/internal/sys runtime/trace sort strconv strconv_test strings sync sync/atomic syscall testing time unicode unicode/utf8 unsafe] + // strconv.Files: [atob.go atof.go atoi.go decimal.go doc.go extfloat.go ftoa.go isprint.go itoa.go quote.go internal_test.go] + // strconv_test.Files: [atob_test.go atof_test.go atoi_test.go decimal_test.go example_test.go fp_test.go ftoa_test.go itoa_test.go quote_test.go strconv_test.go] +} diff --git a/vendor/golang.org/x/tools/go/loader/loader.go b/vendor/golang.org/x/tools/go/loader/loader.go new file mode 100644 index 0000000..de756f7 --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/loader.go @@ -0,0 +1,1077 @@ +// 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 + +// See doc.go for package documentation and implementation notes. + +import ( + "errors" + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "go/types" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" + + "golang.org/x/tools/go/ast/astutil" +) + +var ignoreVendor build.ImportMode + +const trace = false // show timing info for type-checking + +// Config specifies the configuration for loading a whole program from +// Go source code. +// 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. + // The supplied Import function is not used either. + TypeChecker types.Config + + // TypeCheckFuncBodies is a predicate over package 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(path string) 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. + // The map keys are package paths. + // + // 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 from a given directory. + // If FindPackage is nil, (*build.Context).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, fromDir string, mode build.ImportMode) (*build.Package, error) + + // AfterTypeCheck is called immediately after a list of files + // has been type-checked and appended to info.Files. + // + // This optional hook function is the earliest opportunity for + // the client to observe the output of the type checker, + // which may be useful to reduce analysis latency when loading + // a large program. + // + // The function is permitted to modify info.Info, for instance + // to clear data structures that are no longer needed, which can + // dramatically reduce peak memory consumption. + // + // The function may be called twice for the same PackageInfo: + // once for the files of the package and again for the + // in-package test files. + // + // It must be safe to call concurrently from multiple goroutines. + AfterTypeCheck func(info *PackageInfo, files []*ast.File) +} + +// 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. +// +// For vendoring purposes, the package's directory is the one that +// contains the first file. +type PkgSpec struct { + Path string // package 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 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. + // + // NOTE: these files must not import "C". Cgo preprocessing is + // only performed on imported packages, not ad hoc packages. + // + // TODO(adonovan): we need to copy and adapt the logic of + // goFilesPackage (from $GOROOT/src/cmd/go/build.go) and make + // Config.Import and Config.Create methods return the same kind + // of entity, essentially a build.Package. + // Perhaps we can even reuse that type directly. + Created []*PackageInfo + + // Imported contains the initially imported packages, + // as specified by Config.ImportPkgs. + Imported map[string]*PackageInfo + + // AllPackages contains the PackageInfo of every package + // encountered by Load: all initial packages and all + // dependencies, including incomplete ones. + AllPackages map[*types.Package]*PackageInfo + + // importMap is the canonical mapping of package paths to + // packages. It contains all Imported initial packages, but not + // Created ones, and all imported dependencies. + importMap map[string]*types.Package +} + +// 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. + dir string // package directory + + 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 == "C" { + 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 f.Pos() == token.NoPos { + // This can happen if the parser saw + // too many errors and bailed out. + // (Use parser.AllErrors to prevent that.) + continue + } + 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 +} + +// Package returns the ASTs and results of type checking for the +// specified package. +func (prog *Program) Package(path string) *PackageInfo { + if info, ok := prog.AllPackages[prog.importMap[path]]; ok { + return info + } + for _, info := range prog.Created { + if path == info.Pkg.Path() { + return info + } + } + return nil +} + +// ---------- Implementation ---------- + +// importer holds the working state of the algorithm. +type importer struct { + conf *Config // the client configuration + start time.Time // for logging + + progMu sync.Mutex // guards prog + prog *Program // the resulting program + + // findpkg is a memoization of FindPackage. + findpkgMu sync.Mutex // guards findpkg + findpkg map[findpkgKey]*findpkgValue + + importedMu sync.Mutex // guards imported + 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 +} + +type findpkgKey struct { + importPath string + fromDir string + mode build.ImportMode +} + +type findpkgValue struct { + ready chan struct{} // closed to broadcast readiness + bp *build.Package + err error +} + +// 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 + info *PackageInfo // results of typechecking (including errors) + complete chan struct{} // closed to broadcast that info is set. +} + +// awaitCompletion blocks until ii is complete, +// i.e. the info field is safe to inspect. +func (ii *importInfo) awaitCompletion() { + <-ii.complete // wait for close +} + +// Complete marks ii as complete. +// Its info and err fields will not be subsequently updated. +func (ii *importInfo) Complete(info *PackageInfo) { + if info == nil { + panic("info == nil") + } + ii.info = info + close(ii.complete) +} + +type importError struct { + path string // import path + err error // reason for failure to create a package +} + +// 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) { + // 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 = (*build.Context).Import + } + + prog := &Program{ + Fset: conf.fset(), + Imported: make(map[string]*PackageInfo), + importMap: make(map[string]*types.Package), + AllPackages: make(map[*types.Package]*PackageInfo), + } + + imp := importer{ + conf: conf, + prog: prog, + findpkg: make(map[findpkgKey]*findpkgValue), + 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. + // No vendor check on packages imported from the command line. + infos, importErrors := imp.importAll("", conf.Cwd, conf.ImportPkgs, ignoreVendor) + for _, ie := range importErrors { + conf.TypeChecker.Error(ie.err) // failed to create package + errpkgs = append(errpkgs, ie.path) + } + for _, info := range infos { + prog.Imported[info.Pkg.Path()] = info + } + + // Augment the designated initial packages by their tests. + // Dependencies are loaded in parallel. + var xtestPkgs []*build.Package + for importPath, augment := range conf.ImportPkgs { + if !augment { + continue + } + + // No vendor check on packages imported from command line. + bp, err := imp.findPackage(importPath, conf.Cwd, ignoreVendor) + 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) + } + + // Consult the cache using the canonical package path. + path := bp.ImportPath + imp.importedMu.Lock() // (unnecessary, we're sequential here) + ii, ok := imp.imported[path] + // Paranoid checks added due to issue #11012. + if !ok { + // Unreachable. + // The previous loop called importAll and thus + // startLoad for each path in ImportPkgs, which + // populates imp.imported[path] with a non-zero value. + panic(fmt.Sprintf("imported[%q] not found", path)) + } + if ii == nil { + // Unreachable. + // The ii values in this loop are the same as in + // the previous loop, which enforced the invariant + // that at least one of ii.err and ii.info is non-nil. + panic(fmt.Sprintf("imported[%q] == nil", path)) + } + if ii.info == nil { + // Unreachable. + // awaitCompletion has the postcondition + // ii.info != nil. + panic(fmt.Sprintf("imported[%q].info = nil", path)) + } + info := ii.info + 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, dir string, files []*ast.File, errs []error) { + info := imp.newPackageInfo(path, dir) + 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, conf.Cwd, 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)" + } + } + + dir := conf.Cwd + if len(files) > 0 && files[0].Pos().IsValid() { + dir = filepath.Dir(conf.fset().File(files[0].Pos()).Name()) + } + createPkg(path, dir, 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", bp.Dir, 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) { + if bp.ImportPath == "unsafe" { + return nil, nil + } + 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. +// +// 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) { + if to == "C" { + // This should be unreachable, but ad hoc packages are + // not currently subject to cgo preprocessing. + // See https://github.com/golang/go/issues/11627. + return nil, fmt.Errorf(`the loader doesn't cgo-process ad hoc packages like %q; see Go issue 11627`, + from.Pkg.Path()) + } + + bp, err := imp.findPackage(to, from.dir, 0) + if err != nil { + return nil, err + } + + // The standard unsafe package is handled specially, + // and has no PackageInfo. + if bp.ImportPath == "unsafe" { + return types.Unsafe, nil + } + + // Look for the package in the cache using its canonical path. + path := bp.ImportPath + imp.importedMu.Lock() + ii := imp.imported[path] + imp.importedMu.Unlock() + if ii == nil { + panic("internal error: unexpected import: " + path) + } + 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(path, 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) +} + +// findPackage locates the package denoted by the importPath in the +// specified directory. +func (imp *importer) findPackage(importPath, fromDir string, mode build.ImportMode) (*build.Package, error) { + // We use a non-blocking duplicate-suppressing cache (gopl.io §9.7) + // to avoid holding the lock around FindPackage. + key := findpkgKey{importPath, fromDir, mode} + imp.findpkgMu.Lock() + v, ok := imp.findpkg[key] + if ok { + // cache hit + imp.findpkgMu.Unlock() + + <-v.ready // wait for entry to become ready + } else { + // Cache miss: this goroutine becomes responsible for + // populating the map entry and broadcasting its readiness. + v = &findpkgValue{ready: make(chan struct{})} + imp.findpkg[key] = v + imp.findpkgMu.Unlock() + + ioLimit <- true + v.bp, v.err = imp.conf.FindPackage(imp.conf.build(), importPath, fromDir, mode) + <-ioLimit + + if _, ok := v.err.(*build.NoGoError); ok { + v.err = nil // empty directory is not an error + } + + close(v.ready) // broadcast ready condition + } + return v.bp, v.err +} + +// importAll loads, parses, and type-checks the specified packages in +// parallel and returns their completed importInfos in unspecified order. +// +// fromPath is the package path of the importing package, if it is +// importable, "" otherwise. It is used for cycle detection. +// +// fromDir is the directory containing the import declaration that +// caused these imports. +// +func (imp *importer) importAll(fromPath, fromDir string, imports map[string]bool, mode build.ImportMode) (infos []*PackageInfo, errors []importError) { + // TODO(adonovan): opt: do the loop in parallel once + // findPackage is non-blocking. + var pending []*importInfo + for importPath := range imports { + bp, err := imp.findPackage(importPath, fromDir, mode) + if err != nil { + errors = append(errors, importError{ + path: importPath, + err: err, + }) + continue + } + pending = append(pending, imp.startLoad(bp)) + } + + 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 _, ii := range pending { + deps[ii.path] = true + } + imp.graphMu.Unlock() + } + + for _, ii := range pending { + 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.Fprintf(os.Stderr, "import cycle: %q\n", cycle) + } + continue + } + } + ii.awaitCompletion() + infos = append(infos, ii.info) + } + + return infos, errors +} + +// 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 field. +// +// startLoad is concurrency-safe and idempotent. +// +func (imp *importer) startLoad(bp *build.Package) *importInfo { + path := bp.ImportPath + imp.importedMu.Lock() + ii, ok := imp.imported[path] + if !ok { + ii = &importInfo{path: path, complete: make(chan struct{})} + imp.imported[path] = ii + go func() { + info := imp.load(bp) + ii.Complete(info) + }() + } + imp.importedMu.Unlock() + + return ii +} + +// load implements package loading by parsing Go source files +// located by go/build. +func (imp *importer) load(bp *build.Package) *PackageInfo { + info := imp.newPackageInfo(bp.ImportPath, bp.Dir) + info.Importable = true + files, errs := imp.conf.parsePackageFiles(bp, 'g') + for _, err := range errs { + info.appendError(err) + } + + imp.addFiles(info, files, true) + + imp.progMu.Lock() + imp.prog.importMap[bp.ImportPath] = info.Pkg + imp.progMu.Unlock() + + return info +} + +// 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) { + // Ensure the dependencies are loaded, in parallel. + var fromPath string + if cycleCheck { + fromPath = info.Pkg.Path() + } + // TODO(adonovan): opt: make the caller do scanImports. + // Callers with a build.Package can skip it. + imp.importAll(fromPath, info.dir, scanImports(files), 0) + + if trace { + fmt.Fprintf(os.Stderr, "%s: start %q (%d)\n", + time.Since(imp.start), info.Pkg.Path(), len(files)) + } + + // Don't call checker.Files on Unsafe, even with zero files, + // because it would mutate the package, which is a global. + if info.Pkg == types.Unsafe { + if len(files) > 0 { + panic(`"unsafe" package contains unexpected files`) + } + } else { + // Ignore the returned (first) error since we + // already collect them all in the PackageInfo. + info.checker.Files(files) + info.Files = append(info.Files, files...) + } + + if imp.conf.AfterTypeCheck != nil { + imp.conf.AfterTypeCheck(info, files) + } + + if trace { + fmt.Fprintf(os.Stderr, "%s: stop %q\n", + time.Since(imp.start), info.Pkg.Path()) + } +} + +func (imp *importer) newPackageInfo(path, dir string) *PackageInfo { + var pkg *types.Package + if path == "unsafe" { + pkg = types.Unsafe + } else { + pkg = types.NewPackage(path, "") + } + 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, + dir: dir, + } + + // 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.Importer = closure{imp, info} + tc.Error = info.appendError // appendError wraps the user's Error function + + info.checker = types.NewChecker(&tc, imp.conf.fset(), pkg, &info.Info) + imp.progMu.Lock() + imp.prog.AllPackages[pkg] = info + imp.progMu.Unlock() + return info +} + +type closure struct { + imp *importer + info *PackageInfo +} + +func (c closure) Import(to string) (*types.Package, error) { return c.imp.doImport(c.info, to) } diff --git a/vendor/golang.org/x/tools/go/loader/loader_test.go b/vendor/golang.org/x/tools/go/loader/loader_test.go new file mode 100644 index 0000000..02630eb --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/loader_test.go @@ -0,0 +1,816 @@ +// 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. + +// No testdata on Android. + +// +build !android + +package loader_test + +import ( + "fmt" + "go/build" + "go/constant" + "go/types" + "path/filepath" + "reflect" + "sort" + "strings" + "sync" + "testing" + + "golang.org/x/tools/go/buildutil" + "golang.org/x/tools/go/loader" +) + +// TestFromArgs checks that conf.FromArgs populates conf correctly. +// It does no I/O. +func TestFromArgs(t *testing.T) { + type result struct { + Err string + Rest []string + ImportPkgs map[string]bool + CreatePkgs []loader.PkgSpec + } + for _, test := range []struct { + args []string + tests bool + want result + }{ + // Mix of existing and non-existent packages. + { + args: []string{"nosuchpkg", "errors"}, + want: result{ + ImportPkgs: map[string]bool{"errors": false, "nosuchpkg": false}, + }, + }, + // Same, with -test flag. + { + args: []string{"nosuchpkg", "errors"}, + tests: true, + want: result{ + ImportPkgs: map[string]bool{"errors": true, "nosuchpkg": true}, + }, + }, + // Surplus arguments. + { + args: []string{"fmt", "errors", "--", "surplus"}, + want: result{ + Rest: []string{"surplus"}, + ImportPkgs: map[string]bool{"errors": false, "fmt": false}, + }, + }, + // Ad hoc package specified as *.go files. + { + args: []string{"foo.go", "bar.go"}, + want: result{CreatePkgs: []loader.PkgSpec{{ + Filenames: []string{"foo.go", "bar.go"}, + }}}, + }, + // Mixture of *.go and import paths. + { + args: []string{"foo.go", "fmt"}, + want: result{ + Err: "named files must be .go files: fmt", + }, + }, + } { + var conf loader.Config + rest, err := conf.FromArgs(test.args, test.tests) + got := result{ + Rest: rest, + ImportPkgs: conf.ImportPkgs, + CreatePkgs: conf.CreatePkgs, + } + if err != nil { + got.Err = err.Error() + } + if !reflect.DeepEqual(got, test.want) { + t.Errorf("FromArgs(%q) = %+v, want %+v", test.args, got, test.want) + } + } +} + +func TestLoad_NoInitialPackages(t *testing.T) { + var conf loader.Config + + const wantErr = "no initial packages were loaded" + + prog, err := conf.Load() + if err == nil { + t.Errorf("Load succeeded unexpectedly, want %q", wantErr) + } else if err.Error() != wantErr { + t.Errorf("Load failed with wrong error %q, want %q", err, wantErr) + } + if prog != nil { + t.Errorf("Load unexpectedly returned a Program") + } +} + +func TestLoad_MissingInitialPackage(t *testing.T) { + var conf loader.Config + conf.Import("nosuchpkg") + conf.Import("errors") + + const wantErr = "couldn't load packages due to errors: nosuchpkg" + + prog, err := conf.Load() + if err == nil { + t.Errorf("Load succeeded unexpectedly, want %q", wantErr) + } else if err.Error() != wantErr { + t.Errorf("Load failed with wrong error %q, want %q", err, wantErr) + } + if prog != nil { + t.Errorf("Load unexpectedly returned a Program") + } +} + +func TestLoad_MissingInitialPackage_AllowErrors(t *testing.T) { + var conf loader.Config + conf.AllowErrors = true + conf.Import("nosuchpkg") + conf.ImportWithTests("errors") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed unexpectedly: %v", err) + } + if prog == nil { + t.Fatalf("Load returned a nil Program") + } + if got, want := created(prog), "errors_test"; got != want { + t.Errorf("Created = %s, want %s", got, want) + } + if got, want := imported(prog), "errors"; got != want { + t.Errorf("Imported = %s, want %s", got, want) + } +} + +func TestCreateUnnamedPackage(t *testing.T) { + var conf loader.Config + conf.CreateFromFilenames("") + prog, err := conf.Load() + if err != nil { + t.Fatalf("Load failed: %v", err) + } + if got, want := fmt.Sprint(prog.InitialPackages()), "[(unnamed)]"; got != want { + t.Errorf("InitialPackages = %s, want %s", got, want) + } +} + +func TestLoad_MissingFileInCreatedPackage(t *testing.T) { + var conf loader.Config + conf.CreateFromFilenames("", "missing.go") + + const wantErr = "couldn't load packages due to errors: (unnamed)" + + prog, err := conf.Load() + if prog != nil { + t.Errorf("Load unexpectedly returned a Program") + } + if err == nil { + t.Fatalf("Load succeeded unexpectedly, want %q", wantErr) + } + if err.Error() != wantErr { + t.Fatalf("Load failed with wrong error %q, want %q", err, wantErr) + } +} + +func TestLoad_MissingFileInCreatedPackage_AllowErrors(t *testing.T) { + conf := loader.Config{AllowErrors: true} + conf.CreateFromFilenames("", "missing.go") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed: %v", err) + } + if got, want := fmt.Sprint(prog.InitialPackages()), "[(unnamed)]"; got != want { + t.Fatalf("InitialPackages = %s, want %s", got, want) + } +} + +func TestLoad_ParseError(t *testing.T) { + var conf loader.Config + conf.CreateFromFilenames("badpkg", "testdata/badpkgdecl.go") + + const wantErr = "couldn't load packages due to errors: badpkg" + + prog, err := conf.Load() + if prog != nil { + t.Errorf("Load unexpectedly returned a Program") + } + if err == nil { + t.Fatalf("Load succeeded unexpectedly, want %q", wantErr) + } + if err.Error() != wantErr { + t.Fatalf("Load failed with wrong error %q, want %q", err, wantErr) + } +} + +func TestLoad_ParseError_AllowErrors(t *testing.T) { + var conf loader.Config + conf.AllowErrors = true + conf.CreateFromFilenames("badpkg", "testdata/badpkgdecl.go") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed unexpectedly: %v", err) + } + if prog == nil { + t.Fatalf("Load returned a nil Program") + } + if got, want := created(prog), "badpkg"; got != want { + t.Errorf("Created = %s, want %s", got, want) + } + + badpkg := prog.Created[0] + if len(badpkg.Files) != 1 { + t.Errorf("badpkg has %d files, want 1", len(badpkg.Files)) + } + wantErr := filepath.Join("testdata", "badpkgdecl.go") + ":1:34: expected 'package', found 'EOF'" + if !hasError(badpkg.Errors, wantErr) { + t.Errorf("badpkg.Errors = %v, want %s", badpkg.Errors, wantErr) + } +} + +func TestLoad_FromSource_Success(t *testing.T) { + var conf loader.Config + conf.CreateFromFilenames("P", "testdata/a.go", "testdata/b.go") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed unexpectedly: %v", err) + } + if prog == nil { + t.Fatalf("Load returned a nil Program") + } + if got, want := created(prog), "P"; got != want { + t.Errorf("Created = %s, want %s", got, want) + } +} + +func TestLoad_FromImports_Success(t *testing.T) { + var conf loader.Config + conf.ImportWithTests("fmt") + conf.ImportWithTests("errors") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed unexpectedly: %v", err) + } + if prog == nil { + t.Fatalf("Load returned a nil Program") + } + if got, want := created(prog), "errors_test fmt_test"; got != want { + t.Errorf("Created = %q, want %s", got, want) + } + if got, want := imported(prog), "errors fmt"; got != want { + t.Errorf("Imported = %s, want %s", got, want) + } + // Check set of transitive packages. + // There are >30 and the set may grow over time, so only check a few. + want := map[string]bool{ + "strings": true, + "time": true, + "runtime": true, + "testing": true, + "unicode": true, + } + for _, path := range all(prog) { + delete(want, path) + } + if len(want) > 0 { + t.Errorf("AllPackages is missing these keys: %q", keys(want)) + } +} + +func TestLoad_MissingIndirectImport(t *testing.T) { + pkgs := map[string]string{ + "a": `package a; import _ "b"`, + "b": `package b; import _ "c"`, + } + conf := loader.Config{Build: fakeContext(pkgs)} + conf.Import("a") + + const wantErr = "couldn't load packages due to errors: b" + + prog, err := conf.Load() + if err == nil { + t.Errorf("Load succeeded unexpectedly, want %q", wantErr) + } else if err.Error() != wantErr { + t.Errorf("Load failed with wrong error %q, want %q", err, wantErr) + } + if prog != nil { + t.Errorf("Load unexpectedly returned a Program") + } +} + +func TestLoad_BadDependency_AllowErrors(t *testing.T) { + for _, test := range []struct { + descr string + pkgs map[string]string + wantPkgs string + }{ + + { + descr: "missing dependency", + pkgs: map[string]string{ + "a": `package a; import _ "b"`, + "b": `package b; import _ "c"`, + }, + wantPkgs: "a b", + }, + { + descr: "bad package decl in dependency", + pkgs: map[string]string{ + "a": `package a; import _ "b"`, + "b": `package b; import _ "c"`, + "c": `package`, + }, + wantPkgs: "a b", + }, + { + descr: "parse error in dependency", + pkgs: map[string]string{ + "a": `package a; import _ "b"`, + "b": `package b; import _ "c"`, + "c": `package c; var x = `, + }, + wantPkgs: "a b c", + }, + } { + conf := loader.Config{ + AllowErrors: true, + Build: fakeContext(test.pkgs), + } + conf.Import("a") + + prog, err := conf.Load() + if err != nil { + t.Errorf("%s: Load failed unexpectedly: %v", test.descr, err) + } + if prog == nil { + t.Fatalf("%s: Load returned a nil Program", test.descr) + } + + if got, want := imported(prog), "a"; got != want { + t.Errorf("%s: Imported = %s, want %s", test.descr, got, want) + } + if got := all(prog); strings.Join(got, " ") != test.wantPkgs { + t.Errorf("%s: AllPackages = %s, want %s", test.descr, got, test.wantPkgs) + } + } +} + +func TestCwd(t *testing.T) { + ctxt := fakeContext(map[string]string{"one/two/three": `package three`}) + for _, test := range []struct { + cwd, arg, want string + }{ + {cwd: "/go/src/one", arg: "./two/three", want: "one/two/three"}, + {cwd: "/go/src/one", arg: "../one/two/three", want: "one/two/three"}, + {cwd: "/go/src/one", arg: "one/two/three", want: "one/two/three"}, + {cwd: "/go/src/one/two/three", arg: ".", want: "one/two/three"}, + {cwd: "/go/src/one", arg: "two/three", want: ""}, + } { + conf := loader.Config{ + Cwd: test.cwd, + Build: ctxt, + } + conf.Import(test.arg) + + var got string + prog, err := conf.Load() + if prog != nil { + got = imported(prog) + } + if got != test.want { + t.Errorf("Load(%s) from %s: Imported = %s, want %s", + test.arg, test.cwd, got, test.want) + if err != nil { + t.Errorf("Load failed: %v", err) + } + } + } +} + +func TestLoad_vendor(t *testing.T) { + pkgs := map[string]string{ + "a": `package a; import _ "x"`, + "a/vendor": ``, // mkdir a/vendor + "a/vendor/x": `package xa`, + "b": `package b; import _ "x"`, + "b/vendor": ``, // mkdir b/vendor + "b/vendor/x": `package xb`, + "c": `package c; import _ "x"`, + "x": `package xc`, + } + conf := loader.Config{Build: fakeContext(pkgs)} + conf.Import("a") + conf.Import("b") + conf.Import("c") + + prog, err := conf.Load() + if err != nil { + t.Fatal(err) + } + + // Check that a, b, and c see different versions of x. + for _, r := range "abc" { + name := string(r) + got := prog.Package(name).Pkg.Imports()[0] + want := "x" + name + if got.Name() != want { + t.Errorf("package %s import %q = %s, want %s", + name, "x", got.Name(), want) + } + } +} + +func TestVendorCwd(t *testing.T) { + // Test the interaction of cwd and vendor directories. + ctxt := fakeContext(map[string]string{ + "net": ``, // mkdir net + "net/http": `package http; import _ "hpack"`, + "vendor": ``, // mkdir vendor + "vendor/hpack": `package vendorhpack`, + "hpack": `package hpack`, + }) + for i, test := range []struct { + cwd, arg, want string + }{ + {cwd: "/go/src/net", arg: "http"}, // not found + {cwd: "/go/src/net", arg: "./http", want: "net/http vendor/hpack"}, + {cwd: "/go/src/net", arg: "hpack", want: "vendor/hpack"}, + {cwd: "/go/src/vendor", arg: "hpack", want: "vendor/hpack"}, + {cwd: "/go/src/vendor", arg: "./hpack", want: "vendor/hpack"}, + } { + conf := loader.Config{ + Cwd: test.cwd, + Build: ctxt, + } + conf.Import(test.arg) + + var got string + prog, err := conf.Load() + if prog != nil { + got = strings.Join(all(prog), " ") + } + if got != test.want { + t.Errorf("#%d: Load(%s) from %s: got %s, want %s", + i, test.arg, test.cwd, got, test.want) + if err != nil { + t.Errorf("Load failed: %v", err) + } + } + } +} + +func TestVendorCwdIssue16580(t *testing.T) { + // Regression test for Go issue 16580. + // Import decls in "created" packages were vendor-resolved + // w.r.t. cwd, not the parent directory of the package's files. + ctxt := fakeContext(map[string]string{ + "a": ``, // mkdir a + "a/vendor": ``, // mkdir a/vendor + "a/vendor/b": `package b; const X = true`, + "b": `package b; const X = false`, + }) + for _, test := range []struct { + filename, cwd string + want bool // expected value of b.X; depends on filename, not on cwd + }{ + {filename: "c.go", cwd: "/go/src", want: false}, + {filename: "c.go", cwd: "/go/src/a", want: false}, + {filename: "c.go", cwd: "/go/src/a/b", want: false}, + {filename: "c.go", cwd: "/go/src/a/vendor/b", want: false}, + + {filename: "/go/src/a/c.go", cwd: "/go/src", want: true}, + {filename: "/go/src/a/c.go", cwd: "/go/src/a", want: true}, + {filename: "/go/src/a/c.go", cwd: "/go/src/a/b", want: true}, + {filename: "/go/src/a/c.go", cwd: "/go/src/a/vendor/b", want: true}, + + {filename: "/go/src/c/c.go", cwd: "/go/src", want: false}, + {filename: "/go/src/c/c.go", cwd: "/go/src/a", want: false}, + {filename: "/go/src/c/c.go", cwd: "/go/src/a/b", want: false}, + {filename: "/go/src/c/c.go", cwd: "/go/src/a/vendor/b", want: false}, + } { + conf := loader.Config{ + Cwd: test.cwd, + Build: ctxt, + } + f, err := conf.ParseFile(test.filename, `package dummy; import "b"; const X = b.X`) + if err != nil { + t.Fatal(f) + } + conf.CreateFromFiles("dummy", f) + + prog, err := conf.Load() + if err != nil { + t.Errorf("%+v: Load failed: %v", test, err) + continue + } + + x := constant.BoolVal(prog.Created[0].Pkg.Scope().Lookup("X").(*types.Const).Val()) + if x != test.want { + t.Errorf("%+v: b.X = %t", test, x) + } + } + + // TODO(adonovan): also test imports within XTestGoFiles. +} + +// TODO(adonovan): more Load tests: +// +// failures: +// - to parse package decl of *_test.go files +// - to parse package decl of external *_test.go files +// - to parse whole of *_test.go files +// - to parse whole of external *_test.go files +// - to open a *.go file during import scanning +// - to import from binary + +// features: +// - InitialPackages +// - PackageCreated hook +// - TypeCheckFuncBodies hook + +func TestTransitivelyErrorFreeFlag(t *testing.T) { + // Create an minimal custom build.Context + // that fakes the following packages: + // + // a --> b --> c! c has an error + // \ d and e are transitively error-free. + // e --> d + // + // Each package [a-e] consists of one file, x.go. + pkgs := map[string]string{ + "a": `package a; import (_ "b"; _ "e")`, + "b": `package b; import _ "c"`, + "c": `package c; func f() { _ = int(false) }`, // type error within function body + "d": `package d;`, + "e": `package e; import _ "d"`, + } + conf := loader.Config{ + AllowErrors: true, + Build: fakeContext(pkgs), + } + conf.Import("a") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed: %s", err) + } + if prog == nil { + t.Fatalf("Load returned nil *Program") + } + + for pkg, info := range prog.AllPackages { + var wantErr, wantTEF bool + switch pkg.Path() { + case "a", "b": + case "c": + wantErr = true + case "d", "e": + wantTEF = true + default: + t.Errorf("unexpected package: %q", pkg.Path()) + continue + } + + if (info.Errors != nil) != wantErr { + if wantErr { + t.Errorf("Package %q.Error = nil, want error", pkg.Path()) + } else { + t.Errorf("Package %q has unexpected Errors: %v", + pkg.Path(), info.Errors) + } + } + + if info.TransitivelyErrorFree != wantTEF { + t.Errorf("Package %q.TransitivelyErrorFree=%t, want %t", + pkg.Path(), info.TransitivelyErrorFree, wantTEF) + } + } +} + +// Test that syntax (scan/parse), type, and loader errors are recorded +// (in PackageInfo.Errors) and reported (via Config.TypeChecker.Error). +func TestErrorReporting(t *testing.T) { + pkgs := map[string]string{ + "a": `package a; import (_ "b"; _ "c"); var x int = false`, + "b": `package b; 'syntax error!`, + } + conf := loader.Config{ + AllowErrors: true, + Build: fakeContext(pkgs), + } + var mu sync.Mutex + var allErrors []error + conf.TypeChecker.Error = func(err error) { + mu.Lock() + allErrors = append(allErrors, err) + mu.Unlock() + } + conf.Import("a") + + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed: %s", err) + } + if prog == nil { + t.Fatalf("Load returned nil *Program") + } + + // TODO(adonovan): test keys of ImportMap. + + // Check errors recorded in each PackageInfo. + for pkg, info := range prog.AllPackages { + switch pkg.Path() { + case "a": + if !hasError(info.Errors, "cannot convert false") { + t.Errorf("a.Errors = %v, want bool conversion (type) error", info.Errors) + } + if !hasError(info.Errors, "could not import c") { + t.Errorf("a.Errors = %v, want import (loader) error", info.Errors) + } + case "b": + if !hasError(info.Errors, "rune literal not terminated") { + t.Errorf("b.Errors = %v, want unterminated literal (syntax) error", info.Errors) + } + } + } + + // Check errors reported via error handler. + if !hasError(allErrors, "cannot convert false") || + !hasError(allErrors, "rune literal not terminated") || + !hasError(allErrors, "could not import c") { + t.Errorf("allErrors = %v, want syntax, type and loader errors", allErrors) + } +} + +func TestCycles(t *testing.T) { + for _, test := range []struct { + descr string + ctxt *build.Context + wantErr string + }{ + { + "self-cycle", + fakeContext(map[string]string{ + "main": `package main; import _ "selfcycle"`, + "selfcycle": `package selfcycle; import _ "selfcycle"`, + }), + `import cycle: selfcycle -> selfcycle`, + }, + { + "three-package cycle", + fakeContext(map[string]string{ + "main": `package main; import _ "a"`, + "a": `package a; import _ "b"`, + "b": `package b; import _ "c"`, + "c": `package c; import _ "a"`, + }), + `import cycle: c -> a -> b -> c`, + }, + { + "self-cycle in dependency of test file", + buildutil.FakeContext(map[string]map[string]string{ + "main": { + "main.go": `package main`, + "main_test.go": `package main; import _ "a"`, + }, + "a": { + "a.go": `package a; import _ "a"`, + }, + }), + `import cycle: a -> a`, + }, + // TODO(adonovan): fix: these fail + // { + // "two-package cycle in dependency of test file", + // buildutil.FakeContext(map[string]map[string]string{ + // "main": { + // "main.go": `package main`, + // "main_test.go": `package main; import _ "a"`, + // }, + // "a": { + // "a.go": `package a; import _ "main"`, + // }, + // }), + // `import cycle: main -> a -> main`, + // }, + // { + // "self-cycle in augmented package", + // buildutil.FakeContext(map[string]map[string]string{ + // "main": { + // "main.go": `package main`, + // "main_test.go": `package main; import _ "main"`, + // }, + // }), + // `import cycle: main -> main`, + // }, + } { + conf := loader.Config{ + AllowErrors: true, + Build: test.ctxt, + } + var mu sync.Mutex + var allErrors []error + conf.TypeChecker.Error = func(err error) { + mu.Lock() + allErrors = append(allErrors, err) + mu.Unlock() + } + conf.ImportWithTests("main") + + prog, err := conf.Load() + if err != nil { + t.Errorf("%s: Load failed: %s", test.descr, err) + } + if prog == nil { + t.Fatalf("%s: Load returned nil *Program", test.descr) + } + + if !hasError(allErrors, test.wantErr) { + t.Errorf("%s: Load() errors = %q, want %q", + test.descr, allErrors, test.wantErr) + } + } + + // TODO(adonovan): + // - Test that in a legal test cycle, none of the symbols + // defined by augmentation are visible via import. +} + +// ---- utilities ---- + +// Simplifying wrapper around buildutil.FakeContext for single-file packages. +func fakeContext(pkgs map[string]string) *build.Context { + pkgs2 := make(map[string]map[string]string) + for path, content := range pkgs { + pkgs2[path] = map[string]string{"x.go": content} + } + return buildutil.FakeContext(pkgs2) +} + +func hasError(errors []error, substr string) bool { + for _, err := range errors { + if strings.Contains(err.Error(), substr) { + return true + } + } + return false +} + +func keys(m map[string]bool) (keys []string) { + for key := range m { + keys = append(keys, key) + } + sort.Strings(keys) + return +} + +// Returns all loaded packages. +func all(prog *loader.Program) []string { + var pkgs []string + for _, info := range prog.AllPackages { + pkgs = append(pkgs, info.Pkg.Path()) + } + sort.Strings(pkgs) + return pkgs +} + +// Returns initially imported packages, as a string. +func imported(prog *loader.Program) string { + var pkgs []string + for _, info := range prog.Imported { + pkgs = append(pkgs, info.Pkg.Path()) + } + sort.Strings(pkgs) + return strings.Join(pkgs, " ") +} + +// Returns initially created packages, as a string. +func created(prog *loader.Program) string { + var pkgs []string + for _, info := range prog.Created { + pkgs = append(pkgs, info.Pkg.Path()) + } + return strings.Join(pkgs, " ") +} + +// Load package "io" twice in parallel. +// When run with -race, this is a regression test for Go issue 20718, in +// which the global "unsafe" package was modified concurrently. +func TestLoad1(t *testing.T) { loadIO(t) } +func TestLoad2(t *testing.T) { loadIO(t) } + +func loadIO(t *testing.T) { + t.Parallel() + conf := &loader.Config{ImportPkgs: map[string]bool{"io": false}} + if _, err := conf.Load(); err != nil { + t.Fatal(err) + } +} diff --git a/vendor/golang.org/x/tools/go/loader/stdlib_test.go b/vendor/golang.org/x/tools/go/loader/stdlib_test.go new file mode 100644 index 0000000..2cd066f --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/stdlib_test.go @@ -0,0 +1,201 @@ +// 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_test + +// This file enumerates all packages beneath $GOROOT, loads them, plus +// their external tests if any, runs the type checker on them, and +// prints some summary information. + +import ( + "bytes" + "fmt" + "go/ast" + "go/build" + "go/token" + "go/types" + "io/ioutil" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "golang.org/x/tools/go/buildutil" + "golang.org/x/tools/go/loader" +) + +func TestStdlib(t *testing.T) { + if runtime.GOOS == "android" { + t.Skipf("incomplete std lib on %s", runtime.GOOS) + } + if testing.Short() { + t.Skip("skipping in short mode; uses tons of memory (golang.org/issue/14113)") + } + + runtime.GC() + t0 := time.Now() + var memstats runtime.MemStats + runtime.ReadMemStats(&memstats) + alloc := memstats.Alloc + + // Load, parse and type-check the program. + ctxt := build.Default // copy + ctxt.GOPATH = "" // disable GOPATH + conf := loader.Config{Build: &ctxt} + for _, path := range buildutil.AllPackages(conf.Build) { + conf.ImportWithTests(path) + } + + prog, err := conf.Load() + if err != nil { + t.Fatalf("Load failed: %v", err) + } + + t1 := time.Now() + runtime.GC() + runtime.ReadMemStats(&memstats) + + numPkgs := len(prog.AllPackages) + if want := 205; numPkgs < want { + t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want) + } + + // Dump package members. + if false { + for pkg := range prog.AllPackages { + fmt.Printf("Package %s:\n", pkg.Path()) + scope := pkg.Scope() + qualifier := types.RelativeTo(pkg) + for _, name := range scope.Names() { + if ast.IsExported(name) { + fmt.Printf("\t%s\n", types.ObjectString(scope.Lookup(name), qualifier)) + } + } + fmt.Println() + } + } + + // Check that Test functions for io/ioutil, regexp and + // compress/bzip2 are all simultaneously present. + // (The apparent cycle formed when augmenting all three of + // these packages by their tests was the original motivation + // for reporting b/7114.) + // + // compress/bzip2.TestBitReader in bzip2_test.go imports io/ioutil + // io/ioutil.TestTempFile in tempfile_test.go imports regexp + // regexp.TestRE2Search in exec_test.go imports compress/bzip2 + for _, test := range []struct{ pkg, fn string }{ + {"io/ioutil", "TestTempFile"}, + {"regexp", "TestRE2Search"}, + {"compress/bzip2", "TestBitReader"}, + } { + info := prog.Imported[test.pkg] + if info == nil { + t.Errorf("failed to load package %q", test.pkg) + continue + } + obj, _ := info.Pkg.Scope().Lookup(test.fn).(*types.Func) + if obj == nil { + t.Errorf("package %q has no func %q", test.pkg, test.fn) + continue + } + } + + // Dump some statistics. + + // determine line count + var lineCount int + prog.Fset.Iterate(func(f *token.File) bool { + lineCount += f.LineCount() + return true + }) + + t.Log("GOMAXPROCS: ", runtime.GOMAXPROCS(0)) + t.Log("#Source lines: ", lineCount) + t.Log("Load/parse/typecheck: ", t1.Sub(t0)) + t.Log("#MB: ", int64(memstats.Alloc-alloc)/1000000) +} + +func TestCgoOption(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode; uses tons of memory (golang.org/issue/14113)") + } + switch runtime.GOOS { + // On these systems, the net and os/user packages don't use cgo + // or the std library is incomplete (Android). + case "android", "plan9", "solaris", "windows": + t.Skipf("no cgo or incomplete std lib on %s", runtime.GOOS) + } + // In nocgo builds (e.g. linux-amd64-nocgo), + // there is no "runtime/cgo" package, + // so cgo-generated Go files will have a failing import. + if !build.Default.CgoEnabled { + return + } + // Test that we can load cgo-using packages with + // CGO_ENABLED=[01], which causes go/build to select pure + // Go/native implementations, respectively, based on build + // tags. + // + // Each entry specifies a package-level object and the generic + // file expected to define it when cgo is disabled. + // When cgo is enabled, the exact file is not specified (since + // it varies by platform), but must differ from the generic one. + // + // The test also loads the actual file to verify that the + // object is indeed defined at that location. + for _, test := range []struct { + pkg, name, genericFile string + }{ + {"net", "cgoLookupHost", "cgo_stub.go"}, + {"os/user", "current", "lookup_stubs.go"}, + } { + ctxt := build.Default + for _, ctxt.CgoEnabled = range []bool{false, true} { + conf := loader.Config{Build: &ctxt} + conf.Import(test.pkg) + prog, err := conf.Load() + if err != nil { + t.Errorf("Load failed: %v", err) + continue + } + info := prog.Imported[test.pkg] + if info == nil { + t.Errorf("package %s not found", test.pkg) + continue + } + obj := info.Pkg.Scope().Lookup(test.name) + if obj == nil { + t.Errorf("no object %s.%s", test.pkg, test.name) + continue + } + posn := prog.Fset.Position(obj.Pos()) + t.Logf("%s: %s (CgoEnabled=%t)", posn, obj, ctxt.CgoEnabled) + + gotFile := filepath.Base(posn.Filename) + filesMatch := gotFile == test.genericFile + + if ctxt.CgoEnabled && filesMatch { + t.Errorf("CGO_ENABLED=1: %s found in %s, want native file", + obj, gotFile) + } else if !ctxt.CgoEnabled && !filesMatch { + t.Errorf("CGO_ENABLED=0: %s found in %s, want %s", + obj, gotFile, test.genericFile) + } + + // Load the file and check the object is declared at the right place. + b, err := ioutil.ReadFile(posn.Filename) + if err != nil { + t.Errorf("can't read %s: %s", posn.Filename, err) + continue + } + line := string(bytes.Split(b, []byte("\n"))[posn.Line-1]) + ident := line[posn.Column-1:] + if !strings.HasPrefix(ident, test.name) { + t.Errorf("%s: %s not declared here (looking at %q)", posn, obj, ident) + } + } + } +} diff --git a/vendor/golang.org/x/tools/go/loader/testdata/a.go b/vendor/golang.org/x/tools/go/loader/testdata/a.go new file mode 100644 index 0000000..bae3955 --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/testdata/a.go @@ -0,0 +1 @@ +package P diff --git a/vendor/golang.org/x/tools/go/loader/testdata/b.go b/vendor/golang.org/x/tools/go/loader/testdata/b.go new file mode 100644 index 0000000..bae3955 --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/testdata/b.go @@ -0,0 +1 @@ +package P diff --git a/vendor/golang.org/x/tools/go/loader/testdata/badpkgdecl.go b/vendor/golang.org/x/tools/go/loader/testdata/badpkgdecl.go new file mode 100644 index 0000000..1e39359 --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/testdata/badpkgdecl.go @@ -0,0 +1 @@ +// this file has no package decl diff --git a/vendor/golang.org/x/tools/go/loader/util.go b/vendor/golang.org/x/tools/go/loader/util.go new file mode 100644 index 0000000..7f38dd7 --- /dev/null +++ b/vendor/golang.org/x/tools/go/loader/util.go @@ -0,0 +1,124 @@ +// 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 + +import ( + "go/ast" + "go/build" + "go/parser" + "go/token" + "io" + "os" + "strconv" + "sync" + + "golang.org/x/tools/go/buildutil" +) + +// We use a counting semaphore to limit +// the number of parallel I/O calls per process. +var ioLimit = make(chan bool, 10) + +// parseFiles parses the Go source files within directory dir and +// returns the ASTs of the ones that could be at least partially parsed, +// along with a list of I/O and parse errors encountered. +// +// I/O is done via ctxt, which may specify a virtual file system. +// displayPath is used to transform the filenames attached to the ASTs. +// +func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) { + if displayPath == nil { + displayPath = func(path string) string { return path } + } + var wg sync.WaitGroup + n := len(files) + parsed := make([]*ast.File, n) + errors := make([]error, n) + for i, file := range files { + if !buildutil.IsAbsPath(ctxt, file) { + file = buildutil.JoinPath(ctxt, dir, file) + } + wg.Add(1) + go func(i int, file string) { + ioLimit <- true // wait + defer func() { + wg.Done() + <-ioLimit // signal + }() + var rd io.ReadCloser + var err error + if ctxt.OpenFile != nil { + rd, err = ctxt.OpenFile(file) + } else { + rd, err = os.Open(file) + } + if err != nil { + errors[i] = err // open failed + return + } + + // ParseFile may return both an AST and an error. + parsed[i], errors[i] = parser.ParseFile(fset, displayPath(file), rd, mode) + rd.Close() + }(i, file) + } + wg.Wait() + + // Eliminate nils, preserving order. + var o int + for _, f := range parsed { + if f != nil { + parsed[o] = f + o++ + } + } + parsed = parsed[:o] + + o = 0 + for _, err := range errors { + if err != nil { + errors[o] = err + o++ + } + } + errors = errors[:o] + + return parsed, errors +} + +// scanImports returns the set of all import paths from all +// import specs in the specified files. +func scanImports(files []*ast.File) map[string]bool { + imports := make(map[string]bool) + for _, f := range files { + for _, decl := range f.Decls { + if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT { + for _, spec := range decl.Specs { + spec := spec.(*ast.ImportSpec) + + // NB: do not assume the program is well-formed! + path, err := strconv.Unquote(spec.Path.Value) + if err != nil { + continue // quietly ignore the error + } + if path == "C" { + continue // skip pseudopackage + } + imports[path] = true + } + } + } + } + return imports +} + +// ---------- Internal helpers ---------- + +// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos) +func tokenFileContainsPos(f *token.File, pos token.Pos) bool { + p := int(pos) + base := f.Base() + return base <= p && p < base+f.Size() +} |