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:
parent
d71b7746ee
commit
e8afbfad8c
|
@ -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")
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 == "unsafe" {
|
||||
continue // ignore; has no PackageInfo
|
||||
if path := strings.TrimPrefix(arg, "notest:"); path != arg {
|
||||
conf.Import(path)
|
||||
} else {
|
||||
err = conf.ImportWithTests(path)
|
||||
}
|
||||
|
||||
pkgs = append(pkgs, &initialPkg{
|
||||
path: path,
|
||||
importable: true,
|
||||
})
|
||||
imp.srcpkgs[path] = false // unaugmented source package
|
||||
|
||||
if path != arg {
|
||||
continue // had "notest:" prefix
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Load the external test package.
|
||||
xtestFiles, err := parsePackageFiles(imp.Config.build(), imp.Fset, path, "x")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(xtestFiles) > 0 {
|
||||
pkgs = append(pkgs, &initialPkg{
|
||||
path: path + "_test",
|
||||
importable: false,
|
||||
files: xtestFiles,
|
||||
})
|
||||
}
|
||||
|
||||
// Mark the non-xtest package for augmentation with
|
||||
// in-package *_test.go files when we import it below.
|
||||
imp.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
|
||||
}
|
||||
|
||||
type initialPkg struct {
|
||||
path string // the package's import path
|
||||
importable bool // add package to import map (false for main and xtests)
|
||||
files []*ast.File // set of files (non-importable packages only)
|
||||
}
|
||||
|
||||
// initialPackageFromFiles returns an initialPkg, given a
|
||||
// comma-separated list of *.go source files belonging to the same
|
||||
// directory and possessing the same 'package decl'.
|
||||
//
|
||||
func initialPackageFromFiles(fset *token.FileSet, arg string) (*initialPkg, error) {
|
||||
filenames := strings.Split(arg, ",")
|
||||
for _, filename := range filenames {
|
||||
if !strings.HasSuffix(filename, ".go") {
|
||||
return nil, fmt.Errorf("not a *.go source file: %q", filename)
|
||||
}
|
||||
}
|
||||
|
||||
files, err := ParseFiles(fset, ".", filenames...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Take the package name from the 'package decl' in each file,
|
||||
// all of which must match.
|
||||
pkgname := files[0].Name.Name
|
||||
for i, file := range files[1:] {
|
||||
if pn := file.Name.Name; pn != pkgname {
|
||||
err := fmt.Errorf("can't load package: found packages %s (%s) and %s (%s)",
|
||||
pkgname, filenames[0], pn, filenames[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(adonovan): check dirnames are equal, like 'go build' does.
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
return &initialPkg{
|
||||
path: pkgname,
|
||||
importable: false,
|
||||
files: files,
|
||||
}, nil
|
||||
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" {
|
||||
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.
|
||||
for _, augmented := range conf.ImportPkgs {
|
||||
if augmented {
|
||||
return nil // don't attempt a second
|
||||
}
|
||||
}
|
||||
|
||||
// Load the external test package.
|
||||
xtestFiles, err := parsePackageFiles(conf.build(), conf.fset(), path, "x")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(xtestFiles) > 0 {
|
||||
conf.CreateFromFiles(xtestFiles...)
|
||||
}
|
||||
|
||||
// Mark the non-xtest package for augmentation with
|
||||
// in-package *_test.go files when we import it below.
|
||||
conf.ImportPkgs[path] = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Import is a convenience function that adds path to ImportPkgs, the
|
||||
// set of initial packages that will be imported from source.
|
||||
//
|
||||
func (conf *Config) Import(path string) {
|
||||
if path == "unsafe" {
|
||||
return // ignore; not a real package
|
||||
}
|
||||
if conf.ImportPkgs == nil {
|
||||
conf.ImportPkgs = make(map[string]bool)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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"}
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
for _, spec := range decl.Specs {
|
||||
spec := spec.(*ast.ImportSpec)
|
||||
if path, _ := strconv.Unquote(spec.Path.Value); path != "C" {
|
||||
imports[path] = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break outer // stop at the first non-import
|
||||
}
|
||||
}
|
||||
func packageName(files []*ast.File, fset *token.FileSet) (string, error) {
|
||||
if len(files) == 0 {
|
||||
return "", fmt.Errorf("no files in package")
|
||||
}
|
||||
return imports
|
||||
// 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.
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
|
120
oracle/oracle.go
120
oracle/oracle.go
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
16
ssa/doc.go
16
ssa/doc.go
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue