go.tools/importer: generalize command-line syntax.

Motivation: pointer analysis tools (like the oracle) want the
user to specify a set of initial packages, like 'go test'.
This change enables the user to specify a set of packages on
the command line using importer.LoadInitialPackages(args).

Each argument is interpreted as either:
- a comma-separated list of *.go source files together
  comprising one non-importable ad-hoc package.
  e.g. "src/pkg/net/http/triv.go" gives us [main].
- an import path, denoting both the imported package
  and its non-importable external test package, if any.
  e.g. "fmt" gives us [fmt, fmt_test].

Current type-checker limitations mean that only the first
import path may contribute tests: multiple packages augmented
by *_test.go files could create import cycles, which 'go test'
avoids by building a separate executable for each one.
That approach is less attractive for static analysis.

Details:  (many files touched, but importer.go is the crux)

importer:
- PackageInfo.Importable boolean indicates whether
  package is importable.
- un-expose Importer.Packages; expose AllPackages() instead.
- CreatePackageFromArgs has become LoadInitialPackages.
- imports() moved to util.go, renamed importsOf().
- InitialPackagesUsage usage message exported to clients.
- the package name for ad-hoc packages now comes from the
  'package' decl, not "main".

ssa.Program:
- added CreatePackages() method
- PackagesByPath un-exposed, renamed 'imported'.
- expose AllPackages and ImportedPackage accessors.

oracle:
- describe: explain and workaround a go/types bug.

Misc:
- Removed various unnecessary error.Error() calls in Printf args.

R=crawshaw
CC=golang-dev
https://golang.org/cl/13579043
This commit is contained in:
Alan Donovan 2013-09-06 18:13:57 -04:00
parent 33f988691e
commit 3f2f9a7e70
26 changed files with 664 additions and 328 deletions

View File

