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:
|
Print the callgraph of the trivial web-server in JSON format:
|
||||||
% oracle -format=json src/pkg/net/http/triv.go callgraph
|
% oracle -format=json src/pkg/net/http/triv.go callgraph
|
||||||
` + importer.InitialPackagesUsage
|
` + importer.FromArgsUsage
|
||||||
|
|
||||||
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
|
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ Examples:
|
||||||
% ssadump -build=FPG hello.go # quickly dump SSA form of a single package
|
% ssadump -build=FPG hello.go # quickly dump SSA form of a single package
|
||||||
% ssadump -run -interp=T hello.go # interpret a program, with tracing
|
% ssadump -run -interp=T hello.go # interpret a program, with tracing
|
||||||
% ssadump -run unicode -- -test.v # interpret the unicode package's tests, verbosely
|
% ssadump -run unicode -- -test.v # interpret the unicode package's tests, verbosely
|
||||||
` + importer.InitialPackagesUsage +
|
` + importer.FromArgsUsage +
|
||||||
`
|
`
|
||||||
When -run is specified, ssadump will find the first package that
|
When -run is specified, ssadump will find the first package that
|
||||||
defines a main function and run it in the interpreter.
|
defines a main function and run it in the interpreter.
|
||||||
|
@ -73,28 +73,27 @@ func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
|
|
||||||
impctx := importer.Config{
|
conf := importer.Config{
|
||||||
Build: &build.Default,
|
Build: &build.Default,
|
||||||
SourceImports: true,
|
SourceImports: true,
|
||||||
}
|
}
|
||||||
// TODO(adonovan): make go/types choose its default Sizes from
|
// TODO(adonovan): make go/types choose its default Sizes from
|
||||||
// build.Default or a specified *build.Context.
|
// build.Default or a specified *build.Context.
|
||||||
var wordSize int64 = 8
|
var wordSize int64 = 8
|
||||||
switch impctx.Build.GOARCH {
|
switch conf.Build.GOARCH {
|
||||||
case "386", "arm":
|
case "386", "arm":
|
||||||
wordSize = 4
|
wordSize = 4
|
||||||
}
|
}
|
||||||
impctx.TypeChecker.Sizes = &types.StdSizes{
|
conf.TypeChecker.Sizes = &types.StdSizes{
|
||||||
MaxAlign: 8,
|
MaxAlign: 8,
|
||||||
WordSize: wordSize,
|
WordSize: wordSize,
|
||||||
}
|
}
|
||||||
|
|
||||||
var debugMode bool
|
|
||||||
var mode ssa.BuilderMode
|
var mode ssa.BuilderMode
|
||||||
for _, c := range *buildFlag {
|
for _, c := range *buildFlag {
|
||||||
switch c {
|
switch c {
|
||||||
case 'D':
|
case 'D':
|
||||||
debugMode = true
|
mode |= ssa.GlobalDebug
|
||||||
case 'P':
|
case 'P':
|
||||||
mode |= ssa.LogPackages | ssa.BuildSerially
|
mode |= ssa.LogPackages | ssa.BuildSerially
|
||||||
case 'F':
|
case 'F':
|
||||||
|
@ -106,7 +105,7 @@ func main() {
|
||||||
case 'N':
|
case 'N':
|
||||||
mode |= ssa.NaiveForm
|
mode |= ssa.NaiveForm
|
||||||
case 'G':
|
case 'G':
|
||||||
impctx.SourceImports = false
|
conf.SourceImports = false
|
||||||
case 'L':
|
case 'L':
|
||||||
mode |= ssa.BuildSerially
|
mode |= ssa.BuildSerially
|
||||||
default:
|
default:
|
||||||
|
@ -141,59 +140,52 @@ func main() {
|
||||||
defer pprof.StopCPUProfile()
|
defer pprof.StopCPUProfile()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load, parse and type-check the program.
|
// Use the initial packages from the command line.
|
||||||
imp := importer.New(&impctx)
|
args, err := conf.FromArgs(args)
|
||||||
infos, args, err := imp.LoadInitialPackages(args)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The interpreter needs the runtime package.
|
// The interpreter needs the runtime package.
|
||||||
if *runFlag {
|
if *runFlag {
|
||||||
if _, err := imp.ImportPackage("runtime"); err != nil {
|
conf.Import("runtime")
|
||||||
log.Fatalf("ImportPackage(runtime) failed: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and build SSA-form program representation.
|
// Load, parse and type-check the whole program.
|
||||||
prog := ssa.NewProgram(imp.Fset, mode)
|
iprog, err := conf.Load()
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if debugMode {
|
// Create and build SSA-form program representation.
|
||||||
for _, pkg := range prog.AllPackages() {
|
prog := ssa.Create(iprog, mode)
|
||||||
pkg.SetDebugMode(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prog.BuildAll()
|
prog.BuildAll()
|
||||||
|
|
||||||
// Run the interpreter.
|
// Run the interpreter.
|
||||||
if *runFlag {
|
if *runFlag {
|
||||||
// If some package defines main, run that.
|
// If a package named "main" defines func main, run that.
|
||||||
// Otherwise run all package's tests.
|
// Otherwise run all packages' tests.
|
||||||
var main *ssa.Package
|
var main *ssa.Package
|
||||||
var pkgs []*ssa.Package
|
pkgs := prog.AllPackages()
|
||||||
for _, info := range infos {
|
for _, pkg := range pkgs {
|
||||||
pkg := prog.Package(info.Pkg)
|
if pkg.Object.Name() == "main" && pkg.Func("main") != nil {
|
||||||
if pkg.Func("main") != nil {
|
|
||||||
main = pkg
|
main = pkg
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
pkgs = append(pkgs, pkg)
|
|
||||||
}
|
}
|
||||||
if main == nil && pkgs != nil {
|
if main == nil && len(pkgs) > 0 {
|
||||||
|
// TODO(adonovan): only run tests if -test flag specified.
|
||||||
main = prog.CreateTestMainPackage(pkgs...)
|
main = prog.CreateTestMainPackage(pkgs...)
|
||||||
}
|
}
|
||||||
if main == nil {
|
if main == nil {
|
||||||
log.Fatal("No main package and no tests")
|
log.Fatal("No main package and no tests")
|
||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOARCH != impctx.Build.GOARCH {
|
if runtime.GOARCH != conf.Build.GOARCH {
|
||||||
log.Fatalf("Cross-interpretation is not yet supported (target has GOARCH %s, interpreter has %s).",
|
log.Fatalf("Cross-interpretation is not yet supported (target has GOARCH %s, interpreter has %s).",
|
||||||
impctx.Build.GOARCH, runtime.GOARCH)
|
conf.Build.GOARCH, runtime.GOARCH)
|
||||||
}
|
}
|
||||||
|
|
||||||
interp.Interpret(main, interpMode, impctx.TypeChecker.Sizes, main.Object.Path(), args)
|
interp.Interpret(main, interpMode, conf.TypeChecker.Sizes, main.Object.Path(), args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,47 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// Package importer defines the Importer, which loads, parses and
|
// Package importer loads, parses and type-checks packages of Go code
|
||||||
// type-checks packages of Go code plus their transitive closure, and
|
// plus their transitive closure, and retains both the ASTs and the
|
||||||
// retains both the ASTs and the derived facts.
|
// derived facts.
|
||||||
|
//
|
||||||
|
// THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE.
|
||||||
|
//
|
||||||
|
// The package defines two primary types: Config, which specifies a
|
||||||
|
// set of initial packages to load and various other options; and
|
||||||
|
// Program, which is the result of successfully loading the packages
|
||||||
|
// specified by a configuration.
|
||||||
|
//
|
||||||
|
// The configuration can be set directly, but *Config provides various
|
||||||
|
// convenience methods to simplify the common cases, each of which can
|
||||||
|
// be called any number of times. Finally, these are followed by a
|
||||||
|
// call to Load() to actually load and type-check the program.
|
||||||
|
//
|
||||||
|
// var conf importer.Config
|
||||||
|
//
|
||||||
|
// // Use the command-line arguments to specify
|
||||||
|
// // a set of initial packages to load from source.
|
||||||
|
// // See FromArgsUsage for help.
|
||||||
|
// rest, err := conf.FromArgs(os.Args[1:])
|
||||||
|
//
|
||||||
|
// // Parse the specified files and create an ad-hoc package.
|
||||||
|
// // All files must have the same 'package' declaration.
|
||||||
|
// err := conf.CreateFromFilenames("foo.go", "bar.go")
|
||||||
|
//
|
||||||
|
// // Create an ad-hoc package from the specified already-parsed files.
|
||||||
|
// // All ASTs must have the same 'package' declaration.
|
||||||
|
// err := conf.CreateFromFiles(parsedFiles)
|
||||||
|
//
|
||||||
|
// // Add "runtime" to the set of packages to be loaded.
|
||||||
|
// err := conf.Import("runtime")
|
||||||
|
//
|
||||||
|
// // Adds "fmt" and "fmt_test" to the set of packages
|
||||||
|
// // to be loaded. "fmt" will include *_test.go files.
|
||||||
|
// err := conf.ImportWithTests("fmt")
|
||||||
|
//
|
||||||
|
// // Finally, load all the packages specified by the configuration.
|
||||||
|
// prog, err := conf.Load()
|
||||||
|
//
|
||||||
//
|
//
|
||||||
// CONCEPTS AND TERMINOLOGY
|
// CONCEPTS AND TERMINOLOGY
|
||||||
//
|
//
|
||||||
|
@ -17,11 +55,14 @@
|
||||||
// same directory. (go/build.Package calls these files XTestFiles.)
|
// same directory. (go/build.Package calls these files XTestFiles.)
|
||||||
//
|
//
|
||||||
// An IMPORTABLE package is one that can be referred to by some import
|
// An IMPORTABLE package is one that can be referred to by some import
|
||||||
// spec. Ad-hoc packages and external test packages are non-importable.
|
// spec. The Path() of each importable package is unique within a
|
||||||
// The importer and its clients must be careful not to assume that
|
// Program.
|
||||||
// the import path of a package may be used for a name-based lookup.
|
//
|
||||||
// For example, a pointer analysis scope may consist of two initial
|
// Ad-hoc packages and external test packages are NON-IMPORTABLE. The
|
||||||
// (ad-hoc) packages both called "main".
|
// Path() of an ad-hoc package is inferred from the package
|
||||||
|
// declarations of its files and is therefore not a unique package key.
|
||||||
|
// For example, Config.CreatePkgs may specify two initial ad-hoc
|
||||||
|
// packages both called "main".
|
||||||
//
|
//
|
||||||
// An AUGMENTED package is an importable package P plus all the
|
// An AUGMENTED package is an importable package P plus all the
|
||||||
// *_test.go files with same 'package foo' declaration as P.
|
// *_test.go files with same 'package foo' declaration as P.
|
||||||
|
@ -40,13 +81,25 @@
|
||||||
// Importer will only augment (and create an external test package
|
// Importer will only augment (and create an external test package
|
||||||
// for) the first import path specified on the command-line.
|
// for) the first import path specified on the command-line.
|
||||||
//
|
//
|
||||||
|
// The INITIAL packages are those specified in the configuration. A
|
||||||
|
// DEPENDENCY is a package loaded to satisfy an import in an initial
|
||||||
|
// package or another dependency.
|
||||||
|
//
|
||||||
package importer
|
package importer
|
||||||
|
|
||||||
|
// TODO(adonovan):
|
||||||
|
// - Rename this package go.tools/go/loader.
|
||||||
|
// - (*Config).ParseFile is very handy, but feels like feature creep.
|
||||||
|
// (*Config).CreateFromFiles has a nasty precondition.
|
||||||
|
// - Ideally some of this logic would move under the umbrella of
|
||||||
|
// go/types; see bug 7114.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
"go/build"
|
||||||
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -57,30 +110,20 @@ import (
|
||||||
"code.google.com/p/go.tools/go/types"
|
"code.google.com/p/go.tools/go/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// An Importer's exported methods are not thread-safe.
|
// Config specifies the configuration for a program to load.
|
||||||
type Importer struct {
|
|
||||||
Fset *token.FileSet // position info for all files seen
|
|
||||||
Config *Config // the client configuration, unmodified
|
|
||||||
importfn types.Importer // client's type import function
|
|
||||||
srcpkgs map[string]bool // for each package to load from source, whether to augment
|
|
||||||
allPackages []*PackageInfo // all packages, including non-importable ones
|
|
||||||
imported map[string]*importInfo // all imported packages (incl. failures) by import path
|
|
||||||
}
|
|
||||||
|
|
||||||
// importInfo holds internal information about each import path.
|
|
||||||
type importInfo struct {
|
|
||||||
path string // import path
|
|
||||||
info *PackageInfo // results of typechecking (including type errors)
|
|
||||||
err error // reason for failure to construct a package
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config specifies the configuration for the importer.
|
|
||||||
// The zero value for Config is a ready-to-use default configuration.
|
// The zero value for Config is a ready-to-use default configuration.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
// Fset is the file set for the parser to use when loading the
|
||||||
|
// program. If nil, it will be lazily initialized by any
|
||||||
|
// method of Config.
|
||||||
|
Fset *token.FileSet
|
||||||
|
|
||||||
// TypeChecker contains options relating to the type checker.
|
// TypeChecker contains options relating to the type checker.
|
||||||
//
|
//
|
||||||
// The supplied IgnoreFuncBodies is not used; the effective
|
// The supplied IgnoreFuncBodies is not used; the effective
|
||||||
// value comes from the TypeCheckFuncBodies func below.
|
// value comes from the TypeCheckFuncBodies func below.
|
||||||
|
//
|
||||||
|
// TypeChecker.Packages is lazily initialized during Load.
|
||||||
TypeChecker types.Config
|
TypeChecker types.Config
|
||||||
|
|
||||||
// TypeCheckFuncBodies is a predicate over package import
|
// TypeCheckFuncBodies is a predicate over package import
|
||||||
|
@ -91,209 +134,91 @@ type Config struct {
|
||||||
// checked.
|
// checked.
|
||||||
TypeCheckFuncBodies func(string) bool
|
TypeCheckFuncBodies func(string) bool
|
||||||
|
|
||||||
// SourceImports determines whether to satisfy all imports by
|
// SourceImports determines whether to satisfy dependencies by
|
||||||
// loading Go source code.
|
// loading Go source code.
|
||||||
//
|
//
|
||||||
|
// If true, the entire program---the initial packages and
|
||||||
|
// their transitive closure of dependencies---will be loaded,
|
||||||
|
// parsed and type-checked. This is required for
|
||||||
|
// whole-program analyses such as pointer analysis.
|
||||||
|
//
|
||||||
// If false, the TypeChecker.Import mechanism will be used
|
// If false, the TypeChecker.Import mechanism will be used
|
||||||
// instead. Since that typically supplies only the types of
|
// instead. Since that typically supplies only the types of
|
||||||
// package-level declarations and values of constants, but no
|
// package-level declarations and values of constants, but no
|
||||||
// code, it will not yield a whole program. It is intended
|
// code, it will not yield a whole program. It is intended
|
||||||
// for analyses that perform intraprocedural analysis of a
|
// for analyses that perform intraprocedural analysis of a
|
||||||
// single package.
|
// single package, e.g. traditional compilation.
|
||||||
//
|
//
|
||||||
// The importer's initial packages are always loaded from
|
// The initial packages (CreatePkgs and ImportPkgs) are always
|
||||||
// source, regardless of this flag's setting.
|
// loaded from Go source, regardless of this flag's setting.
|
||||||
SourceImports bool
|
SourceImports bool
|
||||||
|
|
||||||
// If Build is non-nil, it is used to locate source packages.
|
// If Build is non-nil, it is used to locate source packages.
|
||||||
// Otherwise &build.Default is used.
|
// Otherwise &build.Default is used.
|
||||||
Build *build.Context
|
Build *build.Context
|
||||||
}
|
|
||||||
|
|
||||||
// build returns the effective build context.
|
// CreatePkgs specifies a list of non-importable initial
|
||||||
func (c *Config) build() *build.Context {
|
// packages to create. Each element is a list of parsed files
|
||||||
if c.Build != nil {
|
// to be type-checked into a new package whose name is taken
|
||||||
return c.Build
|
// from ast.File.Package.
|
||||||
}
|
|
||||||
return &build.Default
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new, empty Importer using configuration options
|
|
||||||
// specified by config.
|
|
||||||
//
|
//
|
||||||
func New(config *Config) *Importer {
|
// The resulting packages will appear in the corresponding
|
||||||
// Initialize by mutating the caller's copy,
|
// elements of the Program.Created slice.
|
||||||
// so all copies agree on the identity of the map.
|
CreatePkgs [][]*ast.File
|
||||||
if config.TypeChecker.Packages == nil {
|
|
||||||
config.TypeChecker.Packages = make(map[string]*types.Package)
|
// ImportPkgs specifies a set of initial packages to load from
|
||||||
|
// source. The map keys are package import paths, used to
|
||||||
|
// locate the package relative to $GOROOT. The corresponding
|
||||||
|
// values indicate whether to augment the package by *_test.go
|
||||||
|
// files.
|
||||||
|
//
|
||||||
|
// Due to current type-checker limitations, at most one entry
|
||||||
|
// may be augmented (true).
|
||||||
|
ImportPkgs map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the caller's effective Import funcion.
|
// A Program is a Go program loaded from source or binary
|
||||||
importfn := config.TypeChecker.Import
|
// as specified by a Config.
|
||||||
if importfn == nil {
|
type Program struct {
|
||||||
importfn = gcimporter.Import
|
Fset *token.FileSet // the file set for this program
|
||||||
|
|
||||||
|
// Created[i] contains the initial package whose ASTs were
|
||||||
|
// supplied by Config.CreatePkgs[i].
|
||||||
|
Created []*PackageInfo
|
||||||
|
|
||||||
|
// Imported contains the initially imported packages,
|
||||||
|
// as specified by Config.ImportPkgs.
|
||||||
|
Imported map[string]*PackageInfo
|
||||||
|
|
||||||
|
// ImportMap is the canonical mapping of import paths to
|
||||||
|
// packages used by the type-checker (Config.TypeChecker.Packages).
|
||||||
|
// It contains all Imported initial packages, but not Created
|
||||||
|
// ones, and all imported dependencies.
|
||||||
|
ImportMap map[string]*types.Package
|
||||||
|
|
||||||
|
// AllPackages contains the PackageInfo of every package
|
||||||
|
// encountered by Load: all initial packages and all
|
||||||
|
// dependencies, including incomplete ones.
|
||||||
|
AllPackages map[*types.Package]*PackageInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
imp := &Importer{
|
func (conf *Config) fset() *token.FileSet {
|
||||||
Fset: token.NewFileSet(),
|
if conf.Fset == nil {
|
||||||
Config: config,
|
conf.Fset = token.NewFileSet()
|
||||||
importfn: importfn,
|
|
||||||
srcpkgs: make(map[string]bool),
|
|
||||||
imported: make(map[string]*importInfo),
|
|
||||||
}
|
}
|
||||||
return imp
|
return conf.Fset
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllPackages returns a new slice containing all complete packages
|
// ParseFile is a convenience function that invokes the parser using
|
||||||
// loaded by importer imp.
|
// the Config's FileSet, which is initialized if nil.
|
||||||
//
|
//
|
||||||
// This returns only packages that were loaded from source or directly
|
func (conf *Config) ParseFile(filename string, src interface{}, mode parser.Mode) (*ast.File, error) {
|
||||||
// imported from a source package. It does not include packages
|
return parser.ParseFile(conf.fset(), filename, src, mode)
|
||||||
// indirectly referenced by a binary package; they are found in
|
|
||||||
// config.TypeChecker.Packages.
|
|
||||||
// TODO(adonovan): rethink this API.
|
|
||||||
//
|
|
||||||
func (imp *Importer) AllPackages() []*PackageInfo {
|
|
||||||
return append([]*PackageInfo(nil), imp.allPackages...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// doImport imports the package denoted by path.
|
// FromArgsUsage is a partial usage message that applications calling
|
||||||
// It implements the types.Importer prototype.
|
// FromArgs may wish to include in their -help output.
|
||||||
//
|
const FromArgsUsage = `
|
||||||
// imports is the import map of the importing package, later
|
|
||||||
// accessible as types.Package.Imports(). If non-nil, doImport will
|
|
||||||
// update it to include this import. (It may be nil in recursive
|
|
||||||
// calls for prefetching.)
|
|
||||||
//
|
|
||||||
// It returns an error if a package could not be created
|
|
||||||
// (e.g. go/build or parse error), but type errors are reported via
|
|
||||||
// the types.Config.Error callback (the first of which is also saved
|
|
||||||
// in the package's PackageInfo).
|
|
||||||
//
|
|
||||||
// Idempotent.
|
|
||||||
//
|
|
||||||
func (imp *Importer) doImport(imports map[string]*types.Package, path string) (*types.Package, error) {
|
|
||||||
// Package unsafe is handled specially, and has no PackageInfo.
|
|
||||||
// TODO(adonovan): a fake empty package would make things simpler.
|
|
||||||
if path == "unsafe" {
|
|
||||||
return types.Unsafe, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := imp.ImportPackage(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the type checker's package map on success.
|
|
||||||
imports[path] = info.Pkg
|
|
||||||
|
|
||||||
return info.Pkg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImportPackage imports the package whose import path is path, plus
|
|
||||||
// its necessary dependencies.
|
|
||||||
//
|
|
||||||
// Precondition: path != "unsafe".
|
|
||||||
//
|
|
||||||
func (imp *Importer) ImportPackage(path string) (*PackageInfo, error) {
|
|
||||||
ii, ok := imp.imported[path]
|
|
||||||
if !ok {
|
|
||||||
ii = &importInfo{path: path}
|
|
||||||
imp.imported[path] = ii
|
|
||||||
|
|
||||||
// Find and create the actual package.
|
|
||||||
if augment, ok := imp.srcpkgs[ii.path]; ok || imp.Config.SourceImports {
|
|
||||||
which := "g" // load *.go files
|
|
||||||
if augment {
|
|
||||||
which = "gt" // augment package by in-package *_test.go files
|
|
||||||
}
|
|
||||||
imp.loadFromSource(ii, which)
|
|
||||||
} else {
|
|
||||||
imp.loadFromBinary(ii)
|
|
||||||
}
|
|
||||||
if ii.info != nil {
|
|
||||||
ii.info.Importable = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ii.info, ii.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadFromBinary implements package loading from the client-supplied
|
|
||||||
// external source, e.g. object files from the gc compiler.
|
|
||||||
//
|
|
||||||
func (imp *Importer) loadFromBinary(ii *importInfo) {
|
|
||||||
pkg, err := imp.importfn(imp.Config.TypeChecker.Packages, ii.path)
|
|
||||||
if pkg != nil {
|
|
||||||
ii.info = &PackageInfo{Pkg: pkg}
|
|
||||||
imp.allPackages = append(imp.allPackages, ii.info)
|
|
||||||
} else {
|
|
||||||
ii.err = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadFromSource implements package loading by parsing Go source files
|
|
||||||
// located by go/build. which indicates which files to include in the
|
|
||||||
// package.
|
|
||||||
//
|
|
||||||
func (imp *Importer) loadFromSource(ii *importInfo, which string) {
|
|
||||||
if files, err := parsePackageFiles(imp.Config.build(), imp.Fset, ii.path, which); err == nil {
|
|
||||||
// Type-check the package.
|
|
||||||
ii.info = imp.CreatePackage(ii.path, files...)
|
|
||||||
} else {
|
|
||||||
ii.err = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreatePackage creates and type-checks a package from the specified
|
|
||||||
// list of parsed files, importing their dependencies. It returns a
|
|
||||||
// PackageInfo containing the resulting types.Package, the ASTs, and
|
|
||||||
// other type information.
|
|
||||||
//
|
|
||||||
// The order of files determines the package initialization order.
|
|
||||||
//
|
|
||||||
// path is the full name under which this package is known, such as
|
|
||||||
// appears in an import declaration. e.g. "sync/atomic". It need not
|
|
||||||
// be unique; for example, it is possible to construct two distinct
|
|
||||||
// packages both named "main".
|
|
||||||
//
|
|
||||||
// The resulting package is accessible via AllPackages() but is not
|
|
||||||
// importable, i.e. no 'import' spec can resolve to it.
|
|
||||||
//
|
|
||||||
// CreatePackage never fails, but the resulting package may contain type
|
|
||||||
// errors; the first of these is recorded in PackageInfo.Err.
|
|
||||||
//
|
|
||||||
func (imp *Importer) CreatePackage(path string, files ...*ast.File) *PackageInfo {
|
|
||||||
info := &PackageInfo{
|
|
||||||
Files: files,
|
|
||||||
Info: types.Info{
|
|
||||||
Types: make(map[ast.Expr]types.Type),
|
|
||||||
Values: make(map[ast.Expr]exact.Value),
|
|
||||||
Objects: make(map[*ast.Ident]types.Object),
|
|
||||||
Implicits: make(map[ast.Node]types.Object),
|
|
||||||
Scopes: make(map[ast.Node]*types.Scope),
|
|
||||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use a copy of the types.Config so we can vary IgnoreFuncBodies.
|
|
||||||
tc := imp.Config.TypeChecker
|
|
||||||
tc.IgnoreFuncBodies = false
|
|
||||||
if f := imp.Config.TypeCheckFuncBodies; f != nil {
|
|
||||||
tc.IgnoreFuncBodies = !f(path)
|
|
||||||
}
|
|
||||||
if tc.Error == nil {
|
|
||||||
tc.Error = func(e error) { fmt.Fprintln(os.Stderr, e) }
|
|
||||||
}
|
|
||||||
tc.Import = imp.doImport // doImport wraps the user's importfn, effectively
|
|
||||||
info.Pkg, info.Err = tc.Check(path, imp.Fset, files, &info.Info)
|
|
||||||
imp.allPackages = append(imp.allPackages, info)
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
// InitialPackagesUsage is a partial usage message that client
|
|
||||||
// applications may wish to include in their -help output.
|
|
||||||
const InitialPackagesUsage = `
|
|
||||||
<args> is a list of arguments denoting a set of initial packages.
|
<args> is a list of arguments denoting a set of initial packages.
|
||||||
Each argument may take one of two forms:
|
Each argument may take one of two forms:
|
||||||
|
|
||||||
|
@ -326,41 +251,15 @@ Each argument may take one of two forms:
|
||||||
A '--' argument terminates the list of packages.
|
A '--' argument terminates the list of packages.
|
||||||
`
|
`
|
||||||
|
|
||||||
// LoadInitialPackages interprets args as a set of packages, loads
|
// FromArgs interprets args as a set of initial packages to load from
|
||||||
// those packages and their dependencies, and returns them.
|
// source and updates the configuration. It returns the list of
|
||||||
|
// unconsumed arguments.
|
||||||
//
|
//
|
||||||
// It is intended for use in command-line interfaces that require a
|
// It is intended for use in command-line interfaces that require a
|
||||||
// set of initial packages to be specified; see InitialPackagesUsage
|
// set of initial packages to be specified; see FromArgsUsage message
|
||||||
// message for details.
|
// for details.
|
||||||
//
|
//
|
||||||
// The second result parameter returns the list of unconsumed
|
func (conf *Config) FromArgs(args []string) (rest []string, err error) {
|
||||||
// arguments.
|
|
||||||
//
|
|
||||||
// It is an error to specify no packages.
|
|
||||||
//
|
|
||||||
// Precondition: LoadInitialPackages cannot be called after any
|
|
||||||
// previous calls to Load* on the same importer.
|
|
||||||
//
|
|
||||||
func (imp *Importer) LoadInitialPackages(args []string) ([]*PackageInfo, []string, error) {
|
|
||||||
// The "augmentation" mechanism requires that we mark all
|
|
||||||
// packages to be augmented before we import a single one.
|
|
||||||
if len(imp.allPackages) > 0 {
|
|
||||||
return nil, nil, errors.New("LoadInitialPackages called on non-pristine Importer")
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use two passes. The first parses the files for each
|
|
||||||
// non-importable package and discovers the set of importable
|
|
||||||
// packages that require augmentation by in-package _test.go
|
|
||||||
// files. The second creates the ad-hoc packages and imports
|
|
||||||
// the importable ones.
|
|
||||||
//
|
|
||||||
// This is necessary to ensure that all packages requiring
|
|
||||||
// augmentation are known before before any package is
|
|
||||||
// imported.
|
|
||||||
|
|
||||||
// Pass 1: parse the sets of files for each package.
|
|
||||||
var pkgs []*initialPkg
|
|
||||||
var seenAugmented bool
|
|
||||||
for len(args) > 0 {
|
for len(args) > 0 {
|
||||||
arg := args[0]
|
arg := args[0]
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
|
@ -371,134 +270,114 @@ func (imp *Importer) LoadInitialPackages(args []string) ([]*PackageInfo, []strin
|
||||||
if strings.HasSuffix(arg, ".go") {
|
if strings.HasSuffix(arg, ".go") {
|
||||||
// Assume arg is a comma-separated list of *.go files
|
// Assume arg is a comma-separated list of *.go files
|
||||||
// comprising a single package.
|
// comprising a single package.
|
||||||
pkg, err := initialPackageFromFiles(imp.Fset, arg)
|
err = conf.CreateFromFilenames(strings.Split(arg, ",")...)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
pkgs = append(pkgs, pkg)
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Assume arg is a directory name denoting a
|
// Assume arg is a directory name denoting a
|
||||||
// package, perhaps plus an external test
|
// package, perhaps plus an external test
|
||||||
// package unless prefixed by "notest:".
|
// package unless prefixed by "notest:".
|
||||||
path := strings.TrimPrefix(arg, "notest:")
|
if path := strings.TrimPrefix(arg, "notest:"); path != arg {
|
||||||
|
conf.Import(path)
|
||||||
|
} else {
|
||||||
|
err = conf.ImportWithTests(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFromFilenames is a convenience function that parses the
|
||||||
|
// specified *.go files and adds a package entry for them to
|
||||||
|
// conf.CreatePkgs.
|
||||||
|
//
|
||||||
|
func (conf *Config) CreateFromFilenames(filenames ...string) error {
|
||||||
|
files, err := parseFiles(conf.fset(), ".", filenames...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
conf.CreateFromFiles(files...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFromFiles is a convenience function that adds a CreatePkgs
|
||||||
|
// entry for the specified parsed files.
|
||||||
|
//
|
||||||
|
// Precondition: conf.Fset is non-nil and was the fileset used to parse
|
||||||
|
// the files. (e.g. the files came from conf.ParseFile().)
|
||||||
|
//
|
||||||
|
func (conf *Config) CreateFromFiles(files ...*ast.File) {
|
||||||
|
if conf.Fset == nil {
|
||||||
|
panic("nil Fset")
|
||||||
|
}
|
||||||
|
conf.CreatePkgs = append(conf.CreatePkgs, files)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportWithTests is a convenience function that adds path to
|
||||||
|
// ImportPkgs, the set of initial source packages located relative to
|
||||||
|
// $GOPATH. The package will be augmented by any *_test.go files in
|
||||||
|
// its directory that contain a "package x" (not "package x_test")
|
||||||
|
// declaration.
|
||||||
|
//
|
||||||
|
// In addition, if any *_test.go files contain a "package <path>_test"
|
||||||
|
// declaration, an additional package comprising just those files will
|
||||||
|
// be added to CreatePkgs.
|
||||||
|
//
|
||||||
|
func (conf *Config) ImportWithTests(path string) error {
|
||||||
if path == "unsafe" {
|
if path == "unsafe" {
|
||||||
continue // ignore; has no PackageInfo
|
return nil // ignore; not a real package
|
||||||
}
|
|
||||||
|
|
||||||
pkgs = append(pkgs, &initialPkg{
|
|
||||||
path: path,
|
|
||||||
importable: true,
|
|
||||||
})
|
|
||||||
imp.srcpkgs[path] = false // unaugmented source package
|
|
||||||
|
|
||||||
if path != arg {
|
|
||||||
continue // had "notest:" prefix
|
|
||||||
}
|
}
|
||||||
|
conf.Import(path)
|
||||||
|
|
||||||
// TODO(adonovan): due to limitations of the current type
|
// TODO(adonovan): due to limitations of the current type
|
||||||
// checker design, we can augment at most one package.
|
// checker design, we can augment at most one package.
|
||||||
if seenAugmented {
|
for _, augmented := range conf.ImportPkgs {
|
||||||
continue // don't attempt a second
|
if augmented {
|
||||||
|
return nil // don't attempt a second
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the external test package.
|
// Load the external test package.
|
||||||
xtestFiles, err := parsePackageFiles(imp.Config.build(), imp.Fset, path, "x")
|
xtestFiles, err := parsePackageFiles(conf.build(), conf.fset(), path, "x")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return err
|
||||||
}
|
}
|
||||||
if len(xtestFiles) > 0 {
|
if len(xtestFiles) > 0 {
|
||||||
pkgs = append(pkgs, &initialPkg{
|
conf.CreateFromFiles(xtestFiles...)
|
||||||
path: path + "_test",
|
|
||||||
importable: false,
|
|
||||||
files: xtestFiles,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark the non-xtest package for augmentation with
|
// Mark the non-xtest package for augmentation with
|
||||||
// in-package *_test.go files when we import it below.
|
// in-package *_test.go files when we import it below.
|
||||||
imp.srcpkgs[path] = true
|
conf.ImportPkgs[path] = true
|
||||||
seenAugmented = true
|
return nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass 2: type-check each set of files to make a package.
|
// Import is a convenience function that adds path to ImportPkgs, the
|
||||||
var infos []*PackageInfo
|
// set of initial packages that will be imported from source.
|
||||||
for _, pkg := range pkgs {
|
|
||||||
var info *PackageInfo
|
|
||||||
if pkg.importable {
|
|
||||||
var err error
|
|
||||||
info, err = imp.ImportPackage(pkg.path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err // e.g. parse error (but not type error)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info = imp.CreatePackage(pkg.path, pkg.files...)
|
|
||||||
}
|
|
||||||
infos = append(infos, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pkgs) == 0 {
|
|
||||||
return nil, nil, errors.New("no *.go source files nor packages were specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
return infos, args, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type initialPkg struct {
|
|
||||||
path string // the package's import path
|
|
||||||
importable bool // add package to import map (false for main and xtests)
|
|
||||||
files []*ast.File // set of files (non-importable packages only)
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialPackageFromFiles returns an initialPkg, given a
|
|
||||||
// comma-separated list of *.go source files belonging to the same
|
|
||||||
// directory and possessing the same 'package decl'.
|
|
||||||
//
|
//
|
||||||
func initialPackageFromFiles(fset *token.FileSet, arg string) (*initialPkg, error) {
|
func (conf *Config) Import(path string) {
|
||||||
filenames := strings.Split(arg, ",")
|
if path == "unsafe" {
|
||||||
for _, filename := range filenames {
|
return // ignore; not a real package
|
||||||
if !strings.HasSuffix(filename, ".go") {
|
|
||||||
return nil, fmt.Errorf("not a *.go source file: %q", filename)
|
|
||||||
}
|
}
|
||||||
|
if conf.ImportPkgs == nil {
|
||||||
|
conf.ImportPkgs = make(map[string]bool)
|
||||||
}
|
}
|
||||||
|
conf.ImportPkgs[path] = false // unaugmented source package
|
||||||
files, err := ParseFiles(fset, ".", filenames...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take the package name from the 'package decl' in each file,
|
|
||||||
// all of which must match.
|
|
||||||
pkgname := files[0].Name.Name
|
|
||||||
for i, file := range files[1:] {
|
|
||||||
if pn := file.Name.Name; pn != pkgname {
|
|
||||||
err := fmt.Errorf("can't load package: found packages %s (%s) and %s (%s)",
|
|
||||||
pkgname, filenames[0], pn, filenames[i])
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// TODO(adonovan): check dirnames are equal, like 'go build' does.
|
|
||||||
}
|
|
||||||
|
|
||||||
return &initialPkg{
|
|
||||||
path: pkgname,
|
|
||||||
importable: false,
|
|
||||||
files: files,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PathEnclosingInterval returns the PackageInfo and ast.Node that
|
// PathEnclosingInterval returns the PackageInfo and ast.Node that
|
||||||
// contain source interval [start, end), and all the node's ancestors
|
// contain source interval [start, end), and all the node's ancestors
|
||||||
// up to the AST root. It searches all ast.Files of all packages in the
|
// up to the AST root. It searches all ast.Files of all packages in prog.
|
||||||
// Importer imp. exact is defined as for astutil.PathEnclosingInterval.
|
// exact is defined as for astutil.PathEnclosingInterval.
|
||||||
//
|
//
|
||||||
// The result is (nil, nil, false) if not found.
|
// The result is (nil, nil, false) if not found.
|
||||||
//
|
//
|
||||||
func (imp *Importer) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) {
|
func (prog *Program) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) {
|
||||||
for _, info := range imp.allPackages {
|
for _, info := range prog.AllPackages {
|
||||||
for _, f := range info.Files {
|
for _, f := range info.Files {
|
||||||
if !tokenFileContainsPos(imp.Fset.File(f.Package), start) {
|
if !tokenFileContainsPos(prog.Fset.File(f.Pos()), start) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil {
|
if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil {
|
||||||
|
@ -509,9 +388,252 @@ func (imp *Importer) PathEnclosingInterval(start, end token.Pos) (pkg *PackageIn
|
||||||
return nil, nil, false
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos)
|
// InitialPackages returns a new slice containing the set of initial
|
||||||
func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
|
// packages (Created + Imported) in unspecified order.
|
||||||
p := int(pos)
|
//
|
||||||
base := f.Base()
|
func (prog *Program) InitialPackages() []*PackageInfo {
|
||||||
return base <= p && p < base+f.Size()
|
infos := make([]*PackageInfo, 0, len(prog.Created)+len(prog.Imported))
|
||||||
|
infos = append(infos, prog.Created...)
|
||||||
|
for _, info := range prog.Imported {
|
||||||
|
infos = append(infos, info)
|
||||||
|
}
|
||||||
|
return infos
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Implementation ----------
|
||||||
|
|
||||||
|
// importer holds the working state of the algorithm.
|
||||||
|
type importer struct {
|
||||||
|
conf *Config // the client configuration
|
||||||
|
prog *Program // resulting program
|
||||||
|
imported map[string]*importInfo // all imported packages (incl. failures) by import path
|
||||||
|
}
|
||||||
|
|
||||||
|
// importInfo tracks the success or failure of a single import.
|
||||||
|
type importInfo struct {
|
||||||
|
info *PackageInfo // results of typechecking (including type errors)
|
||||||
|
err error // reason for failure to make a package
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load creates the initial packages specified by conf.{Create,Import}Pkgs,
|
||||||
|
// loading their dependencies packages as needed.
|
||||||
|
//
|
||||||
|
// On success, it returns a Program containing a PackageInfo for each
|
||||||
|
// package; all are well-typed.
|
||||||
|
//
|
||||||
|
// It is an error if no packages were loaded.
|
||||||
|
//
|
||||||
|
func (conf *Config) Load() (*Program, error) {
|
||||||
|
// Initialize by setting the conf's copy, so all copies of
|
||||||
|
// TypeChecker agree on the identity of the map.
|
||||||
|
if conf.TypeChecker.Packages == nil {
|
||||||
|
conf.TypeChecker.Packages = make(map[string]*types.Package)
|
||||||
|
}
|
||||||
|
|
||||||
|
prog := &Program{
|
||||||
|
Fset: conf.fset(),
|
||||||
|
Imported: make(map[string]*PackageInfo),
|
||||||
|
ImportMap: conf.TypeChecker.Packages,
|
||||||
|
AllPackages: make(map[*types.Package]*PackageInfo),
|
||||||
|
}
|
||||||
|
|
||||||
|
imp := importer{
|
||||||
|
conf: conf,
|
||||||
|
prog: prog,
|
||||||
|
imported: make(map[string]*importInfo),
|
||||||
|
}
|
||||||
|
|
||||||
|
for path := range conf.ImportPkgs {
|
||||||
|
info, err := imp.importPackage(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err // e.g. parse error (but not type error)
|
||||||
|
}
|
||||||
|
prog.Imported[path] = info
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, files := range conf.CreatePkgs {
|
||||||
|
pkgname, err := packageName(files, conf.Fset)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// TODO(adonovan): pkgnames are not unique, but the
|
||||||
|
// typechecker assumes they are in its Id() logic.
|
||||||
|
prog.Created = append(prog.Created, imp.createPackage(pkgname, files...))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(prog.Imported)+len(prog.Created) == 0 {
|
||||||
|
return nil, errors.New("no *.go source files nor packages were specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report errors in indirectly imported packages.
|
||||||
|
var errpkgs []string
|
||||||
|
for _, info := range prog.AllPackages {
|
||||||
|
if info.err != nil {
|
||||||
|
errpkgs = append(errpkgs, info.Pkg.Path())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errpkgs != nil {
|
||||||
|
return nil, fmt.Errorf("couldn't load packages due to type errors: %s",
|
||||||
|
strings.Join(errpkgs, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create infos for indirectly imported packages.
|
||||||
|
// e.g. incomplete packages without syntax, loaded from export data.
|
||||||
|
for _, obj := range prog.ImportMap {
|
||||||
|
if prog.AllPackages[obj] == nil {
|
||||||
|
prog.AllPackages[obj] = &PackageInfo{Pkg: obj, Importable: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return prog, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// build returns the effective build context.
|
||||||
|
func (conf *Config) build() *build.Context {
|
||||||
|
if conf.Build != nil {
|
||||||
|
return conf.Build
|
||||||
|
}
|
||||||
|
return &build.Default
|
||||||
|
}
|
||||||
|
|
||||||
|
// doImport imports the package denoted by path.
|
||||||
|
// It implements the types.Importer signature.
|
||||||
|
//
|
||||||
|
// imports is the import map of the importing package, later
|
||||||
|
// accessible as types.Package.Imports(). If non-nil, doImport will
|
||||||
|
// update it to include this import. (It may be nil in recursive
|
||||||
|
// calls for prefetching.)
|
||||||
|
//
|
||||||
|
// It returns an error if a package could not be created
|
||||||
|
// (e.g. go/build or parse error), but type errors are reported via
|
||||||
|
// the types.Config.Error callback (the first of which is also saved
|
||||||
|
// in the package's PackageInfo).
|
||||||
|
//
|
||||||
|
// Idempotent.
|
||||||
|
//
|
||||||
|
func (imp *importer) doImport(imports map[string]*types.Package, path string) (*types.Package, error) {
|
||||||
|
// Package unsafe is handled specially, and has no PackageInfo.
|
||||||
|
if path == "unsafe" {
|
||||||
|
return types.Unsafe, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := imp.importPackage(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the type checker's package map on success.
|
||||||
|
imports[path] = info.Pkg
|
||||||
|
|
||||||
|
return info.Pkg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// importPackage imports the package with the given import path, plus
|
||||||
|
// its dependencies.
|
||||||
|
//
|
||||||
|
// Precondition: path != "unsafe".
|
||||||
|
//
|
||||||
|
func (imp *importer) importPackage(path string) (*PackageInfo, error) {
|
||||||
|
ii, ok := imp.imported[path]
|
||||||
|
if !ok {
|
||||||
|
// In preorder, initialize the map entry to a cycle
|
||||||
|
// error in case importPackage(path) is called again
|
||||||
|
// before the import is completed.
|
||||||
|
// TODO(adonovan): go/types should be responsible for
|
||||||
|
// reporting cycles; see bug 7114.
|
||||||
|
ii = &importInfo{err: fmt.Errorf("import cycle in package %s", path)}
|
||||||
|
imp.imported[path] = ii
|
||||||
|
|
||||||
|
// Find and create the actual package.
|
||||||
|
if augment, ok := imp.conf.ImportPkgs[path]; ok || imp.conf.SourceImports {
|
||||||
|
which := "g" // load *.go files
|
||||||
|
if augment {
|
||||||
|
which = "gt" // augment package by in-package *_test.go files
|
||||||
|
}
|
||||||
|
|
||||||
|
ii.info, ii.err = imp.importFromSource(path, which)
|
||||||
|
} else {
|
||||||
|
ii.info, ii.err = imp.importFromBinary(path)
|
||||||
|
}
|
||||||
|
if ii.info != nil {
|
||||||
|
ii.info.Importable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ii.info, ii.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// importFromBinary implements package loading from the client-supplied
|
||||||
|
// external source, e.g. object files from the gc compiler.
|
||||||
|
//
|
||||||
|
func (imp *importer) importFromBinary(path string) (*PackageInfo, error) {
|
||||||
|
// Determine the caller's effective Import function.
|
||||||
|
importfn := imp.conf.TypeChecker.Import
|
||||||
|
if importfn == nil {
|
||||||
|
importfn = gcimporter.Import
|
||||||
|
}
|
||||||
|
pkg, err := importfn(imp.conf.TypeChecker.Packages, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
info := &PackageInfo{Pkg: pkg}
|
||||||
|
imp.prog.AllPackages[pkg] = info
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// importFromSource implements package loading by parsing Go source files
|
||||||
|
// located by go/build. which indicates which files to include in the
|
||||||
|
// package.
|
||||||
|
//
|
||||||
|
func (imp *importer) importFromSource(path string, which string) (*PackageInfo, error) {
|
||||||
|
files, err := parsePackageFiles(imp.conf.build(), imp.conf.fset(), path, which)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Type-check the package.
|
||||||
|
return imp.createPackage(path, files...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createPackage creates and type-checks a package from the specified
|
||||||
|
// list of parsed files, importing their dependencies. It returns a
|
||||||
|
// PackageInfo containing the resulting types.Package, the ASTs, and
|
||||||
|
// other type information.
|
||||||
|
//
|
||||||
|
// The order of files determines the package initialization order.
|
||||||
|
//
|
||||||
|
// path will be the resulting package's Path().
|
||||||
|
// For an ad-hoc package, this is not necessarily unique.
|
||||||
|
//
|
||||||
|
// The resulting package is accessible via AllPackages but is not
|
||||||
|
// importable, i.e. no 'import' spec can resolve to it.
|
||||||
|
//
|
||||||
|
// createPackage never fails, but the resulting package may contain type
|
||||||
|
// errors; the first of these is recorded in PackageInfo.err.
|
||||||
|
//
|
||||||
|
func (imp *importer) createPackage(path string, files ...*ast.File) *PackageInfo {
|
||||||
|
info := &PackageInfo{
|
||||||
|
Files: files,
|
||||||
|
Info: types.Info{
|
||||||
|
Types: make(map[ast.Expr]types.Type),
|
||||||
|
Values: make(map[ast.Expr]exact.Value),
|
||||||
|
Objects: make(map[*ast.Ident]types.Object),
|
||||||
|
Implicits: make(map[ast.Node]types.Object),
|
||||||
|
Scopes: make(map[ast.Node]*types.Scope),
|
||||||
|
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a copy of the types.Config so we can vary IgnoreFuncBodies.
|
||||||
|
tc := imp.conf.TypeChecker
|
||||||
|
tc.IgnoreFuncBodies = false
|
||||||
|
if f := imp.conf.TypeCheckFuncBodies; f != nil {
|
||||||
|
tc.IgnoreFuncBodies = !f(path)
|
||||||
|
}
|
||||||
|
if tc.Error == nil {
|
||||||
|
tc.Error = func(e error) { fmt.Fprintln(os.Stderr, e) }
|
||||||
|
}
|
||||||
|
tc.Import = imp.doImport // doImport wraps the user's importfn, effectively
|
||||||
|
info.Pkg, info.err = tc.Check(path, imp.conf.fset(), files, &info.Info)
|
||||||
|
imp.prog.AllPackages[info.Pkg] = info
|
||||||
|
return info
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,52 +6,73 @@ package importer_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.google.com/p/go.tools/importer"
|
"code.google.com/p/go.tools/importer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadInitialPackages(t *testing.T) {
|
func loadFromArgs(args []string) (prog *importer.Program, rest []string, err error) {
|
||||||
|
conf := &importer.Config{}
|
||||||
|
rest, err = conf.FromArgs(args)
|
||||||
|
if err == nil {
|
||||||
|
prog, err = conf.Load()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadFromArgs(t *testing.T) {
|
||||||
// Failed load: bad first import path causes parsePackageFiles to fail.
|
// Failed load: bad first import path causes parsePackageFiles to fail.
|
||||||
args := []string{"nosuchpkg", "errors"}
|
args := []string{"nosuchpkg", "errors"}
|
||||||
if _, _, err := importer.New(&importer.Config{}).LoadInitialPackages(args); err == nil {
|
if _, _, err := loadFromArgs(args); err == nil {
|
||||||
t.Errorf("LoadInitialPackages(%q) succeeded, want failure", args)
|
t.Errorf("loadFromArgs(%q) succeeded, want failure", args)
|
||||||
} else {
|
} else {
|
||||||
// cannot find package: ok.
|
// cannot find package: ok.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Failed load: bad second import path proceeds to doImport0, which fails.
|
// Failed load: bad second import path proceeds to doImport0, which fails.
|
||||||
args = []string{"errors", "nosuchpkg"}
|
args = []string{"errors", "nosuchpkg"}
|
||||||
if _, _, err := importer.New(&importer.Config{}).LoadInitialPackages(args); err == nil {
|
if _, _, err := loadFromArgs(args); err == nil {
|
||||||
t.Errorf("LoadInitialPackages(%q) succeeded, want failure", args)
|
t.Errorf("loadFromArgs(%q) succeeded, want failure", args)
|
||||||
} else {
|
} else {
|
||||||
// cannot find package: ok
|
// cannot find package: ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Successful load.
|
// Successful load.
|
||||||
args = []string{"fmt", "errors", "testdata/a.go,testdata/b.go", "--", "surplus"}
|
args = []string{"fmt", "errors", "testdata/a.go,testdata/b.go", "--", "surplus"}
|
||||||
imp := importer.New(&importer.Config{})
|
prog, rest, err := loadFromArgs(args)
|
||||||
infos, rest, err := imp.LoadInitialPackages(args)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("LoadInitialPackages(%q) failed: %s", args, err)
|
t.Errorf("loadFromArgs(%q) failed: %s", args, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if got, want := fmt.Sprint(rest), "[surplus]"; got != want {
|
if got, want := fmt.Sprint(rest), "[surplus]"; got != want {
|
||||||
t.Errorf("LoadInitialPackages(%q) rest: got %s, want %s", got, want)
|
t.Errorf("loadFromArgs(%q) rest: got %s, want %s", got, want)
|
||||||
}
|
}
|
||||||
// Check list of initial packages.
|
// Check list of Created packages.
|
||||||
var pkgnames []string
|
var pkgnames []string
|
||||||
for _, info := range infos {
|
for _, info := range prog.Created {
|
||||||
pkgnames = append(pkgnames, info.Pkg.Path())
|
pkgnames = append(pkgnames, info.Pkg.Path())
|
||||||
}
|
}
|
||||||
// Only the first import path (currently) contributes tests.
|
// Only the first import path (currently) contributes tests.
|
||||||
if got, want := fmt.Sprint(pkgnames), "[fmt fmt_test errors P]"; got != want {
|
if got, want := fmt.Sprint(pkgnames), "[fmt_test P]"; got != want {
|
||||||
t.Errorf("InitialPackages: got %s, want %s", got, want)
|
t.Errorf("Created: got %s, want %s", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check set of Imported packages.
|
||||||
|
pkgnames = nil
|
||||||
|
for path := range prog.Imported {
|
||||||
|
pkgnames = append(pkgnames, path)
|
||||||
|
}
|
||||||
|
sort.Strings(pkgnames)
|
||||||
|
// Only the first import path (currently) contributes tests.
|
||||||
|
if got, want := fmt.Sprint(pkgnames), "[errors fmt]"; got != want {
|
||||||
|
t.Errorf("Loaded: got %s, want %s", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
// Check set of transitive packages.
|
// Check set of transitive packages.
|
||||||
// There are >30 and the set may grow over time, so only check a few.
|
// There are >30 and the set may grow over time, so only check a few.
|
||||||
all := map[string]struct{}{}
|
all := map[string]struct{}{}
|
||||||
for _, info := range imp.AllPackages() {
|
for _, info := range prog.AllPackages {
|
||||||
all[info.Pkg.Path()] = struct{}{}
|
all[info.Pkg.Path()] = struct{}{}
|
||||||
}
|
}
|
||||||
want := []string{"strings", "time", "runtime", "testing", "unicode"}
|
want := []string{"strings", "time", "runtime", "testing", "unicode"}
|
||||||
|
|
|
@ -20,8 +20,8 @@ import (
|
||||||
type PackageInfo struct {
|
type PackageInfo struct {
|
||||||
Pkg *types.Package
|
Pkg *types.Package
|
||||||
Importable bool // true if 'import "Pkg.Path()"' would resolve to this
|
Importable bool // true if 'import "Pkg.Path()"' would resolve to this
|
||||||
Err error // non-nil if the package had static errors
|
|
||||||
Files []*ast.File // abstract syntax for the package's files
|
Files []*ast.File // abstract syntax for the package's files
|
||||||
|
err error // non-nil if the package had static errors
|
||||||
types.Info // type-checker deductions.
|
types.Info // type-checker deductions.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,8 +82,8 @@ func TestEnclosingFunction(t *testing.T) {
|
||||||
"900", "func@2.27"},
|
"900", "func@2.27"},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
imp := importer.New(&importer.Config{})
|
conf := importer.Config{Fset: token.NewFileSet()}
|
||||||
f, start, end := findInterval(t, imp.Fset, test.input, test.substr)
|
f, start, end := findInterval(t, conf.Fset, test.input, test.substr)
|
||||||
if f == nil {
|
if f == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -92,13 +92,16 @@ func TestEnclosingFunction(t *testing.T) {
|
||||||
t.Errorf("EnclosingFunction(%q) not exact", test.substr)
|
t.Errorf("EnclosingFunction(%q) not exact", test.substr)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mainInfo := imp.CreatePackage("main", f)
|
|
||||||
prog := ssa.NewProgram(imp.Fset, 0)
|
conf.CreateFromFiles(f)
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
|
||||||
|
iprog, err := conf.Load()
|
||||||
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pkg := prog.Package(mainInfo.Pkg)
|
prog := ssa.Create(iprog, 0)
|
||||||
|
pkg := prog.Package(iprog.Created[0].Pkg)
|
||||||
pkg.Build()
|
pkg.Build()
|
||||||
|
|
||||||
name := "(none)"
|
name := "(none)"
|
||||||
|
|
|
@ -8,12 +8,12 @@ package importer
|
||||||
// and used by it.
|
// and used by it.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
"go/build"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,13 +55,13 @@ func parsePackageFiles(ctxt *build.Context, fset *token.FileSet, path string, wh
|
||||||
}
|
}
|
||||||
filenames = append(filenames, s...)
|
filenames = append(filenames, s...)
|
||||||
}
|
}
|
||||||
return ParseFiles(fset, bp.Dir, filenames...)
|
return parseFiles(fset, bp.Dir, filenames...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseFiles parses the Go source files files within directory dir
|
// parseFiles parses the Go source files files within directory dir
|
||||||
// and returns their ASTs, or the first parse error if any.
|
// and returns their ASTs, or the first parse error if any.
|
||||||
//
|
//
|
||||||
func ParseFiles(fset *token.FileSet, dir string, files ...string) ([]*ast.File, error) {
|
func parseFiles(fset *token.FileSet, dir string, files ...string) ([]*ast.File, error) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
n := len(files)
|
n := len(files)
|
||||||
parsed := make([]*ast.File, n, n)
|
parsed := make([]*ast.File, n, n)
|
||||||
|
@ -104,26 +104,32 @@ func unreachable() {
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
// importsOf returns the set of paths imported by the specified files.
|
func packageName(files []*ast.File, fset *token.FileSet) (string, error) {
|
||||||
func importsOf(p string, files []*ast.File) map[string]bool {
|
if len(files) == 0 {
|
||||||
imports := make(map[string]bool)
|
return "", fmt.Errorf("no files in package")
|
||||||
outer:
|
|
||||||
for _, file := range files {
|
|
||||||
for _, decl := range file.Decls {
|
|
||||||
if decl, ok := decl.(*ast.GenDecl); ok {
|
|
||||||
if decl.Tok != token.IMPORT {
|
|
||||||
break outer // stop at the first non-import
|
|
||||||
}
|
}
|
||||||
for _, spec := range decl.Specs {
|
// Take the package name from the 'package decl' in each file,
|
||||||
spec := spec.(*ast.ImportSpec)
|
// all of which must match.
|
||||||
if path, _ := strconv.Unquote(spec.Path.Value); path != "C" {
|
pkgname := files[0].Name.Name
|
||||||
imports[path] = true
|
for _, file := range files[1:] {
|
||||||
|
if pn := file.Name.Name; pn != pkgname {
|
||||||
|
err := fmt.Errorf("can't load package: found packages %s (%s) and %s (%s)",
|
||||||
|
pkgname, filename(files[0], fset),
|
||||||
|
pn, filename(file, fset))
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
|
// TODO(adonovan): check dirnames are equal, like 'go build' does.
|
||||||
}
|
}
|
||||||
} else {
|
return pkgname, nil
|
||||||
break outer // stop at the first non-import
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos)
|
||||||
|
func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
|
||||||
|
p := int(pos)
|
||||||
|
base := f.Base()
|
||||||
|
return base <= p && p < base+f.Size()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return imports
|
func filename(file *ast.File, fset *token.FileSet) string {
|
||||||
|
return fset.File(file.Pos()).Name()
|
||||||
}
|
}
|
||||||
|
|
120
oracle/oracle.go
120
oracle/oracle.go
|
@ -183,7 +183,7 @@ func (res *Result) Serial() *serial.Result {
|
||||||
|
|
||||||
// Query runs a single oracle query.
|
// Query runs a single oracle query.
|
||||||
//
|
//
|
||||||
// args specify the main package in importer.LoadInitialPackages syntax.
|
// args specify the main package in (*importer.Config).FromArgs syntax.
|
||||||
// mode is the query mode ("callers", etc).
|
// mode is the query mode ("callers", etc).
|
||||||
// ptalog is the (optional) pointer-analysis log file.
|
// ptalog is the (optional) pointer-analysis log file.
|
||||||
// buildContext is the go/build configuration for locating packages.
|
// buildContext is the go/build configuration for locating packages.
|
||||||
|
@ -192,8 +192,11 @@ func (res *Result) Serial() *serial.Result {
|
||||||
// Clients that intend to perform multiple queries against the same
|
// Clients that intend to perform multiple queries against the same
|
||||||
// analysis scope should use this pattern instead:
|
// analysis scope should use this pattern instead:
|
||||||
//
|
//
|
||||||
// imp := importer.New(&importer.Config{Build: buildContext, SourceImports: true})
|
// conf := importer.Config{Build: buildContext, SourceImports: true}
|
||||||
// o, err := oracle.New(imp, args, nil)
|
// ... populate config, e.g. conf.FromArgs(args) ...
|
||||||
|
// iprog, err := conf.Load()
|
||||||
|
// if err != nil { ... }
|
||||||
|
// o, err := oracle.New(iprog, nil, false)
|
||||||
// if err != nil { ... }
|
// if err != nil { ... }
|
||||||
// for ... {
|
// for ... {
|
||||||
// qpos, err := oracle.ParseQueryPos(imp, pos, needExact)
|
// qpos, err := oracle.ParseQueryPos(imp, pos, needExact)
|
||||||
|
@ -219,29 +222,44 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
|
||||||
return nil, fmt.Errorf("invalid mode type: %q", mode)
|
return nil, fmt.Errorf("invalid mode type: %q", mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
impcfg := importer.Config{Build: buildContext, SourceImports: true}
|
conf := importer.Config{Build: buildContext, SourceImports: true}
|
||||||
|
|
||||||
|
// Determine initial packages.
|
||||||
|
args, err := conf.FromArgs(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(args) > 0 {
|
||||||
|
return nil, fmt.Errorf("surplus arguments: %q", args)
|
||||||
|
}
|
||||||
|
|
||||||
// For queries needing only a single typed package,
|
// For queries needing only a single typed package,
|
||||||
// reduce the analysis scope to that package.
|
// reduce the analysis scope to that package.
|
||||||
if minfo.needs&(needSSA|needRetainTypeInfo) == 0 {
|
if minfo.needs&(needSSA|needRetainTypeInfo) == 0 {
|
||||||
reduceScope(pos, &impcfg, &args)
|
reduceScope(pos, &conf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(adonovan): report type errors to the user via Serial
|
// TODO(adonovan): report type errors to the user via Serial
|
||||||
// types, not stderr?
|
// types, not stderr?
|
||||||
// impcfg.TypeChecker.Error = func(err error) {
|
// conf.TypeChecker.Error = func(err error) {
|
||||||
// E := err.(types.Error)
|
// E := err.(types.Error)
|
||||||
// fmt.Fprintf(os.Stderr, "%s: %s\n", E.Fset.Position(E.Pos), E.Msg)
|
// fmt.Fprintf(os.Stderr, "%s: %s\n", E.Fset.Position(E.Pos), E.Msg)
|
||||||
// }
|
// }
|
||||||
imp := importer.New(&impcfg)
|
|
||||||
o, err := newOracle(imp, args, ptalog, minfo.needs, reflection)
|
// Load/parse/type-check the program.
|
||||||
|
iprog, err := conf.Load()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
o, err := newOracle(iprog, ptalog, minfo.needs, reflection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var qpos *QueryPos
|
var qpos *QueryPos
|
||||||
if minfo.needs&(needPos|needExactPos) != 0 {
|
if minfo.needs&(needPos|needExactPos) != 0 {
|
||||||
qpos, err = ParseQueryPos(imp, pos, minfo.needs&needExactPos != 0)
|
qpos, err = ParseQueryPos(iprog, pos, minfo.needs&needExactPos != 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -249,7 +267,7 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
|
||||||
|
|
||||||
// SSA is built and we have the QueryPos.
|
// SSA is built and we have the QueryPos.
|
||||||
// Release the other ASTs and type info to the GC.
|
// Release the other ASTs and type info to the GC.
|
||||||
imp = nil
|
iprog = nil
|
||||||
|
|
||||||
return o.query(minfo, qpos)
|
return o.query(minfo, qpos)
|
||||||
}
|
}
|
||||||
|
@ -262,12 +280,7 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
|
||||||
//
|
//
|
||||||
// TODO(adonovan): this is a real mess... but it's fast.
|
// TODO(adonovan): this is a real mess... but it's fast.
|
||||||
//
|
//
|
||||||
func reduceScope(pos string, impcfg *importer.Config, args *[]string) {
|
func reduceScope(pos string, conf *importer.Config) {
|
||||||
// TODO(adonovan): make the 'args' argument of
|
|
||||||
// (*Importer).LoadInitialPackages part of the
|
|
||||||
// importer.Config, and inline LoadInitialPackages into
|
|
||||||
// NewImporter. Then we won't need the 'args' argument.
|
|
||||||
|
|
||||||
fqpos, err := fastQueryPos(pos)
|
fqpos, err := fastQueryPos(pos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return // bad query
|
return // bad query
|
||||||
|
@ -276,7 +289,7 @@ func reduceScope(pos string, impcfg *importer.Config, args *[]string) {
|
||||||
// TODO(adonovan): fix: this gives the wrong results for files
|
// TODO(adonovan): fix: this gives the wrong results for files
|
||||||
// in non-importable packages such as tests and ad-hoc packages
|
// in non-importable packages such as tests and ad-hoc packages
|
||||||
// specified as a list of files (incl. the oracle's tests).
|
// specified as a list of files (incl. the oracle's tests).
|
||||||
_, importPath, err := guessImportPath(fqpos.fset.File(fqpos.start).Name(), impcfg.Build)
|
_, importPath, err := guessImportPath(fqpos.fset.File(fqpos.start).Name(), conf.Build)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return // can't find GOPATH dir
|
return // can't find GOPATH dir
|
||||||
}
|
}
|
||||||
|
@ -287,9 +300,9 @@ func reduceScope(pos string, impcfg *importer.Config, args *[]string) {
|
||||||
// Check that it's possible to load the queried package.
|
// Check that it's possible to load the queried package.
|
||||||
// (e.g. oracle tests contain different 'package' decls in same dir.)
|
// (e.g. oracle tests contain different 'package' decls in same dir.)
|
||||||
// Keep consistent with logic in importer/util.go!
|
// Keep consistent with logic in importer/util.go!
|
||||||
ctxt2 := *impcfg.Build
|
cfg2 := *conf.Build
|
||||||
ctxt2.CgoEnabled = false
|
cfg2.CgoEnabled = false
|
||||||
bp, err := ctxt2.Import(importPath, "", 0)
|
bp, err := cfg2.Import(importPath, "", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return // no files for package
|
return // no files for package
|
||||||
}
|
}
|
||||||
|
@ -302,59 +315,50 @@ func reduceScope(pos string, impcfg *importer.Config, args *[]string) {
|
||||||
// return // not found
|
// return // not found
|
||||||
// found:
|
// found:
|
||||||
|
|
||||||
impcfg.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
|
conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath }
|
||||||
*args = []string{importPath}
|
|
||||||
|
// Ignore packages specified on command line.
|
||||||
|
conf.CreatePkgs = nil
|
||||||
|
conf.ImportPkgs = nil
|
||||||
|
|
||||||
|
// Instead load just the one containing the query position
|
||||||
|
// (and possibly its corresponding tests/production code).
|
||||||
|
// TODO(adonovan): set 'augment' based on which file list
|
||||||
|
// contains
|
||||||
|
_ = conf.ImportWithTests(importPath) // ignore error
|
||||||
}
|
}
|
||||||
|
|
||||||
// New constructs a new Oracle that can be used for a sequence of queries.
|
// New constructs a new Oracle that can be used for a sequence of queries.
|
||||||
//
|
//
|
||||||
// imp will be used to load source code for imported packages.
|
// iprog specifies the program to analyze.
|
||||||
// It must not yet have loaded any packages.
|
|
||||||
//
|
|
||||||
// args specify the main package in importer.LoadInitialPackages syntax.
|
|
||||||
//
|
|
||||||
// ptalog is the (optional) pointer-analysis log file.
|
// ptalog is the (optional) pointer-analysis log file.
|
||||||
// reflection determines whether to model reflection soundly (currently slow).
|
// reflection determines whether to model reflection soundly (currently slow).
|
||||||
//
|
//
|
||||||
func New(imp *importer.Importer, args []string, ptalog io.Writer, reflection bool) (*Oracle, error) {
|
func New(iprog *importer.Program, ptalog io.Writer, reflection bool) (*Oracle, error) {
|
||||||
return newOracle(imp, args, ptalog, needAll, reflection)
|
return newOracle(iprog, ptalog, needAll, reflection)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) {
|
func newOracle(iprog *importer.Program, ptalog io.Writer, needs int, reflection bool) (*Oracle, error) {
|
||||||
o := &Oracle{fset: imp.Fset}
|
o := &Oracle{fset: iprog.Fset}
|
||||||
|
|
||||||
// Load/parse/type-check program from args.
|
|
||||||
initialPkgInfos, args, err := imp.LoadInitialPackages(args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err // I/O or parser error
|
|
||||||
}
|
|
||||||
if len(args) > 0 {
|
|
||||||
return nil, fmt.Errorf("surplus arguments: %q", args)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retain type info for all ASTs in the program.
|
// Retain type info for all ASTs in the program.
|
||||||
if needs&needRetainTypeInfo != 0 {
|
if needs&needRetainTypeInfo != 0 {
|
||||||
m := make(map[*types.Package]*importer.PackageInfo)
|
o.typeInfo = iprog.AllPackages
|
||||||
for _, p := range imp.AllPackages() {
|
|
||||||
m[p.Pkg] = p
|
|
||||||
}
|
|
||||||
o.typeInfo = m
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create SSA package for the initial packages and their dependencies.
|
// Create SSA package for the initial packages and their dependencies.
|
||||||
if needs&needSSA != 0 {
|
if needs&needSSA != 0 {
|
||||||
prog := ssa.NewProgram(o.fset, 0)
|
var mode ssa.BuilderMode
|
||||||
|
if needs&needSSADebug != 0 {
|
||||||
// Create SSA packages.
|
mode |= ssa.GlobalDebug
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
prog := ssa.Create(iprog, mode)
|
||||||
|
|
||||||
// For each initial package (specified on the command line),
|
// For each initial package (specified on the command line),
|
||||||
// if it has a main function, analyze that,
|
// if it has a main function, analyze that,
|
||||||
// otherwise analyze its tests, if any.
|
// otherwise analyze its tests, if any.
|
||||||
var testPkgs, mains []*ssa.Package
|
var testPkgs, mains []*ssa.Package
|
||||||
for _, info := range initialPkgInfos {
|
for _, info := range iprog.InitialPackages() {
|
||||||
initialPkg := prog.Package(info.Pkg)
|
initialPkg := prog.Package(info.Pkg)
|
||||||
|
|
||||||
// Add package to the pointer analysis scope.
|
// Add package to the pointer analysis scope.
|
||||||
|
@ -376,12 +380,6 @@ func newOracle(imp *importer.Importer, args []string, ptalog io.Writer, needs in
|
||||||
o.ptaConfig.Reflection = reflection
|
o.ptaConfig.Reflection = reflection
|
||||||
o.ptaConfig.Mains = mains
|
o.ptaConfig.Mains = mains
|
||||||
|
|
||||||
if needs&needSSADebug != 0 {
|
|
||||||
for _, pkg := range prog.AllPackages() {
|
|
||||||
pkg.SetDebugMode(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
o.prog = prog
|
o.prog = prog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,23 +421,23 @@ func (o *Oracle) query(minfo *modeInfo, qpos *QueryPos) (*Result, error) {
|
||||||
// this is appropriate for queries that allow fairly arbitrary syntax,
|
// this is appropriate for queries that allow fairly arbitrary syntax,
|
||||||
// e.g. "describe".
|
// e.g. "describe".
|
||||||
//
|
//
|
||||||
func ParseQueryPos(imp *importer.Importer, posFlag string, needExact bool) (*QueryPos, error) {
|
func ParseQueryPos(iprog *importer.Program, posFlag string, needExact bool) (*QueryPos, error) {
|
||||||
filename, startOffset, endOffset, err := parsePosFlag(posFlag)
|
filename, startOffset, endOffset, err := parsePosFlag(posFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
start, end, err := findQueryPos(imp.Fset, filename, startOffset, endOffset)
|
start, end, err := findQueryPos(iprog.Fset, filename, startOffset, endOffset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
info, path, exact := imp.PathEnclosingInterval(start, end)
|
info, path, exact := iprog.PathEnclosingInterval(start, end)
|
||||||
if path == nil {
|
if path == nil {
|
||||||
return nil, fmt.Errorf("no syntax here")
|
return nil, fmt.Errorf("no syntax here")
|
||||||
}
|
}
|
||||||
if needExact && !exact {
|
if needExact && !exact {
|
||||||
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
|
return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0]))
|
||||||
}
|
}
|
||||||
return &QueryPos{imp.Fset, start, end, path, exact, info}, nil
|
return &QueryPos{iprog.Fset, start, end, path, exact, info}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteTo writes the oracle query result res to out in a compiler diagnostic format.
|
// WriteTo writes the oracle query result res to out in a compiler diagnostic format.
|
||||||
|
|
|
@ -260,24 +260,29 @@ func TestMultipleQueries(t *testing.T) {
|
||||||
// Importer
|
// Importer
|
||||||
var buildContext = build.Default
|
var buildContext = build.Default
|
||||||
buildContext.GOPATH = "testdata"
|
buildContext.GOPATH = "testdata"
|
||||||
imp := importer.New(&importer.Config{Build: &buildContext})
|
conf := importer.Config{Build: &buildContext}
|
||||||
|
filename := "testdata/src/main/multi.go"
|
||||||
|
conf.CreateFromFilenames(filename)
|
||||||
|
iprog, err := conf.Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Oracle
|
// Oracle
|
||||||
filename := "testdata/src/main/multi.go"
|
o, err := oracle.New(iprog, nil, true)
|
||||||
o, err := oracle.New(imp, []string{filename}, nil, true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("oracle.New failed: %s", err)
|
t.Fatalf("oracle.New failed: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryPos
|
// QueryPos
|
||||||
pos := filename + ":#54,#58"
|
pos := filename + ":#54,#58"
|
||||||
qpos, err := oracle.ParseQueryPos(imp, pos, true)
|
qpos, err := oracle.ParseQueryPos(iprog, pos, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("oracle.ParseQueryPos(%q) failed: %s", pos, err)
|
t.Fatalf("oracle.ParseQueryPos(%q) failed: %s", pos, err)
|
||||||
}
|
}
|
||||||
// SSA is built and we have the QueryPos.
|
// SSA is built and we have the QueryPos.
|
||||||
// Release the other ASTs and type info to the GC.
|
// Release the other ASTs and type info to the GC.
|
||||||
imp = nil
|
iprog = nil
|
||||||
|
|
||||||
// Run different query modes on same scope and selection.
|
// Run different query modes on same scope and selection.
|
||||||
out := new(bytes.Buffer)
|
out := new(bytes.Buffer)
|
||||||
|
|
|
@ -6,7 +6,6 @@ package pointer_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/parser"
|
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"code.google.com/p/go.tools/call"
|
"code.google.com/p/go.tools/call"
|
||||||
|
@ -40,26 +39,27 @@ func main() {
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
// Construct an importer.
|
// Construct an importer.
|
||||||
imp := importer.New(&importer.Config{SourceImports: true})
|
conf := importer.Config{SourceImports: true}
|
||||||
|
|
||||||
// Parse the input file.
|
// Parse the input file.
|
||||||
file, err := parser.ParseFile(imp.Fset, "myprog.go", myprog, 0)
|
file, err := conf.ParseFile("myprog.go", myprog, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Print(err) // parse error
|
fmt.Print(err) // parse error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create single-file main package and import its dependencies.
|
// Create single-file main package and import its dependencies.
|
||||||
mainInfo := imp.CreatePackage("main", file)
|
conf.CreateFromFiles(file)
|
||||||
|
|
||||||
// Create SSA-form program representation.
|
iprog, err := conf.Load()
|
||||||
var mode ssa.BuilderMode
|
if err != nil {
|
||||||
prog := ssa.NewProgram(imp.Fset, mode)
|
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
|
||||||
fmt.Print(err) // type error in some package
|
fmt.Print(err) // type error in some package
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mainPkg := prog.Package(mainInfo.Pkg)
|
|
||||||
|
// Create SSA-form program representation.
|
||||||
|
prog := ssa.Create(iprog, 0)
|
||||||
|
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||||
|
|
||||||
// Build SSA code for bodies of all functions in the whole program.
|
// Build SSA code for bodies of all functions in the whole program.
|
||||||
prog.BuildAll()
|
prog.BuildAll()
|
||||||
|
|
|
@ -151,30 +151,28 @@ func findProbe(prog *ssa.Program, probes map[*ssa.CallCommon]pointer.Pointer, e
|
||||||
}
|
}
|
||||||
|
|
||||||
func doOneInput(input, filename string) bool {
|
func doOneInput(input, filename string) bool {
|
||||||
impctx := &importer.Config{SourceImports: true}
|
conf := importer.Config{SourceImports: true}
|
||||||
imp := importer.New(impctx)
|
|
||||||
|
|
||||||
// Parsing.
|
// Parsing.
|
||||||
f, err := parser.ParseFile(imp.Fset, filename, input, 0)
|
f, err := conf.ParseFile(filename, input, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO(adonovan): err is a scanner error list;
|
|
||||||
// display all errors not just first?
|
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create single-file main package and import its dependencies.
|
// Create single-file main package and import its dependencies.
|
||||||
info := imp.CreatePackage("main", f)
|
conf.CreateFromFiles(f)
|
||||||
|
iprog, err := conf.Load()
|
||||||
// SSA creation + building.
|
if err != nil {
|
||||||
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SSA creation + building.
|
||||||
|
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
|
||||||
prog.BuildAll()
|
prog.BuildAll()
|
||||||
|
|
||||||
mainpkg := prog.Package(info.Pkg)
|
mainpkg := prog.Package(iprog.Created[0].Pkg)
|
||||||
ptrmain := mainpkg // main package for the pointer analysis
|
ptrmain := mainpkg // main package for the pointer analysis
|
||||||
if mainpkg.Func("main") == nil {
|
if mainpkg.Func("main") == nil {
|
||||||
// No main function; assume it's a test.
|
// No main function; assume it's a test.
|
||||||
|
@ -228,7 +226,7 @@ func doOneInput(input, filename string) bool {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mainFileScope := mainpkg.Object.Scope().Child(0)
|
mainFileScope := mainpkg.Object.Scope().Child(0)
|
||||||
t, _, err = types.EvalNode(imp.Fset, texpr, mainpkg.Object, mainFileScope)
|
t, _, err = types.EvalNode(prog.Fset, texpr, mainpkg.Object, mainFileScope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ok = false
|
ok = false
|
||||||
// Don't print err since its location is bad.
|
// Don't print err since its location is bad.
|
||||||
|
|
|
@ -11,7 +11,7 @@ import "testing"
|
||||||
|
|
||||||
func log(f func(*testing.T)) {
|
func log(f func(*testing.T)) {
|
||||||
// The PTS of f is the set of called tests. TestingQuux is not present.
|
// The PTS of f is the set of called tests. TestingQuux is not present.
|
||||||
print(f) // @pointsto main.Test | main.TestFoo
|
print(f) // @pointsto a.Test | a.TestFoo
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
func Test(t *testing.T) {
|
||||||
|
@ -36,7 +36,7 @@ func ExampleBar() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Excludes TestingQuux.
|
// Excludes TestingQuux.
|
||||||
// @calls testing.tRunner -> main.Test
|
// @calls testing.tRunner -> a.Test
|
||||||
// @calls testing.tRunner -> main.TestFoo
|
// @calls testing.tRunner -> a.TestFoo
|
||||||
// @calls testing.runExample -> main.ExampleBar
|
// @calls testing.runExample -> a.ExampleBar
|
||||||
// @calls (*testing.B).runN -> main.BenchmarkFoo
|
// @calls (*testing.B).runN -> a.BenchmarkFoo
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
package ssa_test
|
package ssa_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/parser"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -40,22 +39,24 @@ func main() {
|
||||||
w.Write(nil) // interface invoke of external declared method
|
w.Write(nil) // interface invoke of external declared method
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
imp := importer.New(&importer.Config{})
|
|
||||||
|
|
||||||
f, err := parser.ParseFile(imp.Fset, "<input>", test, 0)
|
// Create a single-file main package.
|
||||||
|
var conf importer.Config
|
||||||
|
f, err := conf.ParseFile("<input>", test, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conf.CreateFromFiles(f)
|
||||||
|
|
||||||
|
iprog, err := conf.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mainInfo := imp.CreatePackage("main", f)
|
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
|
||||||
|
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||||
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mainPkg := prog.Package(mainInfo.Pkg)
|
|
||||||
mainPkg.Build()
|
mainPkg.Build()
|
||||||
|
|
||||||
// The main package, its direct and indirect dependencies are loaded.
|
// The main package, its direct and indirect dependencies are loaded.
|
||||||
|
@ -204,21 +205,22 @@ func TestTypesWithMethodSets(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
imp := importer.New(&importer.Config{})
|
// Create a single-file main package.
|
||||||
|
var conf importer.Config
|
||||||
f, err := parser.ParseFile(imp.Fset, "<input>", test.input, 0)
|
f, err := conf.ParseFile("<input>", test.input, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("test %d: %s", i, err)
|
t.Errorf("test %d: %s", i, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
conf.CreateFromFiles(f)
|
||||||
|
|
||||||
mainInfo := imp.CreatePackage("p", f)
|
iprog, err := conf.Load()
|
||||||
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
if err != nil {
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
t.Errorf("test %d: Load: %s", i, err)
|
||||||
t.Errorf("test %d: %s", i, err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mainPkg := prog.Package(mainInfo.Pkg)
|
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
|
||||||
|
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||||
prog.BuildAll()
|
prog.BuildAll()
|
||||||
|
|
||||||
var typstrs []string
|
var typstrs []string
|
||||||
|
|
|
@ -8,11 +8,9 @@ package ssa
|
||||||
// See builder.go for explanation.
|
// See builder.go for explanation.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.google.com/p/go.tools/go/types"
|
"code.google.com/p/go.tools/go/types"
|
||||||
"code.google.com/p/go.tools/importer"
|
"code.google.com/p/go.tools/importer"
|
||||||
|
@ -28,25 +26,32 @@ const (
|
||||||
SanityCheckFunctions // Perform sanity checking of function bodies
|
SanityCheckFunctions // Perform sanity checking of function bodies
|
||||||
NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers
|
NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers
|
||||||
BuildSerially // Build packages serially, not in parallel.
|
BuildSerially // Build packages serially, not in parallel.
|
||||||
|
GlobalDebug // Enable debug info for all packages
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewProgram returns a new SSA Program initially containing no
|
// Create returns a new SSA Program, creating all packages and all
|
||||||
// packages.
|
// their members.
|
||||||
//
|
//
|
||||||
// fset specifies the mapping from token positions to source location
|
// Code for bodies of functions is not built until Build() is called
|
||||||
// that will be used by all ASTs of this program.
|
// on the result.
|
||||||
//
|
//
|
||||||
// mode controls diagnostics and checking during SSA construction.
|
// mode controls diagnostics and checking during SSA construction.
|
||||||
//
|
//
|
||||||
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
|
func Create(iprog *importer.Program, mode BuilderMode) *Program {
|
||||||
return &Program{
|
prog := &Program{
|
||||||
Fset: fset,
|
Fset: iprog.Fset,
|
||||||
imported: make(map[string]*Package),
|
imported: make(map[string]*Package),
|
||||||
packages: make(map[*types.Package]*Package),
|
packages: make(map[*types.Package]*Package),
|
||||||
boundMethodWrappers: make(map[*types.Func]*Function),
|
boundMethodWrappers: make(map[*types.Func]*Function),
|
||||||
ifaceMethodWrappers: make(map[*types.Func]*Function),
|
ifaceMethodWrappers: make(map[*types.Func]*Function),
|
||||||
mode: mode,
|
mode: mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, info := range iprog.AllPackages {
|
||||||
|
prog.CreatePackage(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
return prog
|
||||||
}
|
}
|
||||||
|
|
||||||
// memberFromObject populates package pkg with a member for the
|
// memberFromObject populates package pkg with a member for the
|
||||||
|
@ -166,9 +171,6 @@ func membersFromDecl(pkg *Package, decl ast.Decl) {
|
||||||
// until a subsequent call to Package.Build().
|
// until a subsequent call to Package.Build().
|
||||||
//
|
//
|
||||||
func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
|
func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
|
||||||
if info.Err != nil {
|
|
||||||
panic(fmt.Sprintf("package %s has errors: %s", info, info.Err))
|
|
||||||
}
|
|
||||||
if p := prog.packages[info.Pkg]; p != nil {
|
if p := prog.packages[info.Pkg]; p != nil {
|
||||||
return p // already loaded
|
return p // already loaded
|
||||||
}
|
}
|
||||||
|
@ -225,6 +227,10 @@ func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
|
||||||
}
|
}
|
||||||
p.Members[initguard.Name()] = initguard
|
p.Members[initguard.Name()] = initguard
|
||||||
|
|
||||||
|
if prog.mode&GlobalDebug != 0 {
|
||||||
|
p.SetDebugMode(true)
|
||||||
|
}
|
||||||
|
|
||||||
if prog.mode&LogPackages != 0 {
|
if prog.mode&LogPackages != 0 {
|
||||||
p.DumpTo(os.Stderr)
|
p.DumpTo(os.Stderr)
|
||||||
}
|
}
|
||||||
|
@ -241,40 +247,6 @@ func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePackages creates SSA Packages for all error-free packages
|
|
||||||
// loaded by the specified Importer.
|
|
||||||
//
|
|
||||||
// If all packages were error-free, it is safe to call
|
|
||||||
// prog.BuildAll(), and nil is returned. Otherwise an error is
|
|
||||||
// returned.
|
|
||||||
//
|
|
||||||
func (prog *Program) CreatePackages(imp *importer.Importer) error {
|
|
||||||
var errpkgs []string
|
|
||||||
|
|
||||||
// Create source packages and directly imported packages.
|
|
||||||
for _, info := range imp.AllPackages() {
|
|
||||||
if info.Err != nil {
|
|
||||||
errpkgs = append(errpkgs, info.Pkg.Path())
|
|
||||||
} else {
|
|
||||||
prog.CreatePackage(info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create indirectly imported packages.
|
|
||||||
for _, obj := range imp.Config.TypeChecker.Packages {
|
|
||||||
prog.CreatePackage(&importer.PackageInfo{
|
|
||||||
Pkg: obj,
|
|
||||||
Importable: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if errpkgs != nil {
|
|
||||||
return fmt.Errorf("couldn't create these SSA packages due to type errors: %s",
|
|
||||||
strings.Join(errpkgs, ", "))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AllPackages returns a new slice containing all packages in the
|
// AllPackages returns a new slice containing all packages in the
|
||||||
// program prog in unspecified order.
|
// program prog in unspecified order.
|
||||||
//
|
//
|
||||||
|
|
16
ssa/doc.go
16
ssa/doc.go
|
@ -23,17 +23,11 @@
|
||||||
// primitives in the future to facilitate constant-time dispatch of
|
// primitives in the future to facilitate constant-time dispatch of
|
||||||
// switch statements, for example.
|
// switch statements, for example.
|
||||||
//
|
//
|
||||||
// Builder encapsulates the tasks of type-checking (using go/types)
|
// To construct an SSA-form program, call ssa.Create on an
|
||||||
// abstract syntax trees (as defined by go/ast) for the source files
|
// importer.Program, a set of type-checked packages created from
|
||||||
// comprising a Go program, and the conversion of each function from
|
// parsed Go source files. The resulting ssa.Program contains all the
|
||||||
// Go ASTs to the SSA representation.
|
// packages and their members, but SSA code is not created for
|
||||||
//
|
// function bodies until a subsequent call to (*Package).Build.
|
||||||
// By supplying an instance of the SourceLocator function prototype,
|
|
||||||
// clients may control how the builder locates, loads and parses Go
|
|
||||||
// sources files for imported packages. This package provides
|
|
||||||
// MakeGoBuildLoader, which creates a loader that uses go/build to
|
|
||||||
// locate packages in the Go source distribution, and go/parser to
|
|
||||||
// parse them.
|
|
||||||
//
|
//
|
||||||
// The builder initially builds a naive SSA form in which all local
|
// The builder initially builds a naive SSA form in which all local
|
||||||
// variables are addresses of stack locations with explicit loads and
|
// variables are addresses of stack locations with explicit loads and
|
||||||
|
|
|
@ -6,7 +6,6 @@ package ssa_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/parser"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"code.google.com/p/go.tools/importer"
|
"code.google.com/p/go.tools/importer"
|
||||||
|
@ -41,27 +40,28 @@ func main() {
|
||||||
fmt.Println(message)
|
fmt.Println(message)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
// Construct an importer.
|
var conf importer.Config
|
||||||
imp := importer.New(&importer.Config{})
|
|
||||||
|
|
||||||
// Parse the input file.
|
// Parse the input file.
|
||||||
file, err := parser.ParseFile(imp.Fset, "hello.go", hello, 0)
|
file, err := conf.ParseFile("hello.go", hello, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Print(err) // parse error
|
fmt.Print(err) // parse error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create single-file main package and import its dependencies.
|
// Create single-file main package.
|
||||||
mainInfo := imp.CreatePackage("main", file)
|
conf.CreateFromFiles(file)
|
||||||
|
|
||||||
// Create SSA-form program representation.
|
// Load the main package and its dependencies.
|
||||||
var mode ssa.BuilderMode
|
iprog, err := conf.Load()
|
||||||
prog := ssa.NewProgram(imp.Fset, mode)
|
if err != nil {
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
|
||||||
fmt.Print(err) // type error in some package
|
fmt.Print(err) // type error in some package
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mainPkg := prog.Package(mainInfo.Pkg)
|
|
||||||
|
// Create SSA-form program representation.
|
||||||
|
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
|
||||||
|
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||||
|
|
||||||
// Print out the package.
|
// Print out the package.
|
||||||
mainPkg.DumpTo(os.Stdout)
|
mainPkg.DumpTo(os.Stdout)
|
||||||
|
|
|
@ -168,16 +168,16 @@ func run(t *testing.T, dir, input string, success successPredicate) bool {
|
||||||
inputs = append(inputs, dir+i)
|
inputs = append(inputs, dir+i)
|
||||||
}
|
}
|
||||||
|
|
||||||
imp := importer.New(&importer.Config{SourceImports: true})
|
conf := importer.Config{SourceImports: true}
|
||||||
// TODO(adonovan): use LoadInitialPackages, then un-export ParseFiles.
|
// TODO(adonovan): add the following packages' tests, which pass:
|
||||||
// Then add the following packages' tests, which pass:
|
|
||||||
// "flag", "unicode", "unicode/utf8", "testing", "log", "path".
|
// "flag", "unicode", "unicode/utf8", "testing", "log", "path".
|
||||||
files, err := importer.ParseFiles(imp.Fset, ".", inputs...)
|
if err := conf.CreateFromFilenames(inputs...); err != nil {
|
||||||
if err != nil {
|
t.Errorf("CreateFromFilenames(%s) failed: %s", inputs, err)
|
||||||
t.Errorf("ssa.ParseFiles(%s) failed: %s", inputs, err.Error())
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conf.Import("runtime")
|
||||||
|
|
||||||
// Print a helpful hint if we don't make it to the end.
|
// Print a helpful hint if we don't make it to the end.
|
||||||
var hint string
|
var hint string
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -192,20 +192,17 @@ func run(t *testing.T, dir, input string, success successPredicate) bool {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=CFP %s\n", input)
|
hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=CFP %s\n", input)
|
||||||
mainInfo := imp.CreatePackage(files[0].Name.Name, files...)
|
|
||||||
|
|
||||||
if _, err := imp.ImportPackage("runtime"); err != nil {
|
iprog, err := conf.Load()
|
||||||
t.Errorf("ImportPackage(runtime) failed: %s", err)
|
if err != nil {
|
||||||
}
|
t.Errorf("conf.Load(%s) failed: %s", inputs, err)
|
||||||
|
|
||||||
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
|
||||||
t.Errorf("CreatePackages failed: %s", err)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
|
||||||
prog.BuildAll()
|
prog.BuildAll()
|
||||||
|
|
||||||
mainPkg := prog.Package(mainInfo.Pkg)
|
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||||
if mainPkg.Func("main") == nil {
|
if mainPkg.Func("main") == nil {
|
||||||
testmainPkg := prog.CreateTestMainPackage(mainPkg)
|
testmainPkg := prog.CreateTestMainPackage(mainPkg)
|
||||||
if testmainPkg == nil {
|
if testmainPkg == nil {
|
||||||
|
@ -321,17 +318,16 @@ func TestTestmainPackage(t *testing.T) {
|
||||||
|
|
||||||
// CreateTestMainPackage should return nil if there were no tests.
|
// CreateTestMainPackage should return nil if there were no tests.
|
||||||
func TestNullTestmainPackage(t *testing.T) {
|
func TestNullTestmainPackage(t *testing.T) {
|
||||||
imp := importer.New(&importer.Config{})
|
var conf importer.Config
|
||||||
files, err := importer.ParseFiles(imp.Fset, ".", "testdata/b_test.go")
|
if err := conf.CreateFromFilenames("testdata/b_test.go"); err != nil {
|
||||||
if err != nil {
|
t.Fatalf("ParseFile failed: %s", err)
|
||||||
t.Fatalf("ParseFiles failed: %s", err)
|
|
||||||
}
|
}
|
||||||
mainInfo := imp.CreatePackage("b", files...)
|
iprog, err := conf.Load()
|
||||||
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
if err != nil {
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
|
||||||
t.Fatalf("CreatePackages failed: %s", err)
|
t.Fatalf("CreatePackages failed: %s", err)
|
||||||
}
|
}
|
||||||
mainPkg := prog.Package(mainInfo.Pkg)
|
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
|
||||||
|
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||||
if mainPkg.Func("main") != nil {
|
if mainPkg.Func("main") != nil {
|
||||||
t.Fatalf("unexpected main function")
|
t.Fatalf("unexpected main function")
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,12 +24,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestObjValueLookup(t *testing.T) {
|
func TestObjValueLookup(t *testing.T) {
|
||||||
imp := importer.New(&importer.Config{})
|
var conf importer.Config
|
||||||
f, err := parser.ParseFile(imp.Fset, "testdata/objlookup.go", nil, parser.ParseComments)
|
f, err := conf.ParseFile("testdata/objlookup.go", nil, parser.ParseComments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
conf.CreateFromFiles(f)
|
||||||
|
|
||||||
// Maps each var Ident (represented "name:linenum") to the
|
// Maps each var Ident (represented "name:linenum") to the
|
||||||
// kind of ssa.Value we expect (represented "Constant", "&Alloc").
|
// kind of ssa.Value we expect (represented "Constant", "&Alloc").
|
||||||
|
@ -39,7 +40,7 @@ func TestObjValueLookup(t *testing.T) {
|
||||||
re := regexp.MustCompile(`(\b|&)?(\w*)::(\w*)\b`)
|
re := regexp.MustCompile(`(\b|&)?(\w*)::(\w*)\b`)
|
||||||
for _, c := range f.Comments {
|
for _, c := range f.Comments {
|
||||||
text := c.Text()
|
text := c.Text()
|
||||||
pos := imp.Fset.Position(c.Pos())
|
pos := conf.Fset.Position(c.Pos())
|
||||||
for _, m := range re.FindAllStringSubmatch(text, -1) {
|
for _, m := range re.FindAllStringSubmatch(text, -1) {
|
||||||
key := fmt.Sprintf("%s:%d", m[2], pos.Line)
|
key := fmt.Sprintf("%s:%d", m[2], pos.Line)
|
||||||
value := m[1] + m[3]
|
value := m[1] + m[3]
|
||||||
|
@ -47,13 +48,14 @@ func TestObjValueLookup(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mainInfo := imp.CreatePackage("main", f)
|
iprog, err := conf.Load()
|
||||||
|
if err != nil {
|
||||||
prog := ssa.NewProgram(imp.Fset, 0 /*|ssa.LogFunctions*/)
|
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prog := ssa.Create(iprog, 0 /*|ssa.LogFunctions*/)
|
||||||
|
mainInfo := iprog.Created[0]
|
||||||
mainPkg := prog.Package(mainInfo.Pkg)
|
mainPkg := prog.Package(mainInfo.Pkg)
|
||||||
mainPkg.SetDebugMode(true)
|
mainPkg.SetDebugMode(true)
|
||||||
mainPkg.Build()
|
mainPkg.Build()
|
||||||
|
@ -90,7 +92,7 @@ func TestObjValueLookup(t *testing.T) {
|
||||||
}
|
}
|
||||||
if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok {
|
if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok {
|
||||||
ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos())
|
ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos())
|
||||||
pos := imp.Fset.Position(id.Pos())
|
pos := prog.Fset.Position(id.Pos())
|
||||||
exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
|
exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
|
||||||
if exp == "" {
|
if exp == "" {
|
||||||
t.Errorf("%s: no expectation for var ident %s ", pos, id.Name)
|
t.Errorf("%s: no expectation for var ident %s ", pos, id.Name)
|
||||||
|
@ -186,20 +188,23 @@ func checkVarValue(t *testing.T, prog *ssa.Program, pkg *ssa.Package, ref []ast.
|
||||||
// Ensure that, in debug mode, we can determine the ssa.Value
|
// Ensure that, in debug mode, we can determine the ssa.Value
|
||||||
// corresponding to every ast.Expr.
|
// corresponding to every ast.Expr.
|
||||||
func TestValueForExpr(t *testing.T) {
|
func TestValueForExpr(t *testing.T) {
|
||||||
imp := importer.New(&importer.Config{})
|
var conf importer.Config
|
||||||
f, err := parser.ParseFile(imp.Fset, "testdata/valueforexpr.go", nil, parser.ParseComments)
|
f, err := conf.ParseFile("testdata/valueforexpr.go", nil, parser.ParseComments)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conf.CreateFromFiles(f)
|
||||||
|
|
||||||
|
iprog, err := conf.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mainInfo := imp.CreatePackage("main", f)
|
mainInfo := iprog.Created[0]
|
||||||
|
|
||||||
prog := ssa.NewProgram(imp.Fset, 0)
|
prog := ssa.Create(iprog, 0)
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mainPkg := prog.Package(mainInfo.Pkg)
|
mainPkg := prog.Package(mainInfo.Pkg)
|
||||||
mainPkg.SetDebugMode(true)
|
mainPkg.SetDebugMode(true)
|
||||||
mainPkg.Build()
|
mainPkg.Build()
|
||||||
|
@ -232,7 +237,7 @@ func TestValueForExpr(t *testing.T) {
|
||||||
}
|
}
|
||||||
text = text[1:]
|
text = text[1:]
|
||||||
pos := c.End() + 1
|
pos := c.End() + 1
|
||||||
position := imp.Fset.Position(pos)
|
position := prog.Fset.Position(pos)
|
||||||
var e ast.Expr
|
var e ast.Expr
|
||||||
if target := parenExprByPos[pos]; target == nil {
|
if target := parenExprByPos[pos]; target == nil {
|
||||||
t.Errorf("%s: annotation doesn't precede ParenExpr: %q", position, text)
|
t.Errorf("%s: annotation doesn't precede ParenExpr: %q", position, text)
|
||||||
|
|
|
@ -1170,7 +1170,8 @@ type MapUpdate struct {
|
||||||
// For non-Ident expressions, Object() returns nil.
|
// For non-Ident expressions, Object() returns nil.
|
||||||
//
|
//
|
||||||
// DebugRefs are generated only for functions built with debugging
|
// DebugRefs are generated only for functions built with debugging
|
||||||
// enabled; see Package.SetDebugMode().
|
// enabled; see Package.SetDebugMode() and the GlobalDebug builder
|
||||||
|
// mode flag.
|
||||||
//
|
//
|
||||||
// DebugRefs are not emitted for ast.Idents referring to constants or
|
// DebugRefs are not emitted for ast.Idents referring to constants or
|
||||||
// predeclared identifiers, since they are trivial and numerous.
|
// predeclared identifiers, since they are trivial and numerous.
|
||||||
|
@ -1240,7 +1241,7 @@ type anInstruction struct {
|
||||||
// (b) a *MakeClosure, indicating an immediately applied
|
// (b) a *MakeClosure, indicating an immediately applied
|
||||||
// function literal with free variables.
|
// function literal with free variables.
|
||||||
// (c) a *Builtin, indicating a statically dispatched call
|
// (c) a *Builtin, indicating a statically dispatched call
|
||||||
// to a built-in function. StaticCallee returns nil.
|
// to a built-in function.
|
||||||
// (d) any other value, indicating a dynamically dispatched
|
// (d) any other value, indicating a dynamically dispatched
|
||||||
// function call.
|
// function call.
|
||||||
// StaticCallee returns the identity of the callee in cases
|
// StaticCallee returns the identity of the callee in cases
|
||||||
|
|
|
@ -15,21 +15,22 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSwitches(t *testing.T) {
|
func TestSwitches(t *testing.T) {
|
||||||
imp := importer.New(&importer.Config{})
|
var conf importer.Config
|
||||||
f, err := parser.ParseFile(imp.Fset, "testdata/switches.go", nil, parser.ParseComments)
|
f, err := conf.ParseFile("testdata/switches.go", nil, parser.ParseComments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mainInfo := imp.CreatePackage("main", f)
|
conf.CreateFromFiles(f)
|
||||||
|
iprog, err := conf.Load()
|
||||||
prog := ssa.NewProgram(imp.Fset, 0)
|
if err != nil {
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
mainPkg := prog.Package(mainInfo.Pkg)
|
|
||||||
|
prog := ssa.Create(iprog, 0)
|
||||||
|
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||||
mainPkg.Build()
|
mainPkg.Build()
|
||||||
|
|
||||||
for _, mem := range mainPkg.Members {
|
for _, mem := range mainPkg.Members {
|
||||||
|
|
|
@ -23,8 +23,6 @@ import (
|
||||||
"code.google.com/p/go.tools/ssa/ssautil"
|
"code.google.com/p/go.tools/ssa/ssautil"
|
||||||
)
|
)
|
||||||
|
|
||||||
const debugMode = true // cost: +30% space, +18% time for SSA building
|
|
||||||
|
|
||||||
func allPackages() []string {
|
func allPackages() []string {
|
||||||
var pkgs []string
|
var pkgs []string
|
||||||
root := filepath.Join(runtime.GOROOT(), "src/pkg") + string(os.PathSeparator)
|
root := filepath.Join(runtime.GOROOT(), "src/pkg") + string(os.PathSeparator)
|
||||||
|
@ -54,13 +52,17 @@ func TestStdlib(t *testing.T) {
|
||||||
// Load, parse and type-check the program.
|
// Load, parse and type-check the program.
|
||||||
t0 := time.Now()
|
t0 := time.Now()
|
||||||
|
|
||||||
imp := importer.New(&importer.Config{})
|
var conf importer.Config
|
||||||
|
if _, err := conf.FromArgs(allPackages()); err != nil {
|
||||||
if _, _, err := imp.LoadInitialPackages(allPackages()); err != nil {
|
t.Errorf("FromArgs failed: %s", err)
|
||||||
t.Errorf("LoadInitialPackages failed: %s", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iprog, err := conf.Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Load failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
t1 := time.Now()
|
t1 := time.Now()
|
||||||
|
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
|
@ -69,15 +71,11 @@ func TestStdlib(t *testing.T) {
|
||||||
alloc := memstats.Alloc
|
alloc := memstats.Alloc
|
||||||
|
|
||||||
// Create SSA packages.
|
// Create SSA packages.
|
||||||
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
var mode ssa.BuilderMode
|
||||||
if err := prog.CreatePackages(imp); err != nil {
|
// Comment out these lines during benchmarking. Approx SSA build costs are noted.
|
||||||
t.Errorf("CreatePackages failed: %s", err)
|
mode |= ssa.SanityCheckFunctions // + 2% space, + 4% time
|
||||||
return
|
mode |= ssa.GlobalDebug // +30% space, +18% time
|
||||||
}
|
prog := ssa.Create(iprog, mode)
|
||||||
// Enable debug mode globally.
|
|
||||||
for _, info := range imp.AllPackages() {
|
|
||||||
prog.Package(info.Pkg).SetDebugMode(debugMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
t2 := time.Now()
|
t2 := time.Now()
|
||||||
|
|
||||||
|
@ -105,7 +103,7 @@ func TestStdlib(t *testing.T) {
|
||||||
|
|
||||||
// determine line count
|
// determine line count
|
||||||
var lineCount int
|
var lineCount int
|
||||||
imp.Fset.Iterate(func(f *token.File) bool {
|
prog.Fset.Iterate(func(f *token.File) bool {
|
||||||
lineCount += f.LineCount()
|
lineCount += f.LineCount()
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue