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