@ -25,6 +25,7 @@ import (
"runtime"
"runtime/pprof"
"code.google.com/p/go.tools/importer"
"code.google.com/p/go.tools/oracle"
)
@ -41,13 +42,13 @@ var ptalogFlag = flag.String("ptalog", "",
var formatFlag = flag.String("format", "plain", "Output format: 'plain' or 'json'.")
const usage = `Go source code oracle.
Usage: oracle [<flag> ...] [<file.go> ...] [<arg> ...]
Usage: oracle [<flag> ...] <args> ...
Use -help flag to display options.
Examples:
% oracle -pos=hello.go:#123 hello.go
% oracle -pos=hello.go:#123-#456 hello.go
`
` + importer.InitialPackagesUsage
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
@ -76,7 +77,7 @@ func main() {
var ptalog io.Writer
if *ptalogFlag != "" {
if f, err := os.Create(*ptalogFlag); err != nil {
log.Fatalf(err.Error())
log.Fatal(err.Error())
} else {
buf := bufio.NewWriter(f)
ptalog = buf
@ -106,7 +107,7 @@ func main() {
// Ask the oracle.
res, err := oracle.Query(args, *modeFlag, *posFlag, ptalog, &build.Default)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
@ -115,7 +116,7 @@ func main() {
case "json":
b, err := json.Marshal(res)
if err != nil {
fmt.Fprintf(os.Stderr, "JSON error: %s\n", err.Error())
fmt.Fprintf(os.Stderr, "JSON error: %s\n", err)
os.Exit(1)
}
var buf bytes.Buffer

View File

@ -123,22 +123,36 @@ func main() {
// Load, parse and type-check the program.
imp := importer.New(&impctx)
info, args, err := importer.CreatePackageFromArgs(imp, args)
infos, args, err := imp.LoadInitialPackages(args)
if err != nil {
log.Fatal(err.Error())
log.Fatal(err)
}
// Create and build SSA-form program representation.
prog := ssa.NewProgram(imp.Fset, mode)
for _, info := range imp.Packages {
prog.CreatePackage(info).SetDebugMode(debugMode)
if err := prog.CreatePackages(imp); err != nil {
log.Fatal(err)
}
if debugMode {
for _, pkg := range prog.AllPackages() {
pkg.SetDebugMode(true)
}
}
prog.BuildAll()
prog.Package(info.Pkg).CreateTestMainFunction() // TODO(adonovan): remove hack
// Run the interpreter.
// Run the interpreter on the first package with a main function.
if *runFlag {
interp.Interpret(prog.Package(info.Pkg), interpMode, info.Pkg.Path(), args)
var main *ssa.Package
for _, info := range infos {
pkg := prog.Package(info.Pkg)
if pkg.Func("main") != nil || pkg.CreateTestMainFunction() != nil {
main = pkg
break
}
}
if main == nil {
log.Fatal("No main function")
}
interp.Interpret(main, interpMode, main.Object.Path(), args)
}
}

View File

@ -6,49 +6,83 @@
// type-checks packages of Go code plus their transitive closure, and
// retains both the ASTs and the derived facts.
//
// TODO(adonovan): document and test this package better, with examples.
// Currently it's covered by the ssa/ tests.
// 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 src/pkg/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. Ad-hoc packages and external test packages are non-importable.
// The importer and its clients must be careful not to assume that
// the import path of a package may be used for a name-based lookup.
// For example, a pointer analysis scope may consist of 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.)
// 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 importer 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, and
// currently, go/types is incapable of handling such inputs, so the
// Importer will only augment (and create an external test package
// for) the first import path specified on the command-line.
//
// TODO(adonovan): more tests.
//
package importer
import (
"errors"
"fmt"
"go/ast"
"go/build"
"go/token"
"os"
"strconv"
"strings"
"sync"
"code.google.com/p/go.tools/go/exact"
"code.google.com/p/go.tools/go/types"
)
// An Importer's methods are not thread-safe unless specified.
// An Importer's exported methods are not thread-safe.
type Importer struct {
config *Config // the client configuration
Fset *token.FileSet // position info for all files seen
Packages map[string]*PackageInfo // successfully imported packages keyed by import path
errors map[string]error // cache of errors by import path
mu sync.Mutex // guards 'packages' map
packages map[string]*pkgInfo // all attempted imported packages, keyed by import path
Fset *token.FileSet // position info for all files seen
config *Config // the client configuration
augment map[string]bool // packages to be augmented by TestFiles when imported
allPackagesMu sync.Mutex // guards 'allPackages' during internal concurrency
allPackages []*PackageInfo // all packages, including non-importable ones
importedMu sync.Mutex // guards 'imported'
imported map[string]*importInfo // all imported packages (incl. failures) by import path
}
// pkgInfo holds internal per-package information.
type pkgInfo struct {
info *PackageInfo // success info
err error // failure info
// importInfo holds internal information about each import path.
type importInfo struct {
path string // import path
info *PackageInfo // results of typechecking (including type errors)
err error // reason for failure to construct a package
ready chan struct{} // channel close is notification of ready state
}
// Config specifies the configuration for the importer.
//
type Config struct {
// TypeChecker contains options relating to the type checker.
// The Importer will override any user-supplied values for its
// Error and Import fields; other fields will be passed
// through to the type checker.
// through to the type checker. All callbacks must be thread-safe.
TypeChecker types.Config
// If Build is non-nil, it is used to satisfy imports.
@ -68,151 +102,165 @@ type Config struct {
//
func New(config *Config) *Importer {
imp := &Importer{
config: config,
Fset: token.NewFileSet(),
packages: make(map[string]*pkgInfo),
Packages: make(map[string]*PackageInfo),
errors: make(map[string]error),
config: config,
augment: make(map[string]bool),
imported: make(map[string]*importInfo),
}
// TODO(adonovan): get typechecker to supply us with a source
// position, then pass errors back to the application
// (e.g. oracle).
imp.config.TypeChecker.Error = func(e error) { fmt.Fprintln(os.Stderr, e) }
imp.config.TypeChecker.Import = imp.doImport
return imp
}
// doImport loads the typechecker package identified by path
// Implements the types.Importer prototype.
// AllPackages returns a new slice containing all packages loaded by
// importer imp.
//
func (imp *Importer) doImport(imports map[string]*types.Package, path string) (pkg *types.Package, err error) {
func (imp *Importer) AllPackages() []*PackageInfo {
return append([]*PackageInfo(nil), imp.allPackages...)
}
func (imp *Importer) addPackage(info *PackageInfo) {
imp.allPackagesMu.Lock()
imp.allPackages = append(imp.allPackages, info)
imp.allPackagesMu.Unlock()
}
// doImport imports the package denoted by path.
// It implements the types.Importer prototype.
//
// imports is the import map of the importing package, later
// accessible as types.Package.Imports(). If non-nil, doImport will
// update it to include this import. (It may be nil in recursive
// calls for prefetching.)
//
// 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 and thread-safe, but assumes that no two concurrent
// calls will provide the same 'imports' map.
//
func (imp *Importer) doImport(imports map[string]*types.Package, path string) (*types.Package, error) {
// Package unsafe is handled specially, and has no PackageInfo.
// TODO(adonovan): a fake empty package would make things simpler.
if path == "unsafe" {
return types.Unsafe, nil
}
// Load the source/binary for 'path', type-check it, construct
// a PackageInfo and update our map (imp.Packages) and the
// type-checker's map (imports).
// TODO(adonovan): make it the type-checker's job to
// consult/update the 'imports' map. Then we won't need it.
var info *PackageInfo
if imp.config.Build != nil {
info, err = imp.LoadPackage(path)
} else {
if info, ok := imp.Packages[path]; ok {
imports[path] = info.Pkg
pkg = info.Pkg
return // positive cache hit
}
if err = imp.errors[path]; err != nil {
return // negative cache hit
}
if pkg, err = types.GcImport(imports, path); err == nil {
info = &PackageInfo{Pkg: pkg}
imp.Packages[path] = info
}
info, err := imp.doImport0(imports, path)
if err != nil {
return nil, err
}
if err == nil {
// Cache success.
pkg = info.Pkg
imports[path] = pkg
return pkg, nil
if imports != nil {
// Update the package's imports map, whether success or failure.
//
// types.Package.Imports() is used by PackageInfo.Imports and
// thence by ssa.builder.
// TODO(gri): given that it doesn't specify whether it
// contains direct or transitive dependencies, it probably
// shouldn't be exposed. This package can make its own
// arrangements to implement PackageInfo.Imports().
imports[path] = info.Pkg
}
// Cache failure
imp.errors[path] = err
return nil, err
return info.Pkg, nil
}
// imports returns the set of paths imported by the specified files.
func imports(p string, files []*ast.File) map[string]bool {
imports := make(map[string]bool)
outer:
for _, file := range files {
for _, decl := range file.Decls {
if decl, ok := decl.(*ast.GenDecl); ok {
if decl.Tok != token.IMPORT {
break outer // stop at the first non-import
}
for _, spec := range decl.Specs {
spec := spec.(*ast.ImportSpec)
if path, _ := strconv.Unquote(spec.Path.Value); path != "C" {
imports[path] = true
}
}
} else {
break outer // stop at the first non-import
}
}
}
return imports
}
// LoadPackage loads the package of the specified import path,
// performs type-checking, and returns the corresponding
// PackageInfo.
//
// Precondition: Importer.config.Build != nil.
// Idempotent and thread-safe.
//
// TODO(adonovan): fix: violated in call from CreatePackageFromArgs!
// TODO(adonovan): rethink this API.
//
func (imp *Importer) LoadPackage(importPath string) (*PackageInfo, error) {
if imp.config.Build == nil {
panic("Importer.LoadPackage without a go/build.Config")
}
imp.mu.Lock()
pi, ok := imp.packages[importPath]
// Like doImport, but returns a PackageInfo.
// Precondition: path != "unsafe".
func (imp *Importer) doImport0(imports map[string]*types.Package, path string) (*PackageInfo, error) {
imp.importedMu.Lock()
ii, ok := imp.imported[path]
if !ok {
// First request for this pkgInfo:
// create a new one in !ready state.
pi = &pkgInfo{ready: make(chan struct{})}
imp.packages[importPath] = pi
imp.mu.Unlock()
ii = &importInfo{path: path, ready: make(chan struct{})}
imp.imported[path] = ii
}
imp.importedMu.Unlock()
// Load/parse the package.
if files, err := loadPackage(imp.config.Build, imp.Fset, importPath); err == nil {
// Kick off asynchronous I/O and parsing for the imports.
for path := range imports(importPath, files) {
go func(path string) { imp.LoadPackage(path) }(path)
}
// Type-check the package.
pi.info, pi.err = imp.CreateSourcePackage(importPath, files)
if !ok {
// Find and create the actual package.
if imp.config.Build != nil {
imp.importSource(path, ii)
} else {
pi.err = err
imp.importBinary(imports, ii)
}
ii.info.Importable = true
close(pi.ready) // enter ready state and wake up waiters
close(ii.ready) // enter ready state and wake up waiters
} else {
imp.mu.Unlock()
<-pi.ready // wait for ready condition.
<-ii.ready // wait for ready condition
}
// Invariant: pi is ready.
// Invariant: ii is ready.
if pi.err != nil {
return nil, pi.err
}
return pi.info, nil
return ii.info, ii.err
}
// CreateSourcePackage invokes the type-checker on files and returns a
// PackageInfo containing the resulting type-checker package, the
// ASTs, and other type information.
// importBinary implements package loading from object files from the
// gc compiler.
//
func (imp *Importer) importBinary(imports map[string]*types.Package, ii *importInfo) {
pkg, err := types.GcImport(imports, ii.path)
if pkg != nil {
ii.info = &PackageInfo{Pkg: pkg}
imp.addPackage(ii.info)
} else {
ii.err = err
}
}
// importSource implements package loading by parsing Go source files
// located by go/build.
//
func (imp *Importer) importSource(path string, ii *importInfo) {
which := "g" // load *.go files
if imp.augment[path] {
which = "gt" // augment package by in-package *_test.go files
}
if files, err := parsePackageFiles(imp.config.Build, imp.Fset, path, which); err == nil {
// Prefetch the imports asynchronously.
for path := range importsOf(path, files) {
go func(path string) { imp.doImport(nil, path) }(path)
}
// Type-check the package.
ii.info = imp.typeCheck(path, files)
// We needn't wait for the prefetching goroutines to
// finish. Each one either runs quickly and populates
// the imported map, in which case the type checker
// will wait for the map entry to become ready; or, it
// runs slowly, even after we return, but then becomes
// just another map waiter, in which case it won't
// mutate anything.
} else {
ii.err = err
}
}
// typeCheck invokes the type-checker on files and returns a
// PackageInfo containing the resulting types.Package, the ASTs, and
// other type information.
//
// The order of files determines the package initialization order.
//
// importPath is the full name under which this package is known, such
// as appears in an import declaration. e.g. "sync/atomic".
// path is the full name under which this package is known, such as
// appears in an import declaration. e.g. "sync/atomic". It need not
// be unique; for example, it is possible to construct two distinct
// packages both named "main".
//
// Postcondition: for result (info, err), info.Err == err.
// The resulting package is added to imp.allPackages, but is not
// importable unless it is inserted in the imp.imported map.
//
func (imp *Importer) CreateSourcePackage(importPath string, files []*ast.File) (*PackageInfo, error) {
// This function always succeeds, but the package may contain type
// errors; the first of these is recorded in PackageInfo.Err.
//
func (imp *Importer) typeCheck(path string, files []*ast.File) *PackageInfo {
info := &PackageInfo{
Files: files,
Info: types.Info{
@ -224,7 +272,221 @@ func (imp *Importer) CreateSourcePackage(importPath string, files []*ast.File) (
Selections: make(map[*ast.SelectorExpr]*types.Selection),
},
}
info.Pkg, info.Err = imp.config.TypeChecker.Check(importPath, imp.Fset, files, &info.Info)
imp.Packages[importPath] = info
return info, info.Err
info.Pkg, info.Err = imp.config.TypeChecker.Check(path, imp.Fset, files, &info.Info)
imp.addPackage(info)
return info
}
// LoadMainPackage creates and type-checks a package called "main" from
// the specified list of parsed files, importing its dependencies.
//
// The resulting package is not importable, i.e. no 'import' spec can
// resolve to it. LoadMainPackage is provided as an aid to testing.
//
// LoadMainPackage never fails, but the resulting package may contain
// type errors.
//
func (imp *Importer) LoadMainPackage(files ...*ast.File) *PackageInfo {
return imp.typeCheck("main", files)
}
// InitialPackagesUsage is a partial usage message that client
// applications may wish to include in their -help output.
const InitialPackagesUsage = `
<args> is a list of arguments denoting a set of initial pacakges.
Each argument may take one of two forms:
1. A comma-separated list of *.go source files.
All of the specified files are loaded, parsed and type-checked
as a single package. The name of the package is taken from the
files' package declarations, which must all be equal. All the
files must belong to the same directory.
2. An import path.
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 and 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. This behaviour may be
disabled by prefixing the import path with "notest:",
e.g. "notest:fmt".
Due to current limitations in the type-checker, only the first
import path of the command line will contribute any tests.
A '--' argument terminates the list of packages.
`
// LoadInitialPackages interprets args as a set of packages, loads
// those packages and their dependencies, and returns them.
//
// It is intended for use in command-line interfaces that require a
// set of initial packages to be specified; see InitialPackagesUsage
// message for details.
//
// The second result parameter returns the list of unconsumed
// arguments.
//
// It is an error to specify no packages.
//
// Precondition: LoadInitialPackages cannot be called after any
// previous calls to Load* on the same importer.
//
func (imp *Importer) LoadInitialPackages(args []string) ([]*PackageInfo, []string, error) {
// The "augmentation" mechanism requires that we mark all
// packages to be augmented before we import a single one.
if len(imp.allPackages) > 0 {
return nil, nil, errors.New("LoadInitialPackages called on non-pristine Importer")
}
// We use two passes. The first parses the files for each
// non-importable package and discovers the set of importable
// packages that require augmentation by in-package _test.go
// files. The second creates the ad-hoc packages and imports
// the importable ones.
//
// This is necessary to ensure that all packages requiring
// augmentation are known before before any package is
// imported.
// Pass 1: parse the sets of files for each package.
var pkgs []*initialPkg
for len(args) > 0 {
arg := args[0]
args = args[1:]
if arg == "--" {
break // consume "--" and return the remaining args
}
if strings.HasSuffix(arg, ".go") {
// Assume arg is a comma-separated list of *.go files
// comprising a single package.
pkg, err := initialPackageFromFiles(imp.Fset, arg)
if err != nil {
return nil, nil, err
}
pkgs = append(pkgs, pkg)
} else {
// Assume arg is a directory name denoting a
// package, perhaps plus an external test
// package unless prefixed by "notest:".
path := strings.TrimPrefix(arg, "notest:")
if path == "unsafe" {
continue // ignore; has no PackageInfo
}
pkg := &initialPkg{
path: path,
importable: true,
}
pkgs = append(pkgs, pkg)
if path != arg {
continue // had "notest:" prefix
}
if imp.config.Build == nil {
continue // can't locate *_test.go files
}
// TODO(adonovan): due to limitations of the current type
// checker design, we can augment at most one package.
if len(imp.augment) > 0 {
continue // don't attempt a second
}
// Load the external test package.
xtestFiles, err := parsePackageFiles(imp.config.Build, imp.Fset, path, "x")
if err != nil {
return nil, nil, err
}
if len(xtestFiles) > 0 {
pkgs = append(pkgs, &initialPkg{
path: path + "_test",
importable: false,
files: xtestFiles,
})
}
// Mark the non-xtest package for augmentation with
// in-package *_test.go files when we import it below.
imp.augment[pkg.path] = true
}
}
// Pass 2: type-check each set of files to make a package.
var infos []*PackageInfo
for _, pkg := range pkgs {
var info *PackageInfo
if pkg.importable {
// import package
var err error
info, err = imp.doImport0(nil, pkg.path)
if err != nil {
return nil, nil, err // e.g. parse error (but not type error)
}
} else {
// create package
info = imp.typeCheck(pkg.path, pkg.files)
}
infos = append(infos, info)
}
if len(pkgs) == 0 {
return nil, nil, errors.New("no *.go source files nor packages were specified")
}
return infos, args, nil
}
type initialPkg struct {
path string // the package's import path
importable bool // add package to import map false for main and xtests)
files []*ast.File // set of files (non-importable packages only)
}
// initialPackageFromFiles returns an initialPkg, given a
// comma-separated list of *.go source files belonging to the same
// directory and possessing the same 'package decl'.
//
func initialPackageFromFiles(fset *token.FileSet, arg string) (*initialPkg, error) {
filenames := strings.Split(arg, ",")
for _, filename := range filenames {
if !strings.HasSuffix(filename, ".go") {
return nil, fmt.Errorf("not a *.go source file: %q", filename)
}
}
files, err := ParseFiles(fset, ".", filenames...)
if err != nil {
return nil, err
}
// Take the package name from the 'package decl' in each file,
// all of which must match.
pkgname := files[0].Name.Name
for i, file := range files[1:] {
if pn := file.Name.Name; pn != pkgname {
err := fmt.Errorf("can't load package: found packages %s (%s) and %s (%s)",
pkgname, filenames[0], pn, filenames[i])
return nil, err
}
// TODO(adonovan): check dirnames are equal, like 'go build' does.
}
return &initialPkg{
path: pkgname,
importable: false,
files: files,
}, nil
}

View File

@ -23,6 +23,7 @@ import (
//
type PackageInfo struct {
Pkg *types.Package
Importable bool // true if 'import "Pkg.Path()"' would resolve to this
Err error // non-nil if the package had static errors
Files []*ast.File // abstract syntax for the package's files
types.Info // type-checker deductions.

View File

@ -640,7 +640,7 @@ func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
// The result is (nil, nil, false) if not found.
//
func (imp *Importer) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) {
for _, info := range imp.Packages {
for _, info := range imp.allPackages {
for _, f := range info.Files {
if !tokenFileContainsPos(imp.Fset.File(f.Package), start) {
continue

View File

@ -249,17 +249,13 @@ func TestEnclosingFunction(t *testing.T) {
t.Errorf("EnclosingFunction(%q) not exact", test.substr)
continue
}
info, err := imp.CreateSourcePackage("main", []*ast.File{f})
if err != nil {
t.Error(err.Error())
mainInfo := imp.LoadMainPackage(f)
prog := ssa.NewProgram(imp.Fset, 0)
if err := prog.CreatePackages(imp); err != nil {
t.Error(err)
continue
}
prog := ssa.NewProgram(imp.Fset, 0)
for _, info := range imp.Packages {
prog.CreatePackage(info)
}
pkg := prog.Package(info.Pkg)
pkg := prog.Package(mainInfo.Pkg)
pkg.Build()
name := "(none)"

View File

@ -8,55 +8,16 @@ package importer
// and used by it.
import (
"errors"
"go/ast"
"go/build"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
"strconv"
"sync"
)
// CreatePackageFromArgs builds an initial Package from a list of
// command-line arguments.
// If args is a list of *.go files, they are parsed and type-checked.
// If args is a Go package import path, that package is imported.
// The rest result contains the suffix of args that were not consumed.
//
// This utility is provided to facilitate construction of command-line
// tools with a consistent user interface.
//
func CreatePackageFromArgs(imp *Importer, args []string) (info *PackageInfo, rest []string, err error) {
switch {
case len(args) == 0:
return nil, nil, errors.New("no *.go source files nor package name was specified.")
case strings.HasSuffix(args[0], ".go"):
// % tool a.go b.go ...
// Leading consecutive *.go arguments constitute main package.
i := 1
for ; i < len(args) && strings.HasSuffix(args[i], ".go"); i++ {
}
var files []*ast.File
files, err = ParseFiles(imp.Fset, ".", args[:i]...)
rest = args[i:]
if err == nil {
info, err = imp.CreateSourcePackage("main", files)
}
default:
// % tool my/package ...
// First argument is import path of main package.
pkgname := args[0]
info, err = imp.LoadPackage(pkgname)
rest = args[1:]
}
return
}
var cwd string
func init() {
@ -67,9 +28,15 @@ func init() {
}
}
// loadPackage ascertains which files belong to package path, then
// loads, parses and returns them.
func loadPackage(ctxt *build.Context, fset *token.FileSet, path string) (files []*ast.File, err error) {
// parsePackageFiles enumerates the files belonging to package path,
// then loads, parses and returns them.
//
// 'which' is a list of flags indicating which files to include:
// 'g': include non-test *.go source files (GoFiles)
// 't': include in-package *_test.go source files (TestGoFiles)
// 'x': include external *_test.go source files. (XTestGoFiles)
//
func parsePackageFiles(ctxt *build.Context, fset *token.FileSet, path string, which string) ([]*ast.File, error) {
// Set the "!cgo" go/build tag, preferring (dummy) Go to
// native C implementations of net.cgoLookupHost et al.
ctxt2 := *ctxt
@ -82,9 +49,25 @@ func loadPackage(ctxt *build.Context, fset *token.FileSet, path string) (files [
return nil, nil // empty directory
}
if err != nil {
return // import failed
return nil, err // import failed
}
return ParseFiles(fset, bp.Dir, bp.GoFiles...)
var filenames []string
for _, c := range which {
var s []string
switch c {
case 'g':
s = bp.GoFiles
case 't':
s = bp.TestGoFiles
case 'x':
s = bp.XTestGoFiles
default:
panic(c)
}
filenames = append(filenames, s...)
}
return ParseFiles(fset, bp.Dir, filenames...)
}
// ParseFiles parses the Go source files files within directory dir
@ -132,3 +115,27 @@ func unparen(e ast.Expr) ast.Expr {
func unreachable() {
panic("unreachable")
}
// importsOf returns the set of paths imported by the specified files.
func importsOf(p string, files []*ast.File) map[string]bool {
imports := make(map[string]bool)
outer:
for _, file := range files {
for _, decl := range file.Decls {
if decl, ok := decl.(*ast.GenDecl); ok {
if decl.Tok != token.IMPORT {
break outer // stop at the first non-import
}
for _, spec := range decl.Specs {
spec := spec.(*ast.ImportSpec)
if path, _ := strconv.Unquote(spec.Path.Value); path != "C" {
imports[path] = true
}
}
} else {
break outer // stop at the first non-import
}
}
}
return imports
}

View File

@ -693,22 +693,41 @@ func (r *describeTypeResult) toJSON(res *json.Result, fset *token.FileSet) {
func describePackage(o *oracle, path []ast.Node) (*describePackageResult, error) {
var description string
var pkg *ssa.Package
var importPath string
switch n := path[0].(type) {
case *ast.ImportSpec:
// importPath = o.queryPkgInfo.ObjectOf(n.Name).(*types.Package).Path()
// description = "import of package " + importPath
// TODO(gri): o.queryPkgInfo.ObjectOf(n.Name) may be nil.
// e.g. "fmt" import in cmd/oracle/main.go. Why?
// Workaround:
// Most ImportSpecs have no .Name Ident so we can't
// use ObjectOf.
// We could use the types.Info.Implicits mechanism,
// but it's easier just to look it up by name.
description = "import of package " + n.Path.Value
importPath, _ = strconv.Unquote(n.Path.Value)
pkg = o.prog.ImportedPackage(importPath)
case *ast.Ident:
importPath = o.queryPkgInfo.ObjectOf(n).(*types.Package).Path()
obj := o.queryPkgInfo.ObjectOf(n).(*types.Package)
importPath = obj.Path()
if _, isDef := path[1].(*ast.File); isDef {
// e.g. package id
pkg = o.prog.Package(obj)
description = fmt.Sprintf("definition of package %q", importPath)
} else {
// e.g. import id
// or id.F()
// TODO(gri): go/types internally creates a new
// Package object for each import, so the packages
// for 'package x' and 'import "x"' differ!
//
// Here, this should be an invariant, but is not:
// o.prog.ImportedPackage(obj.Path()).Pkg == obj
//
// So we must use the name of the non-canonical package
// to do another lookup.
pkg = o.prog.ImportedPackage(importPath)
description = fmt.Sprintf("reference to package %q", importPath)
}
if importPath == "" {
@ -722,8 +741,9 @@ func describePackage(o *oracle, path []ast.Node) (*describePackageResult, error)
}
var members []*describeMember
// NB: package "unsafe" has no object.
if pkg := o.prog.PackagesByPath[importPath]; pkg != nil {
// NB: "unsafe" has no ssa.Package
// TODO(adonovan): simplify by using types.Packages not ssa.Packages.
if pkg != nil {
// Compute set of exported package members in lexicographic order.
var names []string
for name := range pkg.Members {

View File

@ -123,7 +123,7 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
minfo, ok := modes[mode]
if !ok {
if mode == "" {
return nil, errors.New("you must specify a -mode to perform")
return nil, errors.New("you must specify a -mode of query to perform")
}
return nil, fmt.Errorf("invalid mode type: %q", mode)
}
@ -155,9 +155,12 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
// Load/parse/type-check program from args.
start := time.Now()
initialPkgInfo, _, err := importer.CreatePackageFromArgs(imp, args)
initialPkgInfos, args, err := imp.LoadInitialPackages(args)
if err != nil {
return nil, err // I/O, parser or type error
return nil, err // I/O or parser error
}
if len(args) > 0 {
return nil, fmt.Errorf("surplus arguments: %q", args)
}
o.timers["load/parse/type"] = time.Since(start)
@ -184,21 +187,26 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
if minfo.needs&SSA != 0 {
start = time.Now()
// All packages.
for _, info := range imp.Packages {
o.prog.CreatePackage(info) // create ssa.Package
// Create SSA packages.
if err := o.prog.CreatePackages(imp); err != nil {
return nil, o.errorf(false, "%s", err)
}
// Initial package (specified on command line)
initialPkg := o.prog.Package(initialPkgInfo.Pkg)
// Initial packages (specified on command line)
for _, info := range initialPkgInfos {
initialPkg := o.prog.Package(info.Pkg)
// Add package to the pointer analysis scope.
if initialPkg.Func("main") == nil {
if initialPkg.CreateTestMainFunction() == nil {
return nil, o.errorf(false, "analysis scope has no main() entry points")
// Add package to the pointer analysis scope.
if initialPkg.Func("main") == nil {
// TODO(adonovan): to simulate 'go test' more faithfully, we
// should build a single synthetic testmain package,
// not synthetic main functions to many packages.
if initialPkg.CreateTestMainFunction() == nil {
return nil, o.errorf(false, "analysis scope has no main() entry points")
}
}
o.config.Mains = append(o.config.Mains, initialPkg)
}
o.config.Mains = append(o.config.Mains, initialPkg)
// Query package.
if o.queryPkgInfo != nil {

View File

@ -2,11 +2,11 @@
{
"mode": "describe",
"describe": {
"desc": "definition of package \"main\"",
"desc": "definition of package \"describe\"",
"pos": "testdata/src/main/describe-json.go:1:9",
"detail": "package",
"package": {
"path": "main",
"path": "describe",
"members": [
{
"name": "C",

View File

@ -1,5 +1,5 @@
-------- @describe pkgdecl --------
definition of package "main"
definition of package "describe"
type C int
method (*describe.C) f()
type D struct{}
@ -126,7 +126,7 @@ binary - operation of constant value -2
-------- @describe map-lookup,ok --------
index expression of type (*int, bool)
no points-to information: can't locate SSA Value for expression in main.main
no points-to information: can't locate SSA Value for expression in describe.main
-------- @describe mapval --------
reference to var mapval *int

View File

@ -34,7 +34,7 @@ defined here
reference to var p *int
defined here
value may point to these labels:
main.a
imports.a
-------- @describe ref-pkg --------
reference to package "lib"

View File

@ -33,7 +33,7 @@ This channel of type chan *int may be:
reference to var rA *int
defined here
value may point to these labels:
main.a2
peers.a2
a1
-------- @peers peer-recv-chB --------

View File

@ -195,7 +195,7 @@ func Analyze(config *Config) CallGraphNode {
work: makeMapWorklist(),
}
if reflect := a.prog.PackagesByPath["reflect"]; reflect != nil {
if reflect := a.prog.ImportedPackage("reflect"); reflect != nil {
a.reflectValueObj = reflect.Object.Scope().Lookup("Value")
a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype")
a.reflectRtype = types.NewPointer(a.reflectRtypeObj.Type())

View File

@ -11,7 +11,6 @@ package pointer_test
import (
"bytes"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
@ -171,21 +170,18 @@ func doOneInput(input, filename string) bool {
if err != nil {
// TODO(adonovan): err is a scanner error list;
// display all errors not just first?
fmt.Println(err.Error())
fmt.Println(err)
return false
}
// Type checking.
info, err := imp.CreateSourcePackage("main", []*ast.File{f})
if err != nil {
fmt.Println(err.Error())
return false
}
// Load main package and its dependencies.
info := imp.LoadMainPackage(f)
// SSA creation + building.
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
for _, info := range imp.Packages {
prog.CreatePackage(info)
if err := prog.CreatePackages(imp); err != nil {
fmt.Println(err)
return false
}
prog.BuildAll()
@ -522,7 +518,7 @@ func TestInput(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Errorf("os.Getwd: %s", err.Error())
t.Errorf("os.Getwd: %s", err)
return
}
@ -535,7 +531,7 @@ func TestInput(t *testing.T) {
for _, filename := range inputs {
content, err := ioutil.ReadFile(filename)
if err != nil {
t.Errorf("couldn't read file '%s': %s", filename, err.Error())
t.Errorf("couldn't read file '%s': %s", filename, err)
continue
}

View File

@ -2305,7 +2305,7 @@ func (b *builder) buildFuncDecl(pkg *Package, decl *ast.FuncDecl) {
//
func (prog *Program) BuildAll() {
var wg sync.WaitGroup
for _, p := range prog.PackagesByPath {
for _, p := range prog.packages {
if prog.mode&BuildSerially != 0 {
p.Build()
} else {

View File

@ -5,13 +5,13 @@
package ssa_test
import (
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/importer"
"code.google.com/p/go.tools/ssa"
"go/ast"
"go/parser"
"strings"
"testing"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/importer"
"code.google.com/p/go.tools/ssa"
)
func isEmpty(f *ssa.Function) bool { return f.Blocks == nil }
@ -38,7 +38,7 @@ func main() {
w.Write(nil) // interface invoke of external declared method
}
`
imp := importer.New(new(importer.Config)) // no Loader; uses GC importer
imp := importer.New(new(importer.Config)) // no go/build.Context; uses GC importer
f, err := parser.ParseFile(imp.Fset, "<input>", test, parser.DeclarationErrors)
if err != nil {
@ -46,26 +46,24 @@ func main() {
return
}
info, err := imp.CreateSourcePackage("main", []*ast.File{f})
if err != nil {
t.Error(err.Error())
return
}
mainInfo := imp.LoadMainPackage(f)
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
for _, info := range imp.Packages {
prog.CreatePackage(info)
if err := prog.CreatePackages(imp); err != nil {
t.Error(err)
return
}
mainPkg := prog.Package(info.Pkg)
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.Build()
// Only the main package and its immediate dependencies are loaded.
deps := []string{"bytes", "io", "testing"}
if len(prog.PackagesByPath) != 1+len(deps) {
t.Errorf("unexpected set of loaded packages: %q", prog.PackagesByPath)
all := prog.AllPackages()
if len(all) != 1+len(deps) {
t.Errorf("unexpected set of loaded packages: %q", all)
}
for _, path := range deps {
pkg, _ := prog.PackagesByPath[path]
pkg := prog.ImportedPackage(path)
if pkg == nil {
t.Errorf("package not loaded: %q", path)
continue
@ -97,11 +95,6 @@ func main() {
if !isExt {
t.Fatalf("unexpected name type in main package: %s", mem)
}
if _, ok := mem.Type().Underlying().(*types.Interface); ok {
// TODO(adonovan): workaround bug in types.MethodSet
// whereby mset(*I) is nonempty if I is an interface.
continue
}
mset := types.NewPointer(mem.Type()).MethodSet()
for i, n := 0, mset.Len(); i < n; i++ {
m := prog.Method(mset.At(i))

View File

@ -12,6 +12,7 @@ import (
"go/ast"
"go/token"
"os"
"strings"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/importer"
@ -40,7 +41,7 @@ const (
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
prog := &Program{
Fset: fset,
PackagesByPath: make(map[string]*Package),
imported: make(map[string]*Package),
packages: make(map[*types.Package]*Package),
builtins: make(map[types.Object]*Builtin),
boundMethodWrappers: make(map[*types.Func]*Function),
@ -246,7 +247,9 @@ func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
p.DumpTo(os.Stderr)
}
prog.PackagesByPath[info.Pkg.Path()] = p
if info.Importable {
prog.imported[info.Pkg.Path()] = p
}
prog.packages[p.Object] = p
if prog.mode&SanityCheckFunctions != 0 {
@ -255,3 +258,48 @@ func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
return p
}
// CreatePackages creates SSA Packages for all error-free packages
// loaded by the specified Importer.
//
// If all packages were error-free, it is safe to call
// prog.BuildAll(), and nil is returned. Otherwise an error is
// returned.
//
func (prog *Program) CreatePackages(imp *importer.Importer) error {
var errpkgs []string
for _, info := range imp.AllPackages() {
if info.Err != nil {
errpkgs = append(errpkgs, info.Pkg.Path())
} else {
prog.CreatePackage(info)
}
}
if errpkgs != nil {
return fmt.Errorf("couldn't create these SSA packages due to type errors: %s",
strings.Join(errpkgs, ", "))
}
return nil
}
// AllPackages returns a new slice containing all packages in the
// program prog in unspecified order.
//
func (prog *Program) AllPackages() []*Package {
pkgs := make([]*Package, 0, len(prog.packages))
for _, pkg := range prog.packages {
pkgs = append(pkgs, pkg)
}
return pkgs
}
// ImportedPackage returns the importable SSA Package whose import
// path is path, or nil if no such SSA package has been created.
//
// Not all packages are importable. For example, no import
// declaration can resolve to the x_test package created by 'go test'
// or the ad-hoc main package created 'go build foo.go'.
//
func (prog *Program) ImportedPackage(path string) *Package {
return prog.imported[path]
}

View File

@ -6,7 +6,6 @@ package ssa_test
import (
"fmt"
"go/ast"
"go/build"
"go/parser"
"os"
@ -48,24 +47,21 @@ func main() {
// Parse the input file.
file, err := parser.ParseFile(imp.Fset, "hello.go", hello, parser.DeclarationErrors)
if err != nil {
fmt.Print(err.Error()) // parse error
fmt.Print(err) // parse error
return
}
// Create a "main" package containing one file.
info, err := imp.CreateSourcePackage("main", []*ast.File{file})
if err != nil {
fmt.Print(err.Error()) // type error
return
}
mainInfo := imp.LoadMainPackage(file)
// Create SSA-form program representation.
var mode ssa.BuilderMode
prog := ssa.NewProgram(imp.Fset, mode)
for _, info := range imp.Packages {
prog.CreatePackage(info)
if err := prog.CreatePackages(imp); err != nil {
fmt.Print(err) // type error in some package
return
}
mainPkg := prog.Package(info.Pkg)
mainPkg := prog.Package(mainInfo.Pkg)
// Print out the package.
mainPkg.DumpTo(os.Stdout)

View File

@ -1,3 +1,7 @@
// 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 ssa/interp defines an interpreter for the SSA
// representation of Go programs.
//
@ -390,10 +394,11 @@ func prepareCall(fr *frame, call *ssa.CallCommon) (fn value, args []value) {
if recv.t == nil {
panic("method invoked on nil interface")
}
fn = lookupMethod(fr.i, recv.t, call.Method)
if fn == nil {
if f := lookupMethod(fr.i, recv.t, call.Method); f == nil {
// Unreachable in well-typed programs.
panic(fmt.Sprintf("method set for dynamic type %v does not contain %s", recv.t, call.Method))
} else {
fn = f
}
args = append(args, copyVal(recv.v))
}
@ -545,7 +550,7 @@ func Interpret(mainpkg *ssa.Package, mode Mode, filename string, args []string)
}
initReflect(i)
for importPath, pkg := range i.prog.PackagesByPath {
for _, pkg := range i.prog.AllPackages() {
// Initialize global storage.
for _, m := range pkg.Members {
switch v := m.(type) {
@ -556,7 +561,7 @@ func Interpret(mainpkg *ssa.Package, mode Mode, filename string, args []string)
}
// Ad-hoc initialization for magic system variables.
switch importPath {
switch pkg.Object.Path() {
case "syscall":
var envs []value
for _, s := range os.Environ() {

View File

@ -166,6 +166,7 @@ func run(t *testing.T, dir, input string) bool {
}
imp := importer.New(&importer.Config{Build: &build.Default})
// TODO(adonovan): use LoadInitialPackages, then un-export ParseFiles.
files, err := importer.ParseFiles(imp.Fset, ".", inputs...)
if err != nil {
t.Errorf("ssa.ParseFiles(%s) failed: %s", inputs, err.Error())
@ -184,19 +185,16 @@ func run(t *testing.T, dir, input string) bool {
}()
hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump; ./ssadump -build=CFP %s\n", input)
info, err := imp.CreateSourcePackage("main", files)
if err != nil {
t.Errorf("importer.CreateSourcePackage(%s) failed: %s", inputs, err)
return false
}
mainInfo := imp.LoadMainPackage(files...)
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
for _, info := range imp.Packages {
prog.CreatePackage(info)
if err := prog.CreatePackages(imp); err != nil {
t.Errorf("CreatePackages failed: %s", err)
return false
}
prog.BuildAll()
mainPkg := prog.Package(info.Pkg)
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.CreateTestMainFunction() // (no-op if main already exists)
hint = fmt.Sprintf("To trace execution, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump; ./ssadump -build=C -run --interp=T %s\n", input)

View File

@ -46,17 +46,14 @@ func TestObjValueLookup(t *testing.T) {
}
}
info, err := imp.CreateSourcePackage("main", []*ast.File{f})
if err != nil {
t.Error(err.Error())
return
}
mainInfo := imp.LoadMainPackage(f)
prog := ssa.NewProgram(imp.Fset, 0 /*|ssa.LogFunctions*/)
for _, info := range imp.Packages {
prog.CreatePackage(info)
if err := prog.CreatePackages(imp); err != nil {
t.Error(err)
return
}
mainPkg := prog.Package(info.Pkg)
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.SetDebugMode(true)
mainPkg.Build()
@ -66,7 +63,7 @@ func TestObjValueLookup(t *testing.T) {
ast.Inspect(f, func(n ast.Node) bool {
if id, ok := n.(*ast.Ident); ok {
ids = append(ids, id)
if obj := info.ObjectOf(id); obj != nil {
if obj := mainInfo.ObjectOf(id); obj != nil {
objs[obj] = true
}
}
@ -87,7 +84,7 @@ func TestObjValueLookup(t *testing.T) {
// Check invariants for var objects.
// The result varies based on the specific Ident.
for _, id := range ids {
if obj, ok := info.ObjectOf(id).(*types.Var); ok {
if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok {
ref, _ := importer.PathEnclosingInterval(f, id.Pos(), id.Pos())
pos := imp.Fset.Position(id.Pos())
exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
@ -197,17 +194,14 @@ func TestValueForExpr(t *testing.T) {
return
}
info, err := imp.CreateSourcePackage("main", []*ast.File{f})
if err != nil {
t.Error(err.Error())
return
}
mainInfo := imp.LoadMainPackage(f)
prog := ssa.NewProgram(imp.Fset, 0)
for _, info := range imp.Packages {
prog.CreatePackage(info)
if err := prog.CreatePackages(imp); err != nil {
t.Error(err)
return
}
mainPkg := prog.Package(info.Pkg)
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.SetDebugMode(true)
mainPkg.Build()
@ -251,8 +245,8 @@ func TestValueForExpr(t *testing.T) {
t.Errorf("%s: got value %q, want %q", position, got, want)
}
if v != nil {
if !types.IsIdentical(v.Type(), info.TypeOf(e)) {
t.Errorf("%s: got type %s, want %s", position, info.TypeOf(e), v.Type())
if !types.IsIdentical(v.Type(), mainInfo.TypeOf(e)) {
t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), v.Type())
}
}
}

View File

@ -22,11 +22,11 @@ import (
// A Program is a partial or complete Go program converted to SSA form.
//
type Program struct {
Fset *token.FileSet // position information for the files of this Program
PackagesByPath map[string]*Package // all loaded Packages, keyed by import path
packages map[*types.Package]*Package // all loaded Packages, keyed by object
builtins map[types.Object]*Builtin // all built-in functions, keyed by typechecker objects.
mode BuilderMode // set of mode bits for SSA construction
Fset *token.FileSet // position information for the files of this Program
imported map[string]*Package // all importable Packages, keyed by import path
packages map[*types.Package]*Package // all loaded Packages, keyed by object
builtins map[types.Object]*Builtin // all built-in functions, keyed by typechecker objects.
mode BuilderMode // set of mode bits for SSA construction
methodsMu sync.Mutex // guards the following maps:
methodSets typemap.M // maps type to its concrete methodSet

View File

@ -55,13 +55,11 @@ func TestStdlib(t *testing.T) {
// Load, parse and type-check the program.
t0 := time.Now()
var hasErrors bool
imp := importer.New(&impctx)
for _, importPath := range allPackages() {
if _, err := imp.LoadPackage(importPath); err != nil {
t.Errorf("LoadPackage(%s): %s", importPath, err)
hasErrors = true
}
if _, _, err := imp.LoadInitialPackages(allPackages()); err != nil {
t.Errorf("LoadInitialPackages failed: %s", err)
return
}
t1 := time.Now()
@ -73,25 +71,26 @@ func TestStdlib(t *testing.T) {
// Create SSA packages.
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
for _, info := range imp.Packages {
if info.Err == nil {
prog.CreatePackage(info).SetDebugMode(debugMode)
}
if err := prog.CreatePackages(imp); err != nil {
t.Errorf("CreatePackages failed: %s", err)
return
}
// Enable debug mode globally.
for _, info := range imp.AllPackages() {
prog.Package(info.Pkg).SetDebugMode(debugMode)
}
t2 := time.Now()
// Build SSA IR... if it's safe.
if !hasErrors {
prog.BuildAll()
}
prog.BuildAll()
t3 := time.Now()
runtime.GC()
runtime.ReadMemStats(&memstats)
numPkgs := len(prog.PackagesByPath)
numPkgs := len(prog.AllPackages())
if want := 140; numPkgs < want {
t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
}
@ -108,9 +107,7 @@ func TestStdlib(t *testing.T) {
t.Log("GOMAXPROCS: ", runtime.GOMAXPROCS(0))
t.Log("Load/parse/typecheck: ", t1.Sub(t0))
t.Log("SSA create: ", t2.Sub(t1))
if !hasErrors {
t.Log("SSA build: ", t3.Sub(t2))
}
t.Log("SSA build: ", t3.Sub(t2))
// SSA stats:
t.Log("#Packages: ", numPkgs)

View File

@ -38,7 +38,7 @@ func (pkg *Package) CreateTestMainFunction() *Function {
Pkg: pkg,
}
testingPkg := pkg.Prog.PackagesByPath["testing"]
testingPkg := pkg.Prog.ImportedPackage("testing")
if testingPkg == nil {
// If it doesn't import "testing", it can't be a test.
// TODO(adonovan): but it might contain Examples.

View File

@ -35,7 +35,7 @@ type visitor struct {
}
func (visit *visitor) program() {
for _, pkg := range visit.prog.PackagesByPath {
for _, pkg := range visit.prog.AllPackages() {
for _, mem := range pkg.Members {
switch mem := mem.(type) {
case *Function: