go.tools/importer: API rethink.

The Importer type has been replaced with Config and Program.

Clients populate a Config, directly or more usually via
convenience functions.  They then call its Load() method to do
all of the typechecking and transitive-closure computation.

ssa.NewProgram and ssa.CreatePackages have been fused into
ssa.Create, which now cannot fail, since (*Config).Load()
reports all type errors.

Also:
- The addition of an ssa.GlobalDebug builder mode flag
  eliminates a loop-over-packages repeated in many clients.
- PackageInfo.Err flag unexported.  Clients never see bad infos now.
- cmd/ssadump: now only looks for func "main" in package "main".
- importsOf deleted, was dead code.

STILL TODO:
- ParseFile seems like API creep (though it's convenient)
  and CreateFromFiles is dangerous (w.r.t. FileSet identity).
  Need to think more...
- the need for clients to rely on elementwise correspondence
  of Config.CreatePkgs and Program.Created is a little sad.
- The command-line interface has not changed.
  That will happen in a follow-up.
  r recommends using a repeated flag: -package p -package q ...

R=gri
CC=axwalk, frederik.zipp, golang-codereviews
https://golang.org/cl/49530047
This commit is contained in:
Alan Donovan 2014-01-15 21:37:55 -05:00
parent d71b7746ee
commit e8afbfad8c
21 changed files with 790 additions and 676 deletions

View File

@ -74,7 +74,7 @@ Describe the syntax at offset 530 in this file (an import spec):
Print the callgraph of the trivial web-server in JSON format: Print the callgraph of the trivial web-server in JSON format:
% oracle -format=json src/pkg/net/http/triv.go callgraph % oracle -format=json src/pkg/net/http/triv.go callgraph
` + importer.InitialPackagesUsage ` + importer.FromArgsUsage
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")

View File

@ -48,7 +48,7 @@ Examples:
% ssadump -build=FPG hello.go # quickly dump SSA form of a single package % ssadump -build=FPG hello.go # quickly dump SSA form of a single package
% ssadump -run -interp=T hello.go # interpret a program, with tracing % ssadump -run -interp=T hello.go # interpret a program, with tracing
% ssadump -run unicode -- -test.v # interpret the unicode package's tests, verbosely % ssadump -run unicode -- -test.v # interpret the unicode package's tests, verbosely
` + importer.InitialPackagesUsage + ` + importer.FromArgsUsage +
` `
When -run is specified, ssadump will find the first package that When -run is specified, ssadump will find the first package that
defines a main function and run it in the interpreter. defines a main function and run it in the interpreter.
@ -73,28 +73,27 @@ func main() {
flag.Parse() flag.Parse()
args := flag.Args() args := flag.Args()
impctx := importer.Config{ conf := importer.Config{
Build: &build.Default, Build: &build.Default,
SourceImports: true, SourceImports: true,
} }
// TODO(adonovan): make go/types choose its default Sizes from // TODO(adonovan): make go/types choose its default Sizes from
// build.Default or a specified *build.Context. // build.Default or a specified *build.Context.
var wordSize int64 = 8 var wordSize int64 = 8
switch impctx.Build.GOARCH { switch conf.Build.GOARCH {
case "386", "arm": case "386", "arm":
wordSize = 4 wordSize = 4
} }
impctx.TypeChecker.Sizes = &types.StdSizes{ conf.TypeChecker.Sizes = &types.StdSizes{
MaxAlign: 8, MaxAlign: 8,
WordSize: wordSize, WordSize: wordSize,
} }
var debugMode bool
var mode ssa.BuilderMode var mode ssa.BuilderMode
for _, c := range *buildFlag { for _, c := range *buildFlag {
switch c { switch c {
case 'D': case 'D':
debugMode = true mode |= ssa.GlobalDebug
case 'P': case 'P':
mode |= ssa.LogPackages | ssa.BuildSerially mode |= ssa.LogPackages | ssa.BuildSerially
case 'F': case 'F':
@ -106,7 +105,7 @@ func main() {
case 'N': case 'N':
mode |= ssa.NaiveForm mode |= ssa.NaiveForm
case 'G': case 'G':
impctx.SourceImports = false conf.SourceImports = false
case 'L': case 'L':
mode |= ssa.BuildSerially mode |= ssa.BuildSerially
default: default:
@ -141,59 +140,52 @@ func main() {
defer pprof.StopCPUProfile() defer pprof.StopCPUProfile()
} }
// Load, parse and type-check the program. // Use the initial packages from the command line.
imp := importer.New(&impctx) args, err := conf.FromArgs(args)
infos, args, err := imp.LoadInitialPackages(args)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
// The interpreter needs the runtime package. // The interpreter needs the runtime package.
if *runFlag { if *runFlag {
if _, err := imp.ImportPackage("runtime"); err != nil { conf.Import("runtime")
log.Fatalf("ImportPackage(runtime) failed: %s", err)
}
} }
// Create and build SSA-form program representation. // Load, parse and type-check the whole program.
prog := ssa.NewProgram(imp.Fset, mode) iprog, err := conf.Load()
if err := prog.CreatePackages(imp); err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if debugMode { // Create and build SSA-form program representation.
for _, pkg := range prog.AllPackages() { prog := ssa.Create(iprog, mode)
pkg.SetDebugMode(true)
}
}
prog.BuildAll() prog.BuildAll()
// Run the interpreter. // Run the interpreter.
if *runFlag { if *runFlag {
// If some package defines main, run that. // If a package named "main" defines func main, run that.
// Otherwise run all package's tests. // Otherwise run all packages' tests.
var main *ssa.Package var main *ssa.Package
var pkgs []*ssa.Package pkgs := prog.AllPackages()
for _, info := range infos { for _, pkg := range pkgs {
pkg := prog.Package(info.Pkg) if pkg.Object.Name() == "main" && pkg.Func("main") != nil {
if pkg.Func("main") != nil {
main = pkg main = pkg
break break
} }
pkgs = append(pkgs, pkg)
} }
if main == nil && pkgs != nil { if main == nil && len(pkgs) > 0 {
// TODO(adonovan): only run tests if -test flag specified.
main = prog.CreateTestMainPackage(pkgs...) main = prog.CreateTestMainPackage(pkgs...)
} }
if main == nil { if main == nil {
log.Fatal("No main package and no tests") log.Fatal("No main package and no tests")
} }
if runtime.GOARCH != impctx.Build.GOARCH { if runtime.GOARCH != conf.Build.GOARCH {
log.Fatalf("Cross-interpretation is not yet supported (target has GOARCH %s, interpreter has %s).", log.Fatalf("Cross-interpretation is not yet supported (target has GOARCH %s, interpreter has %s).",
impctx.Build.GOARCH, runtime.GOARCH) conf.Build.GOARCH, runtime.GOARCH)
} }
interp.Interpret(main, interpMode, impctx.TypeChecker.Sizes, main.Object.Path(), args) interp.Interpret(main, interpMode, conf.TypeChecker.Sizes, main.Object.Path(), args)
} }
} }

View File

@ -2,9 +2,47 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package importer defines the Importer, which loads, parses and // Package importer loads, parses and type-checks packages of Go code
// type-checks packages of Go code plus their transitive closure, and // plus their transitive closure, and retains both the ASTs and the
// retains both the ASTs and the derived facts. // derived facts.
//
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
//
// The package defines two primary types: Config, which specifies a
// set of initial packages to load and various other options; and
// Program, which is the result of successfully loading the packages
// specified by a configuration.
//
// The configuration can be set directly, but *Config provides various
// convenience methods to simplify the common cases, each of which can
// be called any number of times. Finally, these are followed by a
// call to Load() to actually load and type-check the program.
//
// var conf importer.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:])
//
// // Parse the specified files and create an ad-hoc package.
// // All files must have the same 'package' declaration.
// err := conf.CreateFromFilenames("foo.go", "bar.go")
//
// // Create an ad-hoc package from the specified already-parsed files.
// // All ASTs must have the same 'package' declaration.
// err := conf.CreateFromFiles(parsedFiles)
//
// // Add "runtime" to the set of packages to be loaded.
// err := conf.Import("runtime")
//
// // Adds "fmt" and "fmt_test" to the set of packages
// // to be loaded. "fmt" will include *_test.go files.
// err := conf.ImportWithTests("fmt")
//
// // Finally, load all the packages specified by the configuration.
// prog, err := conf.Load()
//
// //
// CONCEPTS AND TERMINOLOGY // CONCEPTS AND TERMINOLOGY
// //
@ -17,11 +55,14 @@
// same directory. (go/build.Package calls these files XTestFiles.) // same directory. (go/build.Package calls these files XTestFiles.)
// //
// An IMPORTABLE package is one that can be referred to by some import // An IMPORTABLE package is one that can be referred to by some import
// spec. Ad-hoc packages and external test packages are non-importable. // spec. The Path() of each importable package is unique within a
// The importer and its clients must be careful not to assume that // Program.
// 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 and external test packages are NON-IMPORTABLE. The
// (ad-hoc) packages both called "main". // Path() of an ad-hoc package is inferred from the package
// declarations of its files and is therefore not a unique package key.
// For example, Config.CreatePkgs may specify two initial ad-hoc
// packages both called "main".
// //
// An AUGMENTED package is an importable package P plus all the // An AUGMENTED package is an importable package P plus all the
// *_test.go files with same 'package foo' declaration as P. // *_test.go files with same 'package foo' declaration as P.
@ -40,13 +81,25 @@
// Importer will only augment (and create an external test package // Importer will only augment (and create an external test package
// for) the first import path specified on the command-line. // for) the first import path specified on the command-line.
// //
// 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 importer package importer
// TODO(adonovan):
// - Rename this package go.tools/go/loader.
// - (*Config).ParseFile is very handy, but feels like feature creep.
// (*Config).CreateFromFiles has a nasty precondition.
// - Ideally some of this logic would move under the umbrella of
// go/types; see bug 7114.
import ( import (
"errors" "errors"
"fmt" "fmt"
"go/ast" "go/ast"
"go/build" "go/build"
"go/parser"
"go/token" "go/token"
"os" "os"
"strings" "strings"
@ -57,30 +110,20 @@ import (
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
) )
// An Importer's exported methods are not thread-safe. // Config specifies the configuration for a program to load.
type Importer struct {
Fset *token.FileSet // position info for all files seen
Config *Config // the client configuration, unmodified
importfn types.Importer // client's type import function
srcpkgs map[string]bool // for each package to load from source, whether to augment
allPackages []*PackageInfo // all packages, including non-importable ones
imported map[string]*importInfo // all imported packages (incl. failures) by import path
}
// 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
}
// Config specifies the configuration for the importer.
// The zero value for Config is a ready-to-use default configuration. // The zero value for Config is a ready-to-use default configuration.
type Config struct { type Config struct {
// Fset is the file set for the parser to use when loading the
// program. If nil, it will be lazily initialized by any
// method of Config.
Fset *token.FileSet
// TypeChecker contains options relating to the type checker. // TypeChecker contains options relating to the type checker.
// //
// The supplied IgnoreFuncBodies is not used; the effective // The supplied IgnoreFuncBodies is not used; the effective
// value comes from the TypeCheckFuncBodies func below. // value comes from the TypeCheckFuncBodies func below.
//
// TypeChecker.Packages is lazily initialized during Load.
TypeChecker types.Config TypeChecker types.Config
// TypeCheckFuncBodies is a predicate over package import // TypeCheckFuncBodies is a predicate over package import
@ -91,209 +134,91 @@ type Config struct {
// checked. // checked.
TypeCheckFuncBodies func(string) bool TypeCheckFuncBodies func(string) bool
// SourceImports determines whether to satisfy all imports by // SourceImports determines whether to satisfy dependencies by
// loading Go source code. // loading Go source code.
// //
// If true, the entire program---the initial packages and
// their transitive closure of dependencies---will be loaded,
// parsed and type-checked. This is required for
// whole-program analyses such as pointer analysis.
//
// If false, the TypeChecker.Import mechanism will be used // If false, the TypeChecker.Import mechanism will be used
// instead. Since that typically supplies only the types of // instead. Since that typically supplies only the types of
// package-level declarations and values of constants, but no // package-level declarations and values of constants, but no
// code, it will not yield a whole program. It is intended // code, it will not yield a whole program. It is intended
// for analyses that perform intraprocedural analysis of a // for analyses that perform intraprocedural analysis of a
// single package. // single package, e.g. traditional compilation.
// //
// The importer's initial packages are always loaded from // The initial packages (CreatePkgs and ImportPkgs) are always
// source, regardless of this flag's setting. // loaded from Go source, regardless of this flag's setting.
SourceImports bool SourceImports bool
// If Build is non-nil, it is used to locate source packages. // If Build is non-nil, it is used to locate source packages.
// Otherwise &build.Default is used. // Otherwise &build.Default is used.
Build *build.Context Build *build.Context
}
// build returns the effective build context. // CreatePkgs specifies a list of non-importable initial
func (c *Config) build() *build.Context { // packages to create. Each element is a list of parsed files
if c.Build != nil { // to be type-checked into a new package whose name is taken
return c.Build // from ast.File.Package.
}
return &build.Default
}
// New returns a new, empty Importer using configuration options
// specified by config.
// //
func New(config *Config) *Importer { // The resulting packages will appear in the corresponding
// Initialize by mutating the caller's copy, // elements of the Program.Created slice.
// so all copies agree on the identity of the map. CreatePkgs [][]*ast.File
if config.TypeChecker.Packages == nil {
config.TypeChecker.Packages = make(map[string]*types.Package) // ImportPkgs specifies a set of initial packages to load from
// source. The map keys are package import paths, used to
// locate the package relative to $GOROOT. The corresponding
// values indicate whether to augment the package by *_test.go
// files.
//
// Due to current type-checker limitations, at most one entry
// may be augmented (true).
ImportPkgs map[string]bool
} }
// Save the caller's effective Import funcion. // A Program is a Go program loaded from source or binary
importfn := config.TypeChecker.Import // as specified by a Config.
if importfn == nil { type Program struct {
importfn = gcimporter.Import Fset *token.FileSet // the file set for this program
// Created[i] contains the initial package whose ASTs were
// supplied by Config.CreatePkgs[i].
Created []*PackageInfo
// Imported contains the initially imported packages,
// as specified by Config.ImportPkgs.
Imported map[string]*PackageInfo
// ImportMap is the canonical mapping of import paths to
// packages used by the type-checker (Config.TypeChecker.Packages).
// It contains all Imported initial packages, but not Created
// ones, and all imported dependencies.
ImportMap map[string]*types.Package
// AllPackages contains the PackageInfo of every package
// encountered by Load: all initial packages and all
// dependencies, including incomplete ones.
AllPackages map[*types.Package]*PackageInfo
} }
imp := &Importer{ func (conf *Config) fset() *token.FileSet {
Fset: token.NewFileSet(), if conf.Fset == nil {
Config: config, conf.Fset = token.NewFileSet()
importfn: importfn,
srcpkgs: make(map[string]bool),
imported: make(map[string]*importInfo),
} }
return imp return conf.Fset
} }
// AllPackages returns a new slice containing all complete packages // ParseFile is a convenience function that invokes the parser using
// loaded by importer imp. // the Config's FileSet, which is initialized if nil.
// //
// This returns only packages that were loaded from source or directly func (conf *Config) ParseFile(filename string, src interface{}, mode parser.Mode) (*ast.File, error) {
// imported from a source package. It does not include packages return parser.ParseFile(conf.fset(), filename, src, mode)
// indirectly referenced by a binary package; they are found in
// config.TypeChecker.Packages.
// TODO(adonovan): rethink this API.
//
func (imp *Importer) AllPackages() []*PackageInfo {
return append([]*PackageInfo(nil), imp.allPackages...)
} }
// doImport imports the package denoted by path. // FromArgsUsage is a partial usage message that applications calling
// It implements the types.Importer prototype. // FromArgs may wish to include in their -help output.
// const FromArgsUsage = `
// 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.
//
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
}
info, err := imp.ImportPackage(path)
if err != nil {
return nil, err
}
// Update the type checker's package map on success.
imports[path] = info.Pkg
return info.Pkg, nil
}
// ImportPackage imports the package whose import path is path, plus
// its necessary dependencies.
//
// Precondition: path != "unsafe".
//
func (imp *Importer) ImportPackage(path string) (*PackageInfo, error) {
ii, ok := imp.imported[path]
if !ok {
ii = &importInfo{path: path}
imp.imported[path] = ii
// Find and create the actual package.
if augment, ok := imp.srcpkgs[ii.path]; ok || imp.Config.SourceImports {
which := "g" // load *.go files
if augment {
which = "gt" // augment package by in-package *_test.go files
}
imp.loadFromSource(ii, which)
} else {
imp.loadFromBinary(ii)
}
if ii.info != nil {
ii.info.Importable = true
}
}
return ii.info, ii.err
}
// loadFromBinary implements package loading from the client-supplied
// external source, e.g. object files from the gc compiler.
//
func (imp *Importer) loadFromBinary(ii *importInfo) {
pkg, err := imp.importfn(imp.Config.TypeChecker.Packages, ii.path)
if pkg != nil {
ii.info = &PackageInfo{Pkg: pkg}
imp.allPackages = append(imp.allPackages, ii.info)
} else {
ii.err = err
}
}
// loadFromSource implements package loading by parsing Go source files
// located by go/build. which indicates which files to include in the
// package.
//
func (imp *Importer) loadFromSource(ii *importInfo, which string) {
if files, err := parsePackageFiles(imp.Config.build(), imp.Fset, ii.path, which); err == nil {
// Type-check the package.
ii.info = imp.CreatePackage(ii.path, files...)
} else {
ii.err = err
}
}
// CreatePackage creates and type-checks a package from the specified
// list of parsed files, importing their dependencies. It returns a
// PackageInfo containing the resulting types.Package, the ASTs, and
// other type information.
//
// The order of files determines the package initialization order.
//
// 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".
//
// The resulting package is accessible via AllPackages() but is not
// importable, i.e. no 'import' spec can resolve to it.
//
// CreatePackage never fails, but the resulting package may contain type
// errors; the first of these is recorded in PackageInfo.Err.
//
func (imp *Importer) CreatePackage(path string, files ...*ast.File) *PackageInfo {
info := &PackageInfo{
Files: files,
Info: types.Info{
Types: make(map[ast.Expr]types.Type),
Values: make(map[ast.Expr]exact.Value),
Objects: 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),
},
}
// Use a copy of the types.Config so we can vary IgnoreFuncBodies.
tc := imp.Config.TypeChecker
tc.IgnoreFuncBodies = false
if f := imp.Config.TypeCheckFuncBodies; f != nil {
tc.IgnoreFuncBodies = !f(path)
}
if tc.Error == nil {
tc.Error = func(e error) { fmt.Fprintln(os.Stderr, e) }
}
tc.Import = imp.doImport // doImport wraps the user's importfn, effectively
info.Pkg, info.Err = tc.Check(path, imp.Fset, files, &info.Info)
imp.allPackages = append(imp.allPackages, info)
return info
}
// 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 packages. <args> is a list of arguments denoting a set of initial packages.
Each argument may take one of two forms: Each argument may take one of two forms:
@ -326,41 +251,15 @@ Each argument may take one of two forms:
A '--' argument terminates the list of packages. A '--' argument terminates the list of packages.
` `
// LoadInitialPackages interprets args as a set of packages, loads // FromArgs interprets args as a set of initial packages to load from
// those packages and their dependencies, and returns them. // source and updates the configuration. It returns the list of
// unconsumed arguments.
// //
// It is intended for use in command-line interfaces that require a // It is intended for use in command-line interfaces that require a
// set of initial packages to be specified; see InitialPackagesUsage // set of initial packages to be specified; see FromArgsUsage message
// message for details. // for details.
// //
// The second result parameter returns the list of unconsumed func (conf *Config) FromArgs(args []string) (rest []string, err error) {
// 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
var seenAugmented bool
for len(args) > 0 { for len(args) > 0 {
arg := args[0] arg := args[0]
args = args[1:] args = args[1:]
@ -371,134 +270,114 @@ func (imp *Importer) LoadInitialPackages(args []string) ([]*PackageInfo, []strin
if strings.HasSuffix(arg, ".go") { if strings.HasSuffix(arg, ".go") {
// Assume arg is a comma-separated list of *.go files // Assume arg is a comma-separated list of *.go files
// comprising a single package. // comprising a single package.
pkg, err := initialPackageFromFiles(imp.Fset, arg) err = conf.CreateFromFilenames(strings.Split(arg, ",")...)
if err != nil {
return nil, nil, err
}
pkgs = append(pkgs, pkg)
} else { } else {
// Assume arg is a directory name denoting a // Assume arg is a directory name denoting a
// package, perhaps plus an external test // package, perhaps plus an external test
// package unless prefixed by "notest:". // package unless prefixed by "notest:".
path := strings.TrimPrefix(arg, "notest:") if path := strings.TrimPrefix(arg, "notest:"); path != arg {
conf.Import(path)
} else {
err = conf.ImportWithTests(path)
}
}
if err != nil {
return nil, err
}
}
return args, nil
}
// CreateFromFilenames is a convenience function that parses the
// specified *.go files and adds a package entry for them to
// conf.CreatePkgs.
//
func (conf *Config) CreateFromFilenames(filenames ...string) error {
files, err := parseFiles(conf.fset(), ".", filenames...)
if err != nil {
return err
}
conf.CreateFromFiles(files...)
return nil
}
// CreateFromFiles is a convenience function that adds a CreatePkgs
// entry for the specified parsed files.
//
// Precondition: conf.Fset is non-nil and was the fileset used to parse
// the files. (e.g. the files came from conf.ParseFile().)
//
func (conf *Config) CreateFromFiles(files ...*ast.File) {
if conf.Fset == nil {
panic("nil Fset")
}
conf.CreatePkgs = append(conf.CreatePkgs, 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 <path>_test"
// declaration, an additional package comprising just those files will
// be added to CreatePkgs.
//
func (conf *Config) ImportWithTests(path string) error {
if path == "unsafe" { if path == "unsafe" {
continue // ignore; has no PackageInfo return nil // ignore; not a real package
}
pkgs = append(pkgs, &initialPkg{
path: path,
importable: true,
})
imp.srcpkgs[path] = false // unaugmented source package
if path != arg {
continue // had "notest:" prefix
} }
conf.Import(path)
// TODO(adonovan): due to limitations of the current type // TODO(adonovan): due to limitations of the current type
// checker design, we can augment at most one package. // checker design, we can augment at most one package.
if seenAugmented { for _, augmented := range conf.ImportPkgs {
continue // don't attempt a second if augmented {
return nil // don't attempt a second
}
} }
// Load the external test package. // Load the external test package.
xtestFiles, err := parsePackageFiles(imp.Config.build(), imp.Fset, path, "x") xtestFiles, err := parsePackageFiles(conf.build(), conf.fset(), path, "x")
if err != nil { if err != nil {
return nil, nil, err return err
} }
if len(xtestFiles) > 0 { if len(xtestFiles) > 0 {
pkgs = append(pkgs, &initialPkg{ conf.CreateFromFiles(xtestFiles...)
path: path + "_test",
importable: false,
files: xtestFiles,
})
} }
// Mark the non-xtest package for augmentation with // Mark the non-xtest package for augmentation with
// in-package *_test.go files when we import it below. // in-package *_test.go files when we import it below.
imp.srcpkgs[path] = true conf.ImportPkgs[path] = true
seenAugmented = true return nil
}
} }
// Pass 2: type-check each set of files to make a package. // Import is a convenience function that adds path to ImportPkgs, the
var infos []*PackageInfo // set of initial packages that will be imported from source.
for _, pkg := range pkgs {
var info *PackageInfo
if pkg.importable {
var err error
info, err = imp.ImportPackage(pkg.path)
if err != nil {
return nil, nil, err // e.g. parse error (but not type error)
}
} else {
info = imp.CreatePackage(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) { func (conf *Config) Import(path string) {
filenames := strings.Split(arg, ",") if path == "unsafe" {
for _, filename := range filenames { return // ignore; not a real package
if !strings.HasSuffix(filename, ".go") {
return nil, fmt.Errorf("not a *.go source file: %q", filename)
} }
if conf.ImportPkgs == nil {
conf.ImportPkgs = make(map[string]bool)
} }
conf.ImportPkgs[path] = false // unaugmented source package
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
} }
// PathEnclosingInterval returns the PackageInfo and ast.Node that // PathEnclosingInterval returns the PackageInfo and ast.Node that
// contain source interval [start, end), and all the node's ancestors // 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 the // up to the AST root. It searches all ast.Files of all packages in prog.
// Importer imp. exact is defined as for astutil.PathEnclosingInterval. // exact is defined as for astutil.PathEnclosingInterval.
// //
// The result is (nil, nil, false) if not found. // The result is (nil, nil, false) if not found.
// //
func (imp *Importer) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) { func (prog *Program) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) {
for _, info := range imp.allPackages { for _, info := range prog.AllPackages {
for _, f := range info.Files { for _, f := range info.Files {
if !tokenFileContainsPos(imp.Fset.File(f.Package), start) { if !tokenFileContainsPos(prog.Fset.File(f.Pos()), start) {
continue continue
} }
if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil { if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil {
@ -509,9 +388,252 @@ func (imp *Importer) PathEnclosingInterval(start, end token.Pos) (pkg *PackageIn
return nil, nil, false return nil, nil, false
} }
// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos) // InitialPackages returns a new slice containing the set of initial
func tokenFileContainsPos(f *token.File, pos token.Pos) bool { // packages (Created + Imported) in unspecified order.
p := int(pos) //
base := f.Base() func (prog *Program) InitialPackages() []*PackageInfo {
return base <= p && p < base+f.Size() infos := make([]*PackageInfo, 0, len(prog.Created)+len(prog.Imported))
infos = append(infos, prog.Created...)
for _, info := range prog.Imported {
infos = append(infos, info)
}
return infos
}
// ---------- Implementation ----------
// importer holds the working state of the algorithm.
type importer struct {
conf *Config // the client configuration
prog *Program // resulting program
imported map[string]*importInfo // all imported packages (incl. failures) by import path
}
// importInfo tracks the success or failure of a single import.
type importInfo struct {
info *PackageInfo // results of typechecking (including type errors)
err error // reason for failure to make a package
}
// Load creates the initial packages specified by conf.{Create,Import}Pkgs,
// loading their dependencies packages as needed.
//
// On success, it returns a Program containing a PackageInfo for each
// package; all are well-typed.
//
// It is an error if no packages were loaded.
//
func (conf *Config) Load() (*Program, error) {
// Initialize by setting the conf's copy, so all copies of
// TypeChecker agree on the identity of the map.
if conf.TypeChecker.Packages == nil {
conf.TypeChecker.Packages = make(map[string]*types.Package)
}
prog := &Program{
Fset: conf.fset(),
Imported: make(map[string]*PackageInfo),
ImportMap: conf.TypeChecker.Packages,
AllPackages: make(map[*types.Package]*PackageInfo),
}
imp := importer{
conf: conf,
prog: prog,
imported: make(map[string]*importInfo),
}
for path := range conf.ImportPkgs {
info, err := imp.importPackage(path)
if err != nil {
return nil, err // e.g. parse error (but not type error)
}
prog.Imported[path] = info
}
for _, files := range conf.CreatePkgs {
pkgname, err := packageName(files, conf.Fset)
if err != nil {
return nil, err
}
// TODO(adonovan): pkgnames are not unique, but the
// typechecker assumes they are in its Id() logic.
prog.Created = append(prog.Created, imp.createPackage(pkgname, files...))
}
if len(prog.Imported)+len(prog.Created) == 0 {
return nil, errors.New("no *.go source files nor packages were specified")
}
// Report errors in indirectly imported packages.
var errpkgs []string
for _, info := range prog.AllPackages {
if info.err != nil {
errpkgs = append(errpkgs, info.Pkg.Path())
}
}
if errpkgs != nil {
return nil, fmt.Errorf("couldn't load packages due to type errors: %s",
strings.Join(errpkgs, ", "))
}
// Create infos for indirectly imported packages.
// e.g. incomplete packages without syntax, loaded from export data.
for _, obj := range prog.ImportMap {
if prog.AllPackages[obj] == nil {
prog.AllPackages[obj] = &PackageInfo{Pkg: obj, Importable: true}
}
}
return prog, nil
}
// build returns the effective build context.
func (conf *Config) build() *build.Context {
if conf.Build != nil {
return conf.Build
}
return &build.Default
}
// doImport imports the package denoted by path.
// It implements the types.Importer signature.
//
// 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.
//
func (imp *importer) doImport(imports map[string]*types.Package, path string) (*types.Package, error) {
// Package unsafe is handled specially, and has no PackageInfo.
if path == "unsafe" {
return types.Unsafe, nil
}
info, err := imp.importPackage(path)
if err != nil {
return nil, err
}
// Update the type checker's package map on success.
imports[path] = info.Pkg
return info.Pkg, nil
}
// importPackage imports the package with the given import path, plus
// its dependencies.
//
// Precondition: path != "unsafe".
//
func (imp *importer) importPackage(path string) (*PackageInfo, error) {
ii, ok := imp.imported[path]
if !ok {
// In preorder, initialize the map entry to a cycle
// error in case importPackage(path) is called again
// before the import is completed.
// TODO(adonovan): go/types should be responsible for
// reporting cycles; see bug 7114.
ii = &importInfo{err: fmt.Errorf("import cycle in package %s", path)}
imp.imported[path] = ii
// Find and create the actual package.
if augment, ok := imp.conf.ImportPkgs[path]; ok || imp.conf.SourceImports {
which := "g" // load *.go files
if augment {
which = "gt" // augment package by in-package *_test.go files
}
ii.info, ii.err = imp.importFromSource(path, which)
} else {
ii.info, ii.err = imp.importFromBinary(path)
}
if ii.info != nil {
ii.info.Importable = true
}
}
return ii.info, ii.err
}
// importFromBinary implements package loading from the client-supplied
// external source, e.g. object files from the gc compiler.
//
func (imp *importer) importFromBinary(path string) (*PackageInfo, error) {
// Determine the caller's effective Import function.
importfn := imp.conf.TypeChecker.Import
if importfn == nil {
importfn = gcimporter.Import
}
pkg, err := importfn(imp.conf.TypeChecker.Packages, path)
if err != nil {
return nil, err
}
info := &PackageInfo{Pkg: pkg}
imp.prog.AllPackages[pkg] = info
return info, nil
}
// importFromSource implements package loading by parsing Go source files
// located by go/build. which indicates which files to include in the
// package.
//
func (imp *importer) importFromSource(path string, which string) (*PackageInfo, error) {
files, err := parsePackageFiles(imp.conf.build(), imp.conf.fset(), path, which)
if err != nil {
return nil, err
}
// Type-check the package.
return imp.createPackage(path, files...), nil
}
// createPackage creates and type-checks a package from the specified
// list of parsed files, importing their dependencies. It returns a
// PackageInfo containing the resulting types.Package, the ASTs, and
// other type information.
//
// The order of files determines the package initialization order.
//
// path will be the resulting package's Path().
// For an ad-hoc package, this is not necessarily unique.
//
// The resulting package is accessible via AllPackages but is not
// importable, i.e. no 'import' spec can resolve to it.
//
// createPackage never fails, but the resulting package may contain type
// errors; the first of these is recorded in PackageInfo.err.
//
func (imp *importer) createPackage(path string, files ...*ast.File) *PackageInfo {
info := &PackageInfo{
Files: files,
Info: types.Info{
Types: make(map[ast.Expr]types.Type),
Values: make(map[ast.Expr]exact.Value),
Objects: 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),
},
}
// Use a copy of the types.Config so we can vary IgnoreFuncBodies.
tc := imp.conf.TypeChecker
tc.IgnoreFuncBodies = false
if f := imp.conf.TypeCheckFuncBodies; f != nil {
tc.IgnoreFuncBodies = !f(path)
}
if tc.Error == nil {
tc.Error = func(e error) { fmt.Fprintln(os.Stderr, e) }
}
tc.Import = imp.doImport // doImport wraps the user's importfn, effectively
info.Pkg, info.err = tc.Check(path, imp.conf.fset(), files, &info.Info)
imp.prog.AllPackages[info.Pkg] = info
return info
} }

View File

@ -6,52 +6,73 @@ package importer_test
import ( import (
"fmt" "fmt"
"sort"
"testing" "testing"
"code.google.com/p/go.tools/importer" "code.google.com/p/go.tools/importer"
) )
func TestLoadInitialPackages(t *testing.T) { func loadFromArgs(args []string) (prog *importer.Program, rest []string, err error) {
conf := &importer.Config{}
rest, err = conf.FromArgs(args)
if err == nil {
prog, err = conf.Load()
}
return
}
func TestLoadFromArgs(t *testing.T) {
// Failed load: bad first import path causes parsePackageFiles to fail. // Failed load: bad first import path causes parsePackageFiles to fail.
args := []string{"nosuchpkg", "errors"} args := []string{"nosuchpkg", "errors"}
if _, _, err := importer.New(&importer.Config{}).LoadInitialPackages(args); err == nil { if _, _, err := loadFromArgs(args); err == nil {
t.Errorf("LoadInitialPackages(%q) succeeded, want failure", args) t.Errorf("loadFromArgs(%q) succeeded, want failure", args)
} else { } else {
// cannot find package: ok. // cannot find package: ok.
} }
// Failed load: bad second import path proceeds to doImport0, which fails. // Failed load: bad second import path proceeds to doImport0, which fails.
args = []string{"errors", "nosuchpkg"} args = []string{"errors", "nosuchpkg"}
if _, _, err := importer.New(&importer.Config{}).LoadInitialPackages(args); err == nil { if _, _, err := loadFromArgs(args); err == nil {
t.Errorf("LoadInitialPackages(%q) succeeded, want failure", args) t.Errorf("loadFromArgs(%q) succeeded, want failure", args)
} else { } else {
// cannot find package: ok // cannot find package: ok
} }
// Successful load. // Successful load.
args = []string{"fmt", "errors", "testdata/a.go,testdata/b.go", "--", "surplus"} args = []string{"fmt", "errors", "testdata/a.go,testdata/b.go", "--", "surplus"}
imp := importer.New(&importer.Config{}) prog, rest, err := loadFromArgs(args)
infos, rest, err := imp.LoadInitialPackages(args)
if err != nil { if err != nil {
t.Errorf("LoadInitialPackages(%q) failed: %s", args, err) t.Errorf("loadFromArgs(%q) failed: %s", args, err)
return return
} }
if got, want := fmt.Sprint(rest), "[surplus]"; got != want { if got, want := fmt.Sprint(rest), "[surplus]"; got != want {
t.Errorf("LoadInitialPackages(%q) rest: got %s, want %s", got, want) t.Errorf("loadFromArgs(%q) rest: got %s, want %s", got, want)
} }
// Check list of initial packages. // Check list of Created packages.
var pkgnames []string var pkgnames []string
for _, info := range infos { for _, info := range prog.Created {
pkgnames = append(pkgnames, info.Pkg.Path()) pkgnames = append(pkgnames, info.Pkg.Path())
} }
// Only the first import path (currently) contributes tests. // Only the first import path (currently) contributes tests.
if got, want := fmt.Sprint(pkgnames), "[fmt fmt_test errors P]"; got != want { if got, want := fmt.Sprint(pkgnames), "[fmt_test P]"; got != want {
t.Errorf("InitialPackages: got %s, want %s", got, want) t.Errorf("Created: got %s, want %s", got, want)
} }
// Check set of Imported packages.
pkgnames = nil
for path := range prog.Imported {
pkgnames = append(pkgnames, path)
}
sort.Strings(pkgnames)
// Only the first import path (currently) contributes tests.
if got, want := fmt.Sprint(pkgnames), "[errors fmt]"; got != want {
t.Errorf("Loaded: got %s, want %s", got, want)
}
// Check set of transitive packages. // Check set of transitive packages.
// There are >30 and the set may grow over time, so only check a few. // There are >30 and the set may grow over time, so only check a few.
all := map[string]struct{}{} all := map[string]struct{}{}
for _, info := range imp.AllPackages() { for _, info := range prog.AllPackages {
all[info.Pkg.Path()] = struct{}{} all[info.Pkg.Path()] = struct{}{}
} }
want := []string{"strings", "time", "runtime", "testing", "unicode"} want := []string{"strings", "time", "runtime", "testing", "unicode"}

View File

@ -20,8 +20,8 @@ import (
type PackageInfo struct { type PackageInfo struct {
Pkg *types.Package Pkg *types.Package
Importable bool // true if 'import "Pkg.Path()"' would resolve to this 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 Files []*ast.File // abstract syntax for the package's files
err error // non-nil if the package had static errors
types.Info // type-checker deductions. types.Info // type-checker deductions.
} }

View File

@ -82,8 +82,8 @@ func TestEnclosingFunction(t *testing.T) {
"900", "func@2.27"}, "900", "func@2.27"},
} }
for _, test := range tests { for _, test := range tests {
imp := importer.New(&importer.Config{}) conf := importer.Config{Fset: token.NewFileSet()}
f, start, end := findInterval(t, imp.Fset, test.input, test.substr) f, start, end := findInterval(t, conf.Fset, test.input, test.substr)
if f == nil { if f == nil {
continue continue
} }
@ -92,13 +92,16 @@ func TestEnclosingFunction(t *testing.T) {
t.Errorf("EnclosingFunction(%q) not exact", test.substr) t.Errorf("EnclosingFunction(%q) not exact", test.substr)
continue continue
} }
mainInfo := imp.CreatePackage("main", f)
prog := ssa.NewProgram(imp.Fset, 0) conf.CreateFromFiles(f)
if err := prog.CreatePackages(imp); err != nil {
iprog, err := conf.Load()
if err != nil {
t.Error(err) t.Error(err)
continue continue
} }
pkg := prog.Package(mainInfo.Pkg) prog := ssa.Create(iprog, 0)
pkg := prog.Package(iprog.Created[0].Pkg)
pkg.Build() pkg.Build()
name := "(none)" name := "(none)"

View File

@ -8,12 +8,12 @@ package importer
// and used by it. // and used by it.
import ( import (
"fmt"
"go/ast" "go/ast"
"go/build" "go/build"
"go/parser" "go/parser"
"go/token" "go/token"
"path/filepath" "path/filepath"
"strconv"
"sync" "sync"
) )
@ -55,13 +55,13 @@ func parsePackageFiles(ctxt *build.Context, fset *token.FileSet, path string, wh
} }
filenames = append(filenames, s...) filenames = append(filenames, s...)
} }
return ParseFiles(fset, bp.Dir, filenames...) return parseFiles(fset, bp.Dir, filenames...)
} }
// ParseFiles parses the Go source files files within directory dir // parseFiles parses the Go source files files within directory dir
// and returns their ASTs, or the first parse error if any. // and returns their ASTs, or the first parse error if any.
// //
func ParseFiles(fset *token.FileSet, dir string, files ...string) ([]*ast.File, error) { func parseFiles(fset *token.FileSet, dir string, files ...string) ([]*ast.File, error) {
var wg sync.WaitGroup var wg sync.WaitGroup
n := len(files) n := len(files)
parsed := make([]*ast.File, n, n) parsed := make([]*ast.File, n, n)
@ -104,26 +104,32 @@ func unreachable() {
panic("unreachable") panic("unreachable")
} }
// importsOf returns the set of paths imported by the specified files. func packageName(files []*ast.File, fset *token.FileSet) (string, error) {
func importsOf(p string, files []*ast.File) map[string]bool { if len(files) == 0 {
imports := make(map[string]bool) return "", fmt.Errorf("no files in package")
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 { // Take the package name from the 'package decl' in each file,
spec := spec.(*ast.ImportSpec) // all of which must match.
if path, _ := strconv.Unquote(spec.Path.Value); path != "C" { pkgname := files[0].Name.Name
imports[path] = true for _, 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, filename(files[0], fset),
pn, filename(file, fset))
return "", err
} }
// TODO(adonovan): check dirnames are equal, like 'go build' does.
} }
} else { return pkgname, nil
break outer // stop at the first non-import
} }
// 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()
} }
}
return imports func filename(file *ast.File, fset *token.FileSet) string {
return fset.File(file.Pos()).Name()
} }

