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:
% oracle -format=json src/pkg/net/http/triv.go callgraph
` + importer.InitialPackagesUsage
` + importer.FromArgsUsage
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 -run -interp=T hello.go # interpret a program, with tracing
% 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
defines a main function and run it in the interpreter.
@ -73,28 +73,27 @@ func main() {
flag.Parse()
args := flag.Args()
impctx := importer.Config{
conf := importer.Config{
Build: &build.Default,
SourceImports: true,
}
// TODO(adonovan): make go/types choose its default Sizes from
// build.Default or a specified *build.Context.
var wordSize int64 = 8
switch impctx.Build.GOARCH {
switch conf.Build.GOARCH {
case "386", "arm":
wordSize = 4
}
impctx.TypeChecker.Sizes = &types.StdSizes{
conf.TypeChecker.Sizes = &types.StdSizes{
MaxAlign: 8,
WordSize: wordSize,
}
var debugMode bool
var mode ssa.BuilderMode
for _, c := range *buildFlag {
switch c {
case 'D':
debugMode = true
mode |= ssa.GlobalDebug
case 'P':
mode |= ssa.LogPackages | ssa.BuildSerially
case 'F':
@ -106,7 +105,7 @@ func main() {
case 'N':
mode |= ssa.NaiveForm
case 'G':
impctx.SourceImports = false
conf.SourceImports = false
case 'L':
mode |= ssa.BuildSerially
default:
@ -141,59 +140,52 @@ func main() {
defer pprof.StopCPUProfile()
}
// Load, parse and type-check the program.
imp := importer.New(&impctx)
infos, args, err := imp.LoadInitialPackages(args)
// Use the initial packages from the command line.
args, err := conf.FromArgs(args)
if err != nil {
log.Fatal(err)
}
// The interpreter needs the runtime package.
if *runFlag {
if _, err := imp.ImportPackage("runtime"); err != nil {
log.Fatalf("ImportPackage(runtime) failed: %s", err)
}
conf.Import("runtime")
}
// Create and build SSA-form program representation.
prog := ssa.NewProgram(imp.Fset, mode)
if err := prog.CreatePackages(imp); err != nil {
// Load, parse and type-check the whole program.
iprog, err := conf.Load()
if err != nil {
log.Fatal(err)
}
if debugMode {
for _, pkg := range prog.AllPackages() {
pkg.SetDebugMode(true)
}
}
// Create and build SSA-form program representation.
prog := ssa.Create(iprog, mode)
prog.BuildAll()
// Run the interpreter.
if *runFlag {
// If some package defines main, run that.
// Otherwise run all package's tests.
// If a package named "main" defines func main, run that.
// Otherwise run all packages' tests.
var main *ssa.Package
var pkgs []*ssa.Package
for _, info := range infos {
pkg := prog.Package(info.Pkg)
if pkg.Func("main") != nil {
pkgs := prog.AllPackages()
for _, pkg := range pkgs {
if pkg.Object.Name() == "main" && pkg.Func("main") != nil {
main = pkg
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...)
}
if main == nil {
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).",
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
// license that can be found in the LICENSE file.
// Package importer defines the Importer, which loads, parses and
// type-checks packages of Go code plus their transitive closure, and
// retains both the ASTs and the derived facts.
// Package importer loads, parses and type-checks packages of Go code
// plus their transitive closure, and retains both the ASTs and the
// derived facts.
//
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
//
// The package defines two primary types: Config, which specifies a
// set of initial packages to load and various other options; and
// Program, which is the result of successfully loading the packages
// specified by a configuration.
//
// The configuration can be set directly, but *Config provides various
// convenience methods to simplify the common cases, each of which can
// be called any number of times. Finally, these are followed by a
// call to Load() to actually load and type-check the program.
//
// var conf 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
//
@ -17,11 +55,14 @@
// 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".
// spec. The Path() of each importable package is unique within a
// Program.
//
// Ad-hoc packages and external test packages are NON-IMPORTABLE. The
// Path() of an ad-hoc package is inferred from the package
// declarations of its files and is therefore not a unique package key.
// For example, Config.CreatePkgs may specify two initial ad-hoc
// packages both called "main".
//
// An AUGMENTED package is an importable package P plus all the
// *_test.go files with same 'package foo' declaration as P.
@ -40,13 +81,25 @@
// Importer will only augment (and create an external test package
// 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
// 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 (
"errors"
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"os"
"strings"
@ -57,30 +110,20 @@ import (
"code.google.com/p/go.tools/go/types"
)
// An Importer's exported methods are not thread-safe.
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.
// Config specifies the configuration for a program to load.
// The zero value for Config is a ready-to-use default configuration.
type Config struct {
// Fset is the file set for the parser to use when loading the
// program. If nil, it will be lazily initialized by any
// method of Config.
Fset *token.FileSet
// TypeChecker contains options relating to the type checker.
//
// The supplied IgnoreFuncBodies is not used; the effective
// value comes from the TypeCheckFuncBodies func below.
//
// TypeChecker.Packages is lazily initialized during Load.
TypeChecker types.Config
// TypeCheckFuncBodies is a predicate over package import
@ -91,209 +134,91 @@ type Config struct {
// checked.
TypeCheckFuncBodies func(string) bool
// SourceImports determines whether to satisfy all imports by
// SourceImports determines whether to satisfy dependencies by
// 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
// instead. Since that typically supplies only the types of
// package-level declarations and values of constants, but no
// code, it will not yield a whole program. It is intended
// 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
// source, regardless of this flag's setting.
// The initial packages (CreatePkgs and ImportPkgs) are always
// loaded from Go source, regardless of this flag's setting.
SourceImports bool
// If Build is non-nil, it is used to locate source packages.
// Otherwise &build.Default is used.
Build *build.Context
// CreatePkgs specifies a list of non-importable initial
// packages to create. Each element is a list of parsed files
// to be type-checked into a new package whose name is taken
// from ast.File.Package.
//
// The resulting packages will appear in the corresponding
// elements of the Program.Created slice.
CreatePkgs [][]*ast.File
// 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
}
// build returns the effective build context.
func (c *Config) build() *build.Context {
if c.Build != nil {
return c.Build
}
return &build.Default
// A Program is a Go program loaded from source or binary
// as specified by a Config.
type Program struct {
Fset *token.FileSet // the file set for this program
// Created[i] contains the initial package whose ASTs 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
}
// New returns a new, empty Importer using configuration options
// specified by config.
//
func New(config *Config) *Importer {
// Initialize by mutating the caller's copy,
// so all copies agree on the identity of the map.
if config.TypeChecker.Packages == nil {
config.TypeChecker.Packages = make(map[string]*types.Package)
func (conf *Config) fset() *token.FileSet {
if conf.Fset == nil {
conf.Fset = token.NewFileSet()
}
// Save the caller's effective Import funcion.
importfn := config.TypeChecker.Import
if importfn == nil {
importfn = gcimporter.Import
}
imp := &Importer{
Fset: token.NewFileSet(),
Config: config,
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
// loaded by importer imp.
// ParseFile is a convenience function that invokes the parser using
// the Config's FileSet, which is initialized if nil.
//
// This returns only packages that were loaded from source or directly
// imported from a source package. It does not include packages
// 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...)
func (conf *Config) ParseFile(filename string, src interface{}, mode parser.Mode) (*ast.File, error) {
return parser.ParseFile(conf.fset(), filename, src, mode)
}
// 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.
//
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 = `
// FromArgsUsage is a partial usage message that applications calling
// FromArgs may wish to include in their -help output.
const FromArgsUsage = `
<args> is a list of arguments denoting a set of initial packages.
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.
`
// LoadInitialPackages interprets args as a set of packages, loads
// those packages and their dependencies, and returns them.
// FromArgs interprets args as a set of initial packages to load from
// source and updates the configuration. It returns the list of
// unconsumed arguments.
//
// It is intended for use in command-line interfaces that require a
// set of initial packages to be specified; see InitialPackagesUsage
// message for details.
// set of initial packages to be specified; see FromArgsUsage 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
var seenAugmented bool
func (conf *Config) FromArgs(args []string) (rest []string, err error) {
for len(args) > 0 {
arg := args[0]
args = args[1:]
@ -371,134 +270,114 @@ func (imp *Importer) LoadInitialPackages(args []string) ([]*PackageInfo, []strin
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)
err = conf.CreateFromFilenames(strings.Split(arg, ",")...)
} 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 := 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" {
continue // ignore; has no PackageInfo
}
pkgs = append(pkgs, &initialPkg{
path: path,
importable: true,
})
imp.srcpkgs[path] = false // unaugmented source package
if path != arg {
continue // had "notest:" prefix
return nil // ignore; not a real package
}
conf.Import(path)
// TODO(adonovan): due to limitations of the current type
// checker design, we can augment at most one package.
if seenAugmented {
continue // don't attempt a second
for _, augmented := range conf.ImportPkgs {
if augmented {
return nil // don't attempt a second
}
}
// 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 {
return nil, nil, err
return err
}
if len(xtestFiles) > 0 {
pkgs = append(pkgs, &initialPkg{
path: path + "_test",
importable: false,
files: xtestFiles,
})
conf.CreateFromFiles(xtestFiles...)
}
// Mark the non-xtest package for augmentation with
// in-package *_test.go files when we import it below.
imp.srcpkgs[path] = true
seenAugmented = 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 {
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
conf.ImportPkgs[path] = true
return 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'.
// Import is a convenience function that adds path to ImportPkgs, the
// set of initial packages that will be imported from source.
//
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)
func (conf *Config) Import(path string) {
if path == "unsafe" {
return // ignore; not a real package
}
if conf.ImportPkgs == nil {
conf.ImportPkgs = make(map[string]bool)
}
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
conf.ImportPkgs[path] = false // unaugmented source package
}
// PathEnclosingInterval returns the PackageInfo and ast.Node that
// contain source interval [start, end), and all the node's ancestors
// up to the AST root. It searches all ast.Files of all packages in the
// Importer imp. exact is defined as for astutil.PathEnclosingInterval.
// up to the AST root. It searches all ast.Files of all packages in prog.
// exact is defined as for astutil.PathEnclosingInterval.
//
// 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.allPackages {
func (prog *Program) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) {
for _, info := range prog.AllPackages {
for _, f := range info.Files {
if !tokenFileContainsPos(imp.Fset.File(f.Package), start) {
if !tokenFileContainsPos(prog.Fset.File(f.Pos()), start) {
continue
}
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
}
// 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()
// InitialPackages returns a new slice containing the set of initial
// packages (Created + Imported) in unspecified order.
//
func (prog *Program) InitialPackages() []*PackageInfo {
infos := make([]*PackageInfo, 0, len(prog.Created)+len(prog.Imported))
infos = append(infos, prog.Created...)
for _, info := range prog.Imported {
infos = append(infos, info)
}
return infos
}
// ---------- Implementation ----------
// importer holds the working state of the algorithm.
type importer struct {
conf *Config // the client configuration
prog *Program // resulting program
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 (
"fmt"
"sort"
"testing"
"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.
args := []string{"nosuchpkg", "errors"}
if _, _, err := importer.New(&importer.Config{}).LoadInitialPackages(args); err == nil {
t.Errorf("LoadInitialPackages(%q) succeeded, want failure", args)
if _, _, err := loadFromArgs(args); err == nil {
t.Errorf("loadFromArgs(%q) succeeded, want failure", args)
} else {
// cannot find package: ok.
}
// Failed load: bad second import path proceeds to doImport0, which fails.
args = []string{"errors", "nosuchpkg"}
if _, _, err := importer.New(&importer.Config{}).LoadInitialPackages(args); err == nil {
t.Errorf("LoadInitialPackages(%q) succeeded, want failure", args)
if _, _, err := loadFromArgs(args); err == nil {
t.Errorf("loadFromArgs(%q) succeeded, want failure", args)
} else {
// cannot find package: ok
}
// Successful load.
args = []string{"fmt", "errors", "testdata/a.go,testdata/b.go", "--", "surplus"}
imp := importer.New(&importer.Config{})
infos, rest, err := imp.LoadInitialPackages(args)
prog, rest, err := loadFromArgs(args)
if err != nil {
t.Errorf("LoadInitialPackages(%q) failed: %s", args, err)
t.Errorf("loadFromArgs(%q) failed: %s", args, err)
return
}
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
for _, info := range infos {
for _, info := range prog.Created {
pkgnames = append(pkgnames, info.Pkg.Path())
}
// Only the first import path (currently) contributes tests.
if got, want := fmt.Sprint(pkgnames), "[fmt fmt_test errors P]"; got != want {
t.Errorf("InitialPackages: got %s, want %s", got, want)
if got, want := fmt.Sprint(pkgnames), "[fmt_test P]"; 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.
// There are >30 and the set may grow over time, so only check a few.
all := map[string]struct{}{}
for _, info := range imp.AllPackages() {
for _, info := range prog.AllPackages {
all[info.Pkg.Path()] = struct{}{}
}
want := []string{"strings", "time", "runtime", "testing", "unicode"}

View File

@ -20,8 +20,8 @@ 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
err error // non-nil if the package had static errors
types.Info // type-checker deductions.
}

View File

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

View File

@ -8,12 +8,12 @@ package importer
// and used by it.
import (
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
"path/filepath"
"strconv"
"sync"
)
@ -55,13 +55,13 @@ func parsePackageFiles(ctxt *build.Context, fset *token.FileSet, path string, wh
}
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.
//
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
n := len(files)
parsed := make([]*ast.File, n, n)
@ -104,26 +104,32 @@ 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
func packageName(files []*ast.File, fset *token.FileSet) (string, error) {
if len(files) == 0 {
return "", fmt.Errorf("no files in package")
}
for _, spec := range decl.Specs {
spec := spec.(*ast.ImportSpec)
if path, _ := strconv.Unquote(spec.Path.Value); path != "C" {
imports[path] = true
// Take the package name from the 'package decl' in each file,
// all of which must match.
pkgname := files[0].Name.Name
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 {
break outer // stop at the first non-import
}
}
}
return imports
return pkgname, nil
}
// 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()
}
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.
//
// 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).
// ptalog is the (optional) pointer-analysis log file.
// 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
// analysis scope should use this pattern instead:
//
// imp := importer.New(&importer.Config{Build: buildContext, SourceImports: true})
// o, err := oracle.New(imp, args, nil)
// conf := importer.Config{Build: buildContext, SourceImports: true}
// ... populate config, e.g. conf.FromArgs(args) ...
// iprog, err := conf.Load()
// if err != nil { ... }
// o, err := oracle.New(iprog, nil, false)
// if err != nil { ... }
// for ... {
// 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)
}
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,
// reduce the analysis scope to that package.
if minfo.needs&(needSSA|needRetainTypeInfo) == 0 {
reduceScope(pos, &impcfg, &args)
reduceScope(pos, &conf)
}
// TODO(adonovan): report type errors to the user via Serial
// types, not stderr?
// impcfg.TypeChecker.Error = func(err error) {
// conf.TypeChecker.Error = func(err error) {
// E := err.(types.Error)
// 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 {
return nil, err
}
var qpos *QueryPos
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 {
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.
// Release the other ASTs and type info to the GC.
imp = nil
iprog = nil
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.
//
func reduceScope(pos string, impcfg *importer.Config, args *[]string) {
// 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.
func reduceScope(pos string, conf *importer.Config) {
fqpos, err := fastQueryPos(pos)
if err != nil {
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
// in non-importable packages such as tests and ad-hoc packages
// 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 {
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.
// (e.g. oracle tests contain different 'package' decls in same dir.)
// Keep consistent with logic in importer/util.go!
ctxt2 := *impcfg.Build
ctxt2.CgoEnabled = false
bp, err := ctxt2.Import(importPath, "", 0)
cfg2 := *conf.Build
cfg2.CgoEnabled = false
bp, err := cfg2.Import(importPath, "", 0)
if err != nil {
return // no files for package
}
@ -302,59 +315,50 @@ func reduceScope(pos string, impcfg *importer.Config, args *[]string) {
// return // not found
// found:
impcfg.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
*args = []string{importPath}
conf.TypeCheckFuncBodies = func(p string) bool { return p == 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.
//
// imp will be used to load source code for imported packages.
// It must not yet have loaded any packages.
//
// args specify the main package in importer.LoadInitialPackages syntax.
//
// iprog specifies the program to analyze.
// ptalog is the (optional) pointer-analysis log file.
// reflection determines whether to model reflection soundly (currently slow).
//
func New(imp *importer.Importer, args []string, ptalog io.Writer, reflection bool) (*Oracle, error) {
return newOracle(imp, args, ptalog, needAll, reflection)
func New(iprog *importer.Program, ptalog io.Writer, reflection bool) (*Oracle, error) {
return newOracle(iprog, ptalog, needAll, reflection)
}
func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) {
o := &Oracle{fset: imp.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)
}
func newOracle(iprog *importer.Program, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) {
o := &Oracle{fset: iprog.Fset}
// Retain type info for all ASTs in the program.
if needs&needRetainTypeInfo != 0 {
m := make(map[*types.Package]*importer.PackageInfo)
for _, p := range imp.AllPackages() {
m[p.Pkg] = p
}
o.typeInfo = m
o.typeInfo = iprog.AllPackages
}
// Create SSA package for the initial packages and their dependencies.
if needs&needSSA != 0 {
prog := ssa.NewProgram(o.fset, 0)
// Create SSA packages.
if err := prog.CreatePackages(imp); err != nil {
return nil, err
var mode ssa.BuilderMode
if needs&needSSADebug != 0 {
mode |= ssa.GlobalDebug
}
prog := ssa.Create(iprog, mode)
// For each initial package (specified on the command line),
// if it has a main function, analyze that,
// otherwise analyze its tests, if any.
var testPkgs, mains []*ssa.Package
for _, info := range initialPkgInfos {
for _, info := range iprog.InitialPackages() {
initialPkg := prog.Package(info.Pkg)
// 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.Mains = mains
if needs&needSSADebug != 0 {
for _, pkg := range prog.AllPackages() {
pkg.SetDebugMode(true)
}
}
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,
// 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)
if err != nil {
return nil, err
}
start, end, err := findQueryPos(imp.Fset, filename, startOffset, endOffset)
start, end, err := findQueryPos(iprog.Fset, filename, startOffset, endOffset)
if err != nil {
return nil, err
}
info, path, exact := imp.PathEnclosingInterval(start, end)
info, path, exact := iprog.PathEnclosingInterval(start, end)
if path == nil {
return nil, fmt.Errorf("no syntax here")
}
if needExact && !exact {
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.

View File

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

View File

@ -6,7 +6,6 @@ package pointer_test
import (
"fmt"
"go/parser"
"sort"
"code.google.com/p/go.tools/call"
@ -40,26 +39,27 @@ func main() {
}
`
// Construct an importer.
imp := importer.New(&importer.Config{SourceImports: true})
conf := importer.Config{SourceImports: true}
// 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 {
fmt.Print(err) // parse error
return
}
// Create single-file main package and import its dependencies.
mainInfo := imp.CreatePackage("main", file)
conf.CreateFromFiles(file)
// Create SSA-form program representation.
var mode ssa.BuilderMode
prog := ssa.NewProgram(imp.Fset, mode)
if err := prog.CreatePackages(imp); err != nil {
iprog, err := conf.Load()
if err != nil {
fmt.Print(err) // type error in some package
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.
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 {
impctx := &importer.Config{SourceImports: true}
imp := importer.New(impctx)
conf := importer.Config{SourceImports: true}
// Parsing.
f, err := parser.ParseFile(imp.Fset, filename, input, 0)
f, err := conf.ParseFile(filename, input, 0)
if err != nil {
// TODO(adonovan): err is a scanner error list;
// display all errors not just first?
fmt.Println(err)
return false
}
// Create single-file main package and import its dependencies.
info := imp.CreatePackage("main", f)
// SSA creation + building.
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
if err := prog.CreatePackages(imp); err != nil {
conf.CreateFromFiles(f)
iprog, err := conf.Load()
if err != nil {
fmt.Println(err)
return false
}
// SSA creation + building.
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
prog.BuildAll()
mainpkg := prog.Package(info.Pkg)
mainpkg := prog.Package(iprog.Created[0].Pkg)
ptrmain := mainpkg // main package for the pointer analysis
if mainpkg.Func("main") == nil {
// No main function; assume it's a test.
@ -228,7 +226,7 @@ func doOneInput(input, filename string) bool {
continue
}
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 {
ok = false
// Don't print err since its location is bad.

View File

@ -11,7 +11,7 @@ import "testing"
func log(f func(*testing.T)) {
// 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) {
@ -36,7 +36,7 @@ func ExampleBar() {
}
// Excludes TestingQuux.
// @calls testing.tRunner -> main.Test
// @calls testing.tRunner -> main.TestFoo
// @calls testing.runExample -> main.ExampleBar
// @calls (*testing.B).runN -> main.BenchmarkFoo
// @calls testing.tRunner -> a.Test
// @calls testing.tRunner -> a.TestFoo
// @calls testing.runExample -> a.ExampleBar
// @calls (*testing.B).runN -> a.BenchmarkFoo

View File

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

View File

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

View File

@ -23,17 +23,11 @@
// primitives in the future to facilitate constant-time dispatch of
// switch statements, for example.
//
// Builder encapsulates the tasks of type-checking (using go/types)
// abstract syntax trees (as defined by go/ast) for the source files
// comprising a Go program, and the conversion of each function from
// Go ASTs to the SSA representation.
//
// 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.
// To construct an SSA-form program, call ssa.Create on an
// importer.Program, a set of type-checked packages created from
// parsed Go source files. The resulting ssa.Program contains all the
// packages and their members, but SSA code is not created for
// function bodies until a subsequent call to (*Package).Build.
//
// The builder initially builds a naive SSA form in which all local
// variables are addresses of stack locations with explicit loads and

View File

@ -6,7 +6,6 @@ package ssa_test
import (
"fmt"
"go/parser"
"os"
"code.google.com/p/go.tools/importer"
@ -41,27 +40,28 @@ func main() {
fmt.Println(message)
}
`
// Construct an importer.
imp := importer.New(&importer.Config{})
var conf importer.Config
// 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 {
fmt.Print(err) // parse error
return
}
// Create single-file main package and import its dependencies.
mainInfo := imp.CreatePackage("main", file)
// Create single-file main package.
conf.CreateFromFiles(file)
// Create SSA-form program representation.
var mode ssa.BuilderMode
prog := ssa.NewProgram(imp.Fset, mode)
if err := prog.CreatePackages(imp); err != nil {
// Load the main package and its dependencies.
iprog, err := conf.Load()
if err != nil {
fmt.Print(err) // type error in some package
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.
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)
}
imp := importer.New(&importer.Config{SourceImports: true})
// TODO(adonovan): use LoadInitialPackages, then un-export ParseFiles.
// Then add the following packages' tests, which pass:
conf := importer.Config{SourceImports: true}
// TODO(adonovan): add the following packages' tests, which pass:
// "flag", "unicode", "unicode/utf8", "testing", "log", "path".
files, err := importer.ParseFiles(imp.Fset, ".", inputs...)
if err != nil {
t.Errorf("ssa.ParseFiles(%s) failed: %s", inputs, err.Error())
if err := conf.CreateFromFilenames(inputs...); err != nil {
t.Errorf("CreateFromFilenames(%s) failed: %s", inputs, err)
return false
}
conf.Import("runtime")
// Print a helpful hint if we don't make it to the end.
var hint string
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)
mainInfo := imp.CreatePackage(files[0].Name.Name, files...)
if _, err := imp.ImportPackage("runtime"); err != nil {
t.Errorf("ImportPackage(runtime) failed: %s", err)
}
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
if err := prog.CreatePackages(imp); err != nil {
t.Errorf("CreatePackages failed: %s", err)
iprog, err := conf.Load()
if err != nil {
t.Errorf("conf.Load(%s) failed: %s", inputs, err)
return false
}
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
prog.BuildAll()
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg := prog.Package(iprog.Created[0].Pkg)
if mainPkg.Func("main") == nil {
testmainPkg := prog.CreateTestMainPackage(mainPkg)
if testmainPkg == nil {
@ -321,17 +318,16 @@ func TestTestmainPackage(t *testing.T) {
// CreateTestMainPackage should return nil if there were no tests.
func TestNullTestmainPackage(t *testing.T) {
imp := importer.New(&importer.Config{})
files, err := importer.ParseFiles(imp.Fset, ".", "testdata/b_test.go")
if err != nil {
t.Fatalf("ParseFiles failed: %s", err)
var conf importer.Config
if err := conf.CreateFromFilenames("testdata/b_test.go"); err != nil {
t.Fatalf("ParseFile failed: %s", err)
}
mainInfo := imp.CreatePackage("b", files...)
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
if err := prog.CreatePackages(imp); err != nil {
iprog, err := conf.Load()
if err != nil {
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 {
t.Fatalf("unexpected main function")
}

View File

@ -24,12 +24,13 @@ import (
)
func TestObjValueLookup(t *testing.T) {
imp := importer.New(&importer.Config{})
f, err := parser.ParseFile(imp.Fset, "testdata/objlookup.go", nil, parser.ParseComments)
var conf importer.Config
f, err := conf.ParseFile("testdata/objlookup.go", nil, parser.ParseComments)
if err != nil {
t.Error(err)
return
}
conf.CreateFromFiles(f)
// Maps each var Ident (represented "name:linenum") to the
// 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`)
for _, c := range f.Comments {
text := c.Text()
pos := imp.Fset.Position(c.Pos())
pos := conf.Fset.Position(c.Pos())
for _, m := range re.FindAllStringSubmatch(text, -1) {
key := fmt.Sprintf("%s:%d", m[2], pos.Line)
value := m[1] + m[3]
@ -47,13 +48,14 @@ func TestObjValueLookup(t *testing.T) {
}
}
mainInfo := imp.CreatePackage("main", f)
prog := ssa.NewProgram(imp.Fset, 0 /*|ssa.LogFunctions*/)
if err := prog.CreatePackages(imp); err != nil {
iprog, err := conf.Load()
if err != nil {
t.Error(err)
return
}
prog := ssa.Create(iprog, 0 /*|ssa.LogFunctions*/)
mainInfo := iprog.Created[0]
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.SetDebugMode(true)
mainPkg.Build()
@ -90,7 +92,7 @@ func TestObjValueLookup(t *testing.T) {
}
if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok {
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)]
if exp == "" {
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
// corresponding to every ast.Expr.
func TestValueForExpr(t *testing.T) {
imp := importer.New(&importer.Config{})
f, err := parser.ParseFile(imp.Fset, "testdata/valueforexpr.go", nil, parser.ParseComments)
var conf importer.Config
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 {
t.Error(err)
return
}
mainInfo := imp.CreatePackage("main", f)
mainInfo := iprog.Created[0]
prog := ssa.NewProgram(imp.Fset, 0)
if err := prog.CreatePackages(imp); err != nil {
t.Error(err)
return
}
prog := ssa.Create(iprog, 0)
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.SetDebugMode(true)
mainPkg.Build()
@ -232,7 +237,7 @@ func TestValueForExpr(t *testing.T) {
}
text = text[1:]
pos := c.End() + 1
position := imp.Fset.Position(pos)
position := prog.Fset.Position(pos)
var e ast.Expr
if target := parenExprByPos[pos]; target == nil {
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.
//
// 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
// predeclared identifiers, since they are trivial and numerous.
@ -1240,7 +1241,7 @@ type anInstruction struct {
// (b) a *MakeClosure, indicating an immediately applied
// function literal with free variables.
// (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
// function call.
// StaticCallee returns the identity of the callee in cases

View File

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

View File

@ -23,8 +23,6 @@ import (
"code.google.com/p/go.tools/ssa/ssautil"
)
const debugMode = true // cost: +30% space, +18% time for SSA building
func allPackages() []string {
var pkgs []string
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.
t0 := time.Now()
imp := importer.New(&importer.Config{})
if _, _, err := imp.LoadInitialPackages(allPackages()); err != nil {
t.Errorf("LoadInitialPackages failed: %s", err)
var conf importer.Config
if _, err := conf.FromArgs(allPackages()); err != nil {
t.Errorf("FromArgs failed: %s", err)
return
}
iprog, err := conf.Load()
if err != nil {
t.Errorf("Load failed: %s", err)
}
t1 := time.Now()
runtime.GC()
@ -69,15 +71,11 @@ func TestStdlib(t *testing.T) {
alloc := memstats.Alloc
// Create SSA packages.
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
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)
}
var mode ssa.BuilderMode
// Comment out these lines during benchmarking. Approx SSA build costs are noted.
mode |= ssa.SanityCheckFunctions // + 2% space, + 4% time
mode |= ssa.GlobalDebug // +30% space, +18% time
prog := ssa.Create(iprog, mode)
t2 := time.Now()
@ -105,7 +103,7 @@ func TestStdlib(t *testing.T) {
// determine line count
var lineCount int
imp.Fset.Iterate(func(f *token.File) bool {
prog.Fset.Iterate(func(f *token.File) bool {
lineCount += f.LineCount()
return true
})