View File

@ -183,7 +183,7 @@ func (res *Result) Serial() *serial.Result {
// Query runs a single oracle query. // Query runs a single oracle query.
// //
// args specify the main package in importer.LoadInitialPackages syntax. // args specify the main package in (*importer.Config).FromArgs syntax.
// mode is the query mode ("callers", etc). // mode is the query mode ("callers", etc).
// ptalog is the (optional) pointer-analysis log file. // ptalog is the (optional) pointer-analysis log file.
// buildContext is the go/build configuration for locating packages. // buildContext is the go/build configuration for locating packages.
@ -192,8 +192,11 @@ func (res *Result) Serial() *serial.Result {
// Clients that intend to perform multiple queries against the same // Clients that intend to perform multiple queries against the same
// analysis scope should use this pattern instead: // analysis scope should use this pattern instead:
// //
// imp := importer.New(&importer.Config{Build: buildContext, SourceImports: true}) // conf := importer.Config{Build: buildContext, SourceImports: true}
// o, err := oracle.New(imp, args, nil) // ... populate config, e.g. conf.FromArgs(args) ...
// iprog, err := conf.Load()
// if err != nil { ... }
// o, err := oracle.New(iprog, nil, false)
// if err != nil { ... } // if err != nil { ... }
// for ... { // for ... {
// qpos, err := oracle.ParseQueryPos(imp, pos, needExact) // qpos, err := oracle.ParseQueryPos(imp, pos, needExact)
@ -219,29 +222,44 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
return nil, fmt.Errorf("invalid mode type: %q", mode) return nil, fmt.Errorf("invalid mode type: %q", mode)
} }
impcfg := importer.Config{Build: buildContext, SourceImports: true} conf := importer.Config{Build: buildContext, SourceImports: true}
// Determine initial packages.
args, err := conf.FromArgs(args)
if err != nil {
return nil, err
}
if len(args) > 0 {
return nil, fmt.Errorf("surplus arguments: %q", args)
}
// For queries needing only a single typed package, // For queries needing only a single typed package,
// reduce the analysis scope to that package. // reduce the analysis scope to that package.
if minfo.needs&(needSSA|needRetainTypeInfo) == 0 { if minfo.needs&(needSSA|needRetainTypeInfo) == 0 {
reduceScope(pos, &impcfg, &args) reduceScope(pos, &conf)
} }
// TODO(adonovan): report type errors to the user via Serial // TODO(adonovan): report type errors to the user via Serial
// types, not stderr? // types, not stderr?
// impcfg.TypeChecker.Error = func(err error) { // conf.TypeChecker.Error = func(err error) {
// E := err.(types.Error) // E := err.(types.Error)
// fmt.Fprintf(os.Stderr, "%s: %s\n", E.Fset.Position(E.Pos), E.Msg) // fmt.Fprintf(os.Stderr, "%s: %s\n", E.Fset.Position(E.Pos), E.Msg)
// } // }
imp := importer.New(&impcfg)
o, err := newOracle(imp, args, ptalog, minfo.needs, reflection) // Load/parse/type-check the program.
iprog, err := conf.Load()
if err != nil {
return nil, err
}
o, err := newOracle(iprog, ptalog, minfo.needs, reflection)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var qpos *QueryPos var qpos *QueryPos
if minfo.needs&(needPos|needExactPos) != 0 { if minfo.needs&(needPos|needExactPos) != 0 {
qpos, err = ParseQueryPos(imp, pos, minfo.needs&needExactPos != 0) qpos, err = ParseQueryPos(iprog, pos, minfo.needs&needExactPos != 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -249,7 +267,7 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
// SSA is built and we have the QueryPos. // SSA is built and we have the QueryPos.
// Release the other ASTs and type info to the GC. // Release the other ASTs and type info to the GC.
imp = nil iprog = nil
return o.query(minfo, qpos) return o.query(minfo, qpos)
} }
@ -262,12 +280,7 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
// //
// TODO(adonovan): this is a real mess... but it's fast. // TODO(adonovan): this is a real mess... but it's fast.
// //
func reduceScope(pos string, impcfg *importer.Config, args *[]string) { func reduceScope(pos string, conf *importer.Config) {
// TODO(adonovan): make the 'args' argument of
// (*Importer).LoadInitialPackages part of the
// importer.Config, and inline LoadInitialPackages into
// NewImporter. Then we won't need the 'args' argument.
fqpos, err := fastQueryPos(pos) fqpos, err := fastQueryPos(pos)
if err != nil { if err != nil {
return // bad query return // bad query
@ -276,7 +289,7 @@ func reduceScope(pos string, impcfg *importer.Config, args *[]string) {
// TODO(adonovan): fix: this gives the wrong results for files // TODO(adonovan): fix: this gives the wrong results for files
// in non-importable packages such as tests and ad-hoc packages // in non-importable packages such as tests and ad-hoc packages
// specified as a list of files (incl. the oracle's tests). // specified as a list of files (incl. the oracle's tests).
_, importPath, err := guessImportPath(fqpos.fset.File(fqpos.start).Name(), impcfg.Build) _, importPath, err := guessImportPath(fqpos.fset.File(fqpos.start).Name(), conf.Build)
if err != nil { if err != nil {
return // can't find GOPATH dir return // can't find GOPATH dir
} }
@ -287,9 +300,9 @@ func reduceScope(pos string, impcfg *importer.Config, args *[]string) {
// Check that it's possible to load the queried package. // Check that it's possible to load the queried package.
// (e.g. oracle tests contain different 'package' decls in same dir.) // (e.g. oracle tests contain different 'package' decls in same dir.)
// Keep consistent with logic in importer/util.go! // Keep consistent with logic in importer/util.go!
ctxt2 := *impcfg.Build cfg2 := *conf.Build
ctxt2.CgoEnabled = false cfg2.CgoEnabled = false
bp, err := ctxt2.Import(importPath, "", 0) bp, err := cfg2.Import(importPath, "", 0)
if err != nil { if err != nil {
return // no files for package return // no files for package
} }
@ -302,59 +315,50 @@ func reduceScope(pos string, impcfg *importer.Config, args *[]string) {
// return // not found // return // not found
// found: // found:
impcfg.TypeCheckFuncBodies = func(p string) bool { return p == importPath } conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
*args = []string{importPath}
// Ignore packages specified on command line.
conf.CreatePkgs = nil
conf.ImportPkgs = nil
// Instead load just the one containing the query position
// (and possibly its corresponding tests/production code).
// TODO(adonovan): set 'augment' based on which file list
// contains
_ = conf.ImportWithTests(importPath) // ignore error
} }
// New constructs a new Oracle that can be used for a sequence of queries. // New constructs a new Oracle that can be used for a sequence of queries.
// //
// imp will be used to load source code for imported packages. // iprog specifies the program to analyze.
// It must not yet have loaded any packages.
//
// args specify the main package in importer.LoadInitialPackages syntax.
//
// ptalog is the (optional) pointer-analysis log file. // ptalog is the (optional) pointer-analysis log file.
// reflection determines whether to model reflection soundly (currently slow). // reflection determines whether to model reflection soundly (currently slow).
// //
func New(imp *importer.Importer, args []string, ptalog io.Writer, reflection bool) (*Oracle, error) { func New(iprog *importer.Program, ptalog io.Writer, reflection bool) (*Oracle, error) {
return newOracle(imp, args, ptalog, needAll, reflection) return newOracle(iprog, ptalog, needAll, reflection)
} }
func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) { func newOracle(iprog *importer.Program, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) {
o := &Oracle{fset: imp.Fset} o := &Oracle{fset: iprog.Fset}
// Load/parse/type-check program from args.
initialPkgInfos, args, err := imp.LoadInitialPackages(args)
if err != nil {
return nil, err // I/O or parser error
}
if len(args) > 0 {
return nil, fmt.Errorf("surplus arguments: %q", args)
}
// Retain type info for all ASTs in the program. // Retain type info for all ASTs in the program.
if needs&needRetainTypeInfo != 0 { if needs&needRetainTypeInfo != 0 {
m := make(map[*types.Package]*importer.PackageInfo) o.typeInfo = iprog.AllPackages
for _, p := range imp.AllPackages() {
m[p.Pkg] = p
}
o.typeInfo = m
} }
// Create SSA package for the initial packages and their dependencies. // Create SSA package for the initial packages and their dependencies.
if needs&needSSA != 0 { if needs&needSSA != 0 {
prog := ssa.NewProgram(o.fset, 0) var mode ssa.BuilderMode
if needs&needSSADebug != 0 {
// Create SSA packages. mode |= ssa.GlobalDebug
if err := prog.CreatePackages(imp); err != nil {
return nil, err
} }
prog := ssa.Create(iprog, mode)
// For each initial package (specified on the command line), // For each initial package (specified on the command line),
// if it has a main function, analyze that, // if it has a main function, analyze that,
// otherwise analyze its tests, if any. // otherwise analyze its tests, if any.
var testPkgs, mains []*ssa.Package var testPkgs, mains []*ssa.Package
for _, info := range initialPkgInfos { for _, info := range iprog.InitialPackages() {
initialPkg := prog.Package(info.Pkg) initialPkg := prog.Package(info.Pkg)
// Add package to the pointer analysis scope. // Add package to the pointer analysis scope.
@ -376,12 +380,6 @@ func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs in
o.ptaConfig.Reflection = reflection o.ptaConfig.Reflection = reflection
o.ptaConfig.Mains = mains o.ptaConfig.Mains = mains
if needs&needSSADebug != 0 {
for _, pkg := range prog.AllPackages() {
pkg.SetDebugMode(true)
}
}
o.prog = prog o.prog = prog
} }
@ -423,23 +421,23 @@ func (o *Oracle) query(minfo *modeInfo, qpos *QueryPos) (*Result, error) {
// this is appropriate for queries that allow fairly arbitrary syntax, // this is appropriate for queries that allow fairly arbitrary syntax,
// e.g. "describe". // e.g. "describe".
// //
func ParseQueryPos(imp *importer.Importer, posFlag string, needExact bool) (*QueryPos, error) { func ParseQueryPos(iprog *importer.Program, posFlag string, needExact bool) (*QueryPos, error) {
filename, startOffset, endOffset, err := parsePosFlag(posFlag) filename, startOffset, endOffset, err := parsePosFlag(posFlag)
if err != nil { if err != nil {
return nil, err return nil, err
} }
start, end, err := findQueryPos(imp.Fset, filename, startOffset, endOffset) start, end, err := findQueryPos(iprog.Fset, filename, startOffset, endOffset)
if err != nil { if err != nil {
return nil, err return nil, err
} }
info, path, exact := imp.PathEnclosingInterval(start, end) info, path, exact := iprog.PathEnclosingInterval(start, end)
if path == nil { if path == nil {
return nil, fmt.Errorf("no syntax here") return nil, fmt.Errorf("no syntax here")
} }
if needExact && !exact { if needExact && !exact {
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0])) return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
} }
return &QueryPos{imp.Fset, start, end, path, exact, info}, nil return &QueryPos{iprog.Fset, start, end, path, exact, info}, nil
} }
// WriteTo writes the oracle query result res to out in a compiler diagnostic format. // WriteTo writes the oracle query result res to out in a compiler diagnostic format.

View File

@ -260,24 +260,29 @@ func TestMultipleQueries(t *testing.T) {
// Importer // Importer
var buildContext = build.Default var buildContext = build.Default
buildContext.GOPATH = "testdata" buildContext.GOPATH = "testdata"
imp := importer.New(&importer.Config{Build: &buildContext}) conf := importer.Config{Build: &buildContext}
filename := "testdata/src/main/multi.go"
conf.CreateFromFilenames(filename)
iprog, err := conf.Load()
if err != nil {
t.Fatalf("Load failed: %s", err)
}
// Oracle // Oracle
filename := "testdata/src/main/multi.go" o, err := oracle.New(iprog, nil, true)
o, err := oracle.New(imp, []string{filename}, nil, true)
if err != nil { if err != nil {
t.Fatalf("oracle.New failed: %s", err) t.Fatalf("oracle.New failed: %s", err)
} }
// QueryPos // QueryPos
pos := filename + ":#54,#58" pos := filename + ":#54,#58"
qpos, err := oracle.ParseQueryPos(imp, pos, true) qpos, err := oracle.ParseQueryPos(iprog, pos, true)
if err != nil { if err != nil {
t.Fatalf("oracle.ParseQueryPos(%q) failed: %s", pos, err) t.Fatalf("oracle.ParseQueryPos(%q) failed: %s", pos, err)
} }
// SSA is built and we have the QueryPos. // SSA is built and we have the QueryPos.
// Release the other ASTs and type info to the GC. // Release the other ASTs and type info to the GC.
imp = nil iprog = nil
// Run different query modes on same scope and selection. // Run different query modes on same scope and selection.
out := new(bytes.Buffer) out := new(bytes.Buffer)

View File

@ -6,7 +6,6 @@ package pointer_test
import ( import (
"fmt" "fmt"
"go/parser"
"sort" "sort"
"code.google.com/p/go.tools/call" "code.google.com/p/go.tools/call"
@ -40,26 +39,27 @@ func main() {
} }
` `
// Construct an importer. // Construct an importer.
imp := importer.New(&importer.Config{SourceImports: true}) conf := importer.Config{SourceImports: true}
// Parse the input file. // Parse the input file.
file, err := parser.ParseFile(imp.Fset, "myprog.go", myprog, 0) file, err := conf.ParseFile("myprog.go", myprog, 0)
if err != nil { if err != nil {
fmt.Print(err) // parse error fmt.Print(err) // parse error
return return
} }
// Create single-file main package and import its dependencies. // Create single-file main package and import its dependencies.
mainInfo := imp.CreatePackage("main", file) conf.CreateFromFiles(file)
// Create SSA-form program representation. iprog, err := conf.Load()
var mode ssa.BuilderMode if err != nil {
prog := ssa.NewProgram(imp.Fset, mode)
if err := prog.CreatePackages(imp); err != nil {
fmt.Print(err) // type error in some package fmt.Print(err) // type error in some package
return return
} }
mainPkg := prog.Package(mainInfo.Pkg)
// Create SSA-form program representation.
prog := ssa.Create(iprog, 0)
mainPkg := prog.Package(iprog.Created[0].Pkg)
// Build SSA code for bodies of all functions in the whole program. // Build SSA code for bodies of all functions in the whole program.
prog.BuildAll() prog.BuildAll()

View File

@ -151,30 +151,28 @@ func findProbe(prog *ssa.Program, probes map[*ssa.CallCommon]pointer.Pointer, e
} }
func doOneInput(input, filename string) bool { func doOneInput(input, filename string) bool {
impctx := &importer.Config{SourceImports: true} conf := importer.Config{SourceImports: true}
imp := importer.New(impctx)
// Parsing. // Parsing.
f, err := parser.ParseFile(imp.Fset, filename, input, 0) f, err := conf.ParseFile(filename, input, 0)
if err != nil { if err != nil {
// TODO(adonovan): err is a scanner error list;
// display all errors not just first?
fmt.Println(err) fmt.Println(err)
return false return false
} }
// Create single-file main package and import its dependencies. // Create single-file main package and import its dependencies.
info := imp.CreatePackage("main", f) conf.CreateFromFiles(f)
iprog, err := conf.Load()
// SSA creation + building. if err != nil {
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
if err := prog.CreatePackages(imp); err != nil {
fmt.Println(err) fmt.Println(err)
return false return false
} }
// SSA creation + building.
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
prog.BuildAll() prog.BuildAll()
mainpkg := prog.Package(info.Pkg) mainpkg := prog.Package(iprog.Created[0].Pkg)
ptrmain := mainpkg // main package for the pointer analysis ptrmain := mainpkg // main package for the pointer analysis
if mainpkg.Func("main") == nil { if mainpkg.Func("main") == nil {
// No main function; assume it's a test. // No main function; assume it's a test.
@ -228,7 +226,7 @@ func doOneInput(input, filename string) bool {
continue continue
} }
mainFileScope := mainpkg.Object.Scope().Child(0) mainFileScope := mainpkg.Object.Scope().Child(0)
t, _, err = types.EvalNode(imp.Fset, texpr, mainpkg.Object, mainFileScope) t, _, err = types.EvalNode(prog.Fset, texpr, mainpkg.Object, mainFileScope)
if err != nil { if err != nil {
ok = false ok = false
// Don't print err since its location is bad. // Don't print err since its location is bad.

View File

@ -11,7 +11,7 @@ import "testing"
func log(f func(*testing.T)) { func log(f func(*testing.T)) {
// The PTS of f is the set of called tests. TestingQuux is not present. // The PTS of f is the set of called tests. TestingQuux is not present.
print(f) // @pointsto main.Test | main.TestFoo print(f) // @pointsto a.Test | a.TestFoo
} }
func Test(t *testing.T) { func Test(t *testing.T) {
@ -36,7 +36,7 @@ func ExampleBar() {
} }
// Excludes TestingQuux. // Excludes TestingQuux.
// @calls testing.tRunner -> main.Test // @calls testing.tRunner -> a.Test
// @calls testing.tRunner -> main.TestFoo // @calls testing.tRunner -> a.TestFoo
// @calls testing.runExample -> main.ExampleBar // @calls testing.runExample -> a.ExampleBar
// @calls (*testing.B).runN -> main.BenchmarkFoo // @calls (*testing.B).runN -> a.BenchmarkFoo

View File

@ -5,7 +5,6 @@
package ssa_test package ssa_test
import ( import (
"go/parser"
"reflect" "reflect"
"sort" "sort"
"strings" "strings"
@ -40,22 +39,24 @@ func main() {
w.Write(nil) // interface invoke of external declared method w.Write(nil) // interface invoke of external declared method
} }
` `
imp := importer.New(&importer.Config{})
f, err := parser.ParseFile(imp.Fset, "<input>", test, 0) // Create a single-file main package.
var conf importer.Config
f, err := conf.ParseFile("<input>", test, 0)
if err != nil {
t.Error(err)
return
}
conf.CreateFromFiles(f)
iprog, err := conf.Load()
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
mainInfo := imp.CreatePackage("main", f) prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
mainPkg := prog.Package(iprog.Created[0].Pkg)
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
if err := prog.CreatePackages(imp); err != nil {
t.Error(err)
return
}
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.Build() mainPkg.Build()
// The main package, its direct and indirect dependencies are loaded. // The main package, its direct and indirect dependencies are loaded.
@ -204,21 +205,22 @@ func TestTypesWithMethodSets(t *testing.T) {
}, },
} }
for i, test := range tests { for i, test := range tests {
imp := importer.New(&importer.Config{}) // Create a single-file main package.
var conf importer.Config
f, err := parser.ParseFile(imp.Fset, "<input>", test.input, 0) f, err := conf.ParseFile("<input>", test.input, 0)
if err != nil { if err != nil {
t.Errorf("test %d: %s", i, err) t.Errorf("test %d: %s", i, err)
continue continue
} }
conf.CreateFromFiles(f)
mainInfo := imp.CreatePackage("p", f) iprog, err := conf.Load()
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) if err != nil {
if err := prog.CreatePackages(imp); err != nil { t.Errorf("test %d: Load: %s", i, err)
t.Errorf("test %d: %s", i, err)
continue continue
} }
mainPkg := prog.Package(mainInfo.Pkg) prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
mainPkg := prog.Package(iprog.Created[0].Pkg)
prog.BuildAll() prog.BuildAll()
var typstrs []string var typstrs []string

View File

@ -8,11 +8,9 @@ package ssa
// See builder.go for explanation. // See builder.go for explanation.
import ( import (
"fmt"
"go/ast" "go/ast"
"go/token" "go/token"
"os" "os"
"strings"
"code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/importer" "code.google.com/p/go.tools/importer"
@ -28,25 +26,32 @@ const (
SanityCheckFunctions // Perform sanity checking of function bodies SanityCheckFunctions // Perform sanity checking of function bodies
NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers
BuildSerially // Build packages serially, not in parallel. BuildSerially // Build packages serially, not in parallel.
GlobalDebug // Enable debug info for all packages
) )
// NewProgram returns a new SSA Program initially containing no // Create returns a new SSA Program, creating all packages and all
// packages. // their members.
// //
// fset specifies the mapping from token positions to source location // Code for bodies of functions is not built until Build() is called
// that will be used by all ASTs of this program. // on the result.
// //
// mode controls diagnostics and checking during SSA construction. // mode controls diagnostics and checking during SSA construction.
// //
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program { func Create(iprog *importer.Program, mode BuilderMode) *Program {
return &Program{ prog := &Program{
Fset: fset, Fset: iprog.Fset,
imported: make(map[string]*Package), imported: make(map[string]*Package),
packages: make(map[*types.Package]*Package), packages: make(map[*types.Package]*Package),
boundMethodWrappers: make(map[*types.Func]*Function), boundMethodWrappers: make(map[*types.Func]*Function),
ifaceMethodWrappers: make(map[*types.Func]*Function), ifaceMethodWrappers: make(map[*types.Func]*Function),
mode: mode, mode: mode,
} }
for _, info := range iprog.AllPackages {
prog.CreatePackage(info)
}
return prog
} }
// memberFromObject populates package pkg with a member for the // memberFromObject populates package pkg with a member for the
@ -166,9 +171,6 @@ func membersFromDecl(pkg *Package, decl ast.Decl) {
// until a subsequent call to Package.Build(). // until a subsequent call to Package.Build().
// //
func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package { func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
if info.Err != nil {
panic(fmt.Sprintf("package %s has errors: %s", info, info.Err))
}
if p := prog.packages[info.Pkg]; p != nil { if p := prog.packages[info.Pkg]; p != nil {
return p // already loaded return p // already loaded
} }
@ -225,6 +227,10 @@ func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
} }
p.Members[initguard.Name()] = initguard p.Members[initguard.Name()] = initguard
if prog.mode&GlobalDebug != 0 {
p.SetDebugMode(true)
}
if prog.mode&LogPackages != 0 { if prog.mode&LogPackages != 0 {
p.DumpTo(os.Stderr) p.DumpTo(os.Stderr)
} }
@ -241,40 +247,6 @@ func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
return p 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
// Create source packages and directly imported packages.
for _, info := range imp.AllPackages() {
if info.Err != nil {
errpkgs = append(errpkgs, info.Pkg.Path())
} else {
prog.CreatePackage(info)
}
}
// Create indirectly imported packages.
for _, obj := range imp.Config.TypeChecker.Packages {
prog.CreatePackage(&importer.PackageInfo{
Pkg: obj,
Importable: true,
})
}
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 // AllPackages returns a new slice containing all packages in the
// program prog in unspecified order. // program prog in unspecified order.
// //

View File

@ -23,17 +23,11 @@
// primitives in the future to facilitate constant-time dispatch of // primitives in the future to facilitate constant-time dispatch of
// switch statements, for example. // switch statements, for example.
// //
// Builder encapsulates the tasks of type-checking (using go/types) // To construct an SSA-form program, call ssa.Create on an
// abstract syntax trees (as defined by go/ast) for the source files // importer.Program, a set of type-checked packages created from
// comprising a Go program, and the conversion of each function from // parsed Go source files. The resulting ssa.Program contains all the
// Go ASTs to the SSA representation. // packages and their members, but SSA code is not created for
// // function bodies until a subsequent call to (*Package).Build.
// By supplying an instance of the SourceLocator function prototype,
// clients may control how the builder locates, loads and parses Go
// sources files for imported packages. This package provides
// MakeGoBuildLoader, which creates a loader that uses go/build to
// locate packages in the Go source distribution, and go/parser to
// parse them.
// //
// The builder initially builds a naive SSA form in which all local // The builder initially builds a naive SSA form in which all local
// variables are addresses of stack locations with explicit loads and // variables are addresses of stack locations with explicit loads and

View File

@ -6,7 +6,6 @@ package ssa_test
import ( import (
"fmt" "fmt"
"go/parser"
"os" "os"
"code.google.com/p/go.tools/importer" "code.google.com/p/go.tools/importer"
@ -41,27 +40,28 @@ func main() {
fmt.Println(message) fmt.Println(message)
} }
` `
// Construct an importer. var conf importer.Config
imp := importer.New(&importer.Config{})
// Parse the input file. // Parse the input file.
file, err := parser.ParseFile(imp.Fset, "hello.go", hello, 0) file, err := conf.ParseFile("hello.go", hello, 0)
if err != nil { if err != nil {
fmt.Print(err) // parse error fmt.Print(err) // parse error
return return
} }
// Create single-file main package and import its dependencies. // Create single-file main package.
mainInfo := imp.CreatePackage("main", file) conf.CreateFromFiles(file)
// Create SSA-form program representation. // Load the main package and its dependencies.
var mode ssa.BuilderMode iprog, err := conf.Load()
prog := ssa.NewProgram(imp.Fset, mode) if err != nil {
if err := prog.CreatePackages(imp); err != nil {
fmt.Print(err) // type error in some package fmt.Print(err) // type error in some package
return return
} }
mainPkg := prog.Package(mainInfo.Pkg)
// Create SSA-form program representation.
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
mainPkg := prog.Package(iprog.Created[0].Pkg)
// Print out the package. // Print out the package.
mainPkg.DumpTo(os.Stdout) mainPkg.DumpTo(os.Stdout)

View File

@ -168,16 +168,16 @@ func run(t *testing.T, dir, input string, success successPredicate) bool {
inputs = append(inputs, dir+i) inputs = append(inputs, dir+i)
} }
imp := importer.New(&importer.Config{SourceImports: true}) conf := importer.Config{SourceImports: true}
// TODO(adonovan): use LoadInitialPackages, then un-export ParseFiles. // TODO(adonovan): add the following packages' tests, which pass:
// Then add the following packages' tests, which pass:
// "flag", "unicode", "unicode/utf8", "testing", "log", "path". // "flag", "unicode", "unicode/utf8", "testing", "log", "path".
files, err := importer.ParseFiles(imp.Fset, ".", inputs...) if err := conf.CreateFromFilenames(inputs...); err != nil {
if err != nil { t.Errorf("CreateFromFilenames(%s) failed: %s", inputs, err)
t.Errorf("ssa.ParseFiles(%s) failed: %s", inputs, err.Error())
return false return false
} }
conf.Import("runtime")
// Print a helpful hint if we don't make it to the end. // Print a helpful hint if we don't make it to the end.
var hint string var hint string
defer func() { defer func() {
@ -192,20 +192,17 @@ func run(t *testing.T, dir, input string, success successPredicate) 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) 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)
mainInfo := imp.CreatePackage(files[0].Name.Name, files...)
if _, err := imp.ImportPackage("runtime"); err != nil { iprog, err := conf.Load()
t.Errorf("ImportPackage(runtime) failed: %s", err) if err != nil {
} t.Errorf("conf.Load(%s) failed: %s", inputs, err)
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
if err := prog.CreatePackages(imp); err != nil {
t.Errorf("CreatePackages failed: %s", err)
return false return false
} }
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
prog.BuildAll() prog.BuildAll()
mainPkg := prog.Package(mainInfo.Pkg) mainPkg := prog.Package(iprog.Created[0].Pkg)
if mainPkg.Func("main") == nil { if mainPkg.Func("main") == nil {
testmainPkg := prog.CreateTestMainPackage(mainPkg) testmainPkg := prog.CreateTestMainPackage(mainPkg)
if testmainPkg == nil { if testmainPkg == nil {
@ -321,17 +318,16 @@ func TestTestmainPackage(t *testing.T) {
// CreateTestMainPackage should return nil if there were no tests. // CreateTestMainPackage should return nil if there were no tests.
func TestNullTestmainPackage(t *testing.T) { func TestNullTestmainPackage(t *testing.T) {
imp := importer.New(&importer.Config{}) var conf importer.Config
files, err := importer.ParseFiles(imp.Fset, ".", "testdata/b_test.go") if err := conf.CreateFromFilenames("testdata/b_test.go"); err != nil {
if err != nil { t.Fatalf("ParseFile failed: %s", err)
t.Fatalf("ParseFiles failed: %s", err)
} }
mainInfo := imp.CreatePackage("b", files...) iprog, err := conf.Load()
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) if err != nil {
if err := prog.CreatePackages(imp); err != nil {
t.Fatalf("CreatePackages failed: %s", err) t.Fatalf("CreatePackages failed: %s", err)
} }
mainPkg := prog.Package(mainInfo.Pkg) prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
mainPkg := prog.Package(iprog.Created[0].Pkg)
if mainPkg.Func("main") != nil { if mainPkg.Func("main") != nil {
t.Fatalf("unexpected main function") t.Fatalf("unexpected main function")
} }

View File

@ -24,12 +24,13 @@ import (
) )
func TestObjValueLookup(t *testing.T) { func TestObjValueLookup(t *testing.T) {
imp := importer.New(&importer.Config{}) var conf importer.Config
f, err := parser.ParseFile(imp.Fset, "testdata/objlookup.go", nil, parser.ParseComments) f, err := conf.ParseFile("testdata/objlookup.go", nil, parser.ParseComments)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
conf.CreateFromFiles(f)
// Maps each var Ident (represented "name:linenum") to the // Maps each var Ident (represented "name:linenum") to the
// kind of ssa.Value we expect (represented "Constant", "&Alloc"). // kind of ssa.Value we expect (represented "Constant", "&Alloc").
@ -39,7 +40,7 @@ func TestObjValueLookup(t *testing.T) {
re := regexp.MustCompile(`(\b|&)?(\w*)::(\w*)\b`) re := regexp.MustCompile(`(\b|&)?(\w*)::(\w*)\b`)
for _, c := range f.Comments { for _, c := range f.Comments {
text := c.Text() text := c.Text()
pos := imp.Fset.Position(c.Pos()) pos := conf.Fset.Position(c.Pos())
for _, m := range re.FindAllStringSubmatch(text, -1) { for _, m := range re.FindAllStringSubmatch(text, -1) {
key := fmt.Sprintf("%s:%d", m[2], pos.Line) key := fmt.Sprintf("%s:%d", m[2], pos.Line)
value := m[1] + m[3] value := m[1] + m[3]
@ -47,13 +48,14 @@ func TestObjValueLookup(t *testing.T) {
} }
} }
mainInfo := imp.CreatePackage("main", f) iprog, err := conf.Load()
if err != nil {
prog := ssa.NewProgram(imp.Fset, 0 /*|ssa.LogFunctions*/)
if err := prog.CreatePackages(imp); err != nil {
t.Error(err) t.Error(err)
return return
} }
prog := ssa.Create(iprog, 0 /*|ssa.LogFunctions*/)
mainInfo := iprog.Created[0]
mainPkg := prog.Package(mainInfo.Pkg) mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.SetDebugMode(true) mainPkg.SetDebugMode(true)
mainPkg.Build() mainPkg.Build()
@ -90,7 +92,7 @@ func TestObjValueLookup(t *testing.T) {
} }
if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok { if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok {
ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos()) ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos())
pos := imp.Fset.Position(id.Pos()) pos := prog.Fset.Position(id.Pos())
exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)] exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
if exp == "" { if exp == "" {
t.Errorf("%s: no expectation for var ident %s ", pos, id.Name) t.Errorf("%s: no expectation for var ident %s ", pos, id.Name)
@ -186,20 +188,23 @@ func checkVarValue(t *testing.T, prog *ssa.Program, pkg *ssa.Package, ref []ast.
// Ensure that, in debug mode, we can determine the ssa.Value // Ensure that, in debug mode, we can determine the ssa.Value
// corresponding to every ast.Expr. // corresponding to every ast.Expr.
func TestValueForExpr(t *testing.T) { func TestValueForExpr(t *testing.T) {
imp := importer.New(&importer.Config{}) var conf importer.Config
f, err := parser.ParseFile(imp.Fset, "testdata/valueforexpr.go", nil, parser.ParseComments) f, err := conf.ParseFile("testdata/valueforexpr.go", nil, parser.ParseComments)
if err != nil {
t.Error(err)
return
}
conf.CreateFromFiles(f)
iprog, err := conf.Load()
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
mainInfo := imp.CreatePackage("main", f) mainInfo := iprog.Created[0]
prog := ssa.NewProgram(imp.Fset, 0) prog := ssa.Create(iprog, 0)
if err := prog.CreatePackages(imp); err != nil {
t.Error(err)
return
}
mainPkg := prog.Package(mainInfo.Pkg) mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.SetDebugMode(true) mainPkg.SetDebugMode(true)
mainPkg.Build() mainPkg.Build()
@ -232,7 +237,7 @@ func TestValueForExpr(t *testing.T) {
} }
text = text[1:] text = text[1:]
pos := c.End() + 1 pos := c.End() + 1
position := imp.Fset.Position(pos) position := prog.Fset.Position(pos)
var e ast.Expr var e ast.Expr
if target := parenExprByPos[pos]; target == nil { if target := parenExprByPos[pos]; target == nil {
t.Errorf("%s: annotation doesn't precede ParenExpr: %q", position, text) t.Errorf("%s: annotation doesn't precede ParenExpr: %q", position, text)

View File

@ -1170,7 +1170,8 @@ type MapUpdate struct {
// For non-Ident expressions, Object() returns nil. // For non-Ident expressions, Object() returns nil.
// //
// DebugRefs are generated only for functions built with debugging // DebugRefs are generated only for functions built with debugging
// enabled; see Package.SetDebugMode(). // enabled; see Package.SetDebugMode() and the GlobalDebug builder
// mode flag.
// //
// DebugRefs are not emitted for ast.Idents referring to constants or // DebugRefs are not emitted for ast.Idents referring to constants or
// predeclared identifiers, since they are trivial and numerous. // predeclared identifiers, since they are trivial and numerous.
@ -1240,7 +1241,7 @@ type anInstruction struct {
// (b) a *MakeClosure, indicating an immediately applied // (b) a *MakeClosure, indicating an immediately applied
// function literal with free variables. // function literal with free variables.
// (c) a *Builtin, indicating a statically dispatched call // (c) a *Builtin, indicating a statically dispatched call
// to a built-in function. StaticCallee returns nil. // to a built-in function.
// (d) any other value, indicating a dynamically dispatched // (d) any other value, indicating a dynamically dispatched
// function call. // function call.
// StaticCallee returns the identity of the callee in cases // StaticCallee returns the identity of the callee in cases

View File

@ -15,21 +15,22 @@ import (
) )
func TestSwitches(t *testing.T) { func TestSwitches(t *testing.T) {
imp := importer.New(&importer.Config{}) var conf importer.Config
f, err := parser.ParseFile(imp.Fset, "testdata/switches.go", nil, parser.ParseComments) f, err := conf.ParseFile("testdata/switches.go", nil, parser.ParseComments)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
} }
mainInfo := imp.CreatePackage("main", f) conf.CreateFromFiles(f)
iprog, err := conf.Load()
prog := ssa.NewProgram(imp.Fset, 0) if err != nil {
if err := prog.CreatePackages(imp); err != nil {
t.Error(err) t.Error(err)
return return
} }
mainPkg := prog.Package(mainInfo.Pkg)
prog := ssa.Create(iprog, 0)
mainPkg := prog.Package(iprog.Created[0].Pkg)
mainPkg.Build() mainPkg.Build()
for _, mem := range mainPkg.Members { for _, mem := range mainPkg.Members {

View File

@ -23,8 +23,6 @@ import (
"code.google.com/p/go.tools/ssa/ssautil" "code.google.com/p/go.tools/ssa/ssautil"
) )
const debugMode = true // cost: +30% space, +18% time for SSA building
func allPackages() []string { func allPackages() []string {
var pkgs []string var pkgs []string
root := filepath.Join(runtime.GOROOT(), "src/pkg") + string(os.PathSeparator) root := filepath.Join(runtime.GOROOT(), "src/pkg") + string(os.PathSeparator)
@ -54,13 +52,17 @@ func TestStdlib(t *testing.T) {
// Load, parse and type-check the program. // Load, parse and type-check the program.
t0 := time.Now() t0 := time.Now()
imp := importer.New(&importer.Config{}) var conf importer.Config
if _, err := conf.FromArgs(allPackages()); err != nil {
if _, _, err := imp.LoadInitialPackages(allPackages()); err != nil { t.Errorf("FromArgs failed: %s", err)
t.Errorf("LoadInitialPackages failed: %s", err)
return return
} }
iprog, err := conf.Load()
if err != nil {
t.Errorf("Load failed: %s", err)
}
t1 := time.Now() t1 := time.Now()
runtime.GC() runtime.GC()
@ -69,15 +71,11 @@ func TestStdlib(t *testing.T) {
alloc := memstats.Alloc alloc := memstats.Alloc
// Create SSA packages. // Create SSA packages.
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) var mode ssa.BuilderMode
if err := prog.CreatePackages(imp); err != nil { // Comment out these lines during benchmarking. Approx SSA build costs are noted.
t.Errorf("CreatePackages failed: %s", err) mode |= ssa.SanityCheckFunctions // + 2% space, + 4% time
return mode |= ssa.GlobalDebug // +30% space, +18% time
} prog := ssa.Create(iprog, mode)
// Enable debug mode globally.
for _, info := range imp.AllPackages() {
prog.Package(info.Pkg).SetDebugMode(debugMode)
}
t2 := time.Now() t2 := time.Now()
@ -105,7 +103,7 @@ func TestStdlib(t *testing.T) {
// determine line count // determine line count
var lineCount int var lineCount int
imp.Fset.Iterate(func(f *token.File) bool { prog.Fset.Iterate(func(f *token.File) bool {
lineCount += f.LineCount() lineCount += f.LineCount()
return true return true
}) })