From 9c57c19a58835b00c0e3e283952087842242e49b Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Tue, 14 Apr 2015 16:56:02 -0400 Subject: [PATCH] go/loader: eliminate ImportFromBinary option and PackageCreated hook The loader package now loads, parses, and type-checks a whole program from source, and that is all. Also: - simplified loader logic - ssa.Create is gone; use ssautil.CreateProgram. - ssautil.LoadPackage renamed to BuildPackage. It is now independent of go/types' Import hook and the Packages map. - ssadump: -importbin flag removed. The value of this flag was that it caused the tool to print IR for only a single package; this is now the normal behaviour. Fixes #9955 Change-Id: I4571118258ab1a46dccece3241b7dc51401a3acc Reviewed-on: https://go-review.googlesource.com/8953 Reviewed-by: Robert Griesemer --- cmd/callgraph/main.go | 3 +- cmd/ssadump/main.go | 21 +++-- go/callgraph/cha/cha_test.go | 4 +- go/callgraph/rta/rta_test.go | 3 +- go/callgraph/static/static_test.go | 4 +- go/loader/doc.go | 21 +++-- go/loader/loader.go | 142 +++++------------------------ go/loader/source_test.go | 3 +- go/pointer/example_test.go | 3 +- go/pointer/pointer_test.go | 2 +- go/pointer/stdlib_test.go | 2 +- go/ssa/builder_test.go | 64 +++++++------ go/ssa/create.go | 24 ----- go/ssa/example_test.go | 4 +- go/ssa/interp/interp_test.go | 5 +- go/ssa/source_test.go | 5 +- go/ssa/ssautil/load.go | 56 ++++++------ go/ssa/ssautil/load_test.go | 15 ++- go/ssa/ssautil/switch_test.go | 2 +- go/ssa/stdlib_test.go | 2 +- go/ssa/testmain_test.go | 3 +- godoc/analysis/analysis.go | 2 +- oracle/callees.go | 3 +- oracle/callers.go | 3 +- oracle/callstack.go | 3 +- oracle/peers.go | 2 +- oracle/pointsto.go | 3 +- oracle/whicherrs.go | 2 +- 28 files changed, 155 insertions(+), 251 deletions(-) diff --git a/cmd/callgraph/main.go b/cmd/callgraph/main.go index 04113953..8979b5b4 100644 --- a/cmd/callgraph/main.go +++ b/cmd/callgraph/main.go @@ -37,6 +37,7 @@ import ( "golang.org/x/tools/go/loader" "golang.org/x/tools/go/pointer" "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" ) var algoFlag = flag.String("algo", "rta", @@ -173,7 +174,7 @@ func doCallgraph(ctxt *build.Context, algo, format string, tests bool, args []st } // Create and build SSA-form program representation. - prog := ssa.Create(iprog, 0) + prog := ssautil.CreateProgram(iprog, 0) prog.BuildAll() // -- call graph construction ------------------------------------------ diff --git a/cmd/ssadump/main.go b/cmd/ssadump/main.go index a7b2ea7e..588b8240 100644 --- a/cmd/ssadump/main.go +++ b/cmd/ssadump/main.go @@ -16,14 +16,11 @@ import ( "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/interp" + "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/types" ) var ( - importbinFlag = flag.Bool("importbin", false, - "Import binary export data from gc's object files, not source. "+ - "Imported functions will have no bodies.") - modeFlag = ssa.BuilderModeFlag(flag.CommandLine, "build", 0) testFlag = flag.Bool("test", false, "Loads test code (*_test.go) for imported packages.") @@ -78,10 +75,7 @@ func doMain() error { flag.Parse() args := flag.Args() - conf := loader.Config{ - Build: &build.Default, - ImportFromBinary: *importbinFlag, - } + conf := loader.Config{Build: &build.Default} // TODO(adonovan): make go/types choose its default Sizes from // build.Default or a specified *build.Context. var wordSize int64 = 8 @@ -140,11 +134,18 @@ func doMain() error { } // Create and build SSA-form program representation. - prog := ssa.Create(iprog, *modeFlag) - prog.BuildAll() + prog := ssautil.CreateProgram(iprog, *modeFlag) + + // Build and display only the initial packages + // (and synthetic wrappers), unless -run is specified. + for _, info := range iprog.InitialPackages() { + prog.Package(info.Pkg).Build() + } // Run the interpreter. if *runFlag { + prog.BuildAll() + var main *ssa.Package pkgs := prog.AllPackages() if *testFlag { diff --git a/go/callgraph/cha/cha_test.go b/go/callgraph/cha/cha_test.go index 56c7c1f3..e8ddda47 100644 --- a/go/callgraph/cha/cha_test.go +++ b/go/callgraph/cha/cha_test.go @@ -18,7 +18,7 @@ import ( "golang.org/x/tools/go/callgraph" "golang.org/x/tools/go/callgraph/cha" "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/types" ) @@ -72,7 +72,7 @@ func TestCHA(t *testing.T) { continue } - prog := ssa.Create(iprog, 0) + prog := ssautil.CreateProgram(iprog, 0) mainPkg := prog.Package(iprog.Created[0].Pkg) prog.BuildAll() diff --git a/go/callgraph/rta/rta_test.go b/go/callgraph/rta/rta_test.go index 11fa1a57..aef788df 100644 --- a/go/callgraph/rta/rta_test.go +++ b/go/callgraph/rta/rta_test.go @@ -19,6 +19,7 @@ import ( "golang.org/x/tools/go/callgraph/rta" "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/types" ) @@ -76,7 +77,7 @@ func TestRTA(t *testing.T) { continue } - prog := ssa.Create(iprog, 0) + prog := ssautil.CreateProgram(iprog, 0) mainPkg := prog.Package(iprog.Created[0].Pkg) prog.BuildAll() diff --git a/go/callgraph/static/static_test.go b/go/callgraph/static/static_test.go index 5a74ca1a..62297f79 100644 --- a/go/callgraph/static/static_test.go +++ b/go/callgraph/static/static_test.go @@ -14,7 +14,7 @@ import ( "golang.org/x/tools/go/callgraph" "golang.org/x/tools/go/callgraph/static" "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" ) const input = `package P @@ -62,7 +62,7 @@ func TestStatic(t *testing.T) { P := iprog.Created[0].Pkg - prog := ssa.Create(iprog, 0) + prog := ssautil.CreateProgram(iprog, 0) prog.BuildAll() cg := static.CallGraph(prog) diff --git a/go/loader/doc.go b/go/loader/doc.go index c6dcac24..1ff4b15d 100644 --- a/go/loader/doc.go +++ b/go/loader/doc.go @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package loader loads, parses and type-checks packages of Go code -// plus their transitive closure, and retains both the ASTs and the -// derived facts. +// Package loader loads a complete Go program from source code, parsing +// and type-checking the initial packages plus their transitive closure +// of dependencies. The ASTs and the derived facts are retained for +// later use. // // THIS INTERFACE IS EXPERIMENTAL AND IS LIKELY TO CHANGE. // @@ -25,11 +26,11 @@ // // See FromArgsUsage for help. // rest, err := conf.FromArgs(os.Args[1:], wantTests) // -// // Parse the specified files and create an ad-hoc package with path "foo". +// // Parse the specified files and create an ad hoc package with path "foo". // // All files must have the same 'package' declaration. // conf.CreateFromFilenames("foo", "foo.go", "bar.go") // -// // Create an ad-hoc package with path "foo" from +// // Create an ad hoc package with path "foo" from // // the specified already-parsed files. // // All ASTs must have the same 'package' declaration. // conf.CreateFromFiles("foo", parsedFiles) @@ -49,7 +50,7 @@ // // CONCEPTS AND TERMINOLOGY // -// An AD-HOC package is one specified as a set of source files on the +// An AD HOC package is one specified as a set of source files on the // command line. In the simplest case, it may consist of a single file // such as $GOROOT/src/net/http/triv.go. // @@ -61,10 +62,10 @@ // 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 +// 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 +// 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 @@ -125,7 +126,7 @@ package loader // list of files passed to (Checker).Files at once. Many of these lists // are the production code of an importable Go package, so those nodes // are labelled by the package's import path. The remaining nodes are -// ad-hoc packages and lists of in-package *_test.go files that augment +// ad hoc packages and lists of in-package *_test.go files that augment // an importable package; those nodes have no label. // // The edges of the graph represent import statements appearing within a diff --git a/go/loader/loader.go b/go/loader/loader.go index 41df1512..98df7aad 100644 --- a/go/loader/loader.go +++ b/go/loader/loader.go @@ -20,13 +20,13 @@ import ( "time" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/gcimporter" "golang.org/x/tools/go/types" ) const trace = false // show timing info for type-checking -// Config specifies the configuration for a program to load. +// Config specifies the configuration for loading a whole program from +// Go source code. // 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 @@ -42,8 +42,7 @@ type Config struct { // // The supplied IgnoreFuncBodies is not used; the effective // value comes from the TypeCheckFuncBodies func below. - // - // TypeChecker.Packages is lazily initialized during Load. + // The supplied Import function is not used either. TypeChecker types.Config // TypeCheckFuncBodies is a predicate over package import @@ -54,33 +53,6 @@ type Config struct { // checked. TypeCheckFuncBodies func(string) bool - // ImportFromBinary determines whether to satisfy dependencies by - // loading gc export data instead of Go source code. - // - // If false, the entire program---the initial packages and their - // transitive closure of dependencies---will be loaded from - // source, parsed, and type-checked. This is required for - // whole-program analyses such as pointer analysis. - // - // If true, the go/gcimporter mechanism is used instead to read - // the binary export-data files written by the gc toolchain. - // They supply only the types of package-level declarations and - // values of constants, but no code, this option will not yield - // a whole program. It is intended for analyses that perform - // modular analysis of a single package, e.g. traditional - // compilation. - // - // No check is made that the export data files are up-to-date. - // - // The initial packages (CreatePkgs and ImportPkgs) are always - // loaded from Go source, regardless of this flag's setting. - // - // NB: there is a bug when loading multiple initial packages with - // this flag enabled: https://github.com/golang/go/issues/9955. - // - // THIS FEATURE IS DEPRECATED and will be removed shortly (Apr 2015). - ImportFromBinary bool - // If Build is non-nil, it is used to locate source packages. // Otherwise &build.Default is used. // @@ -131,21 +103,6 @@ type Config struct { // // It must be safe to call concurrently from multiple goroutines. FindPackage func(ctxt *build.Context, importPath string) (*build.Package, error) - - // PackageCreated is a hook called when a types.Package - // is created but before it has been populated. - // - // The package's import Path() and Scope() are defined, - // but not its Name() since no package declaration has - // been seen yet. - // - // Clients may use this to insert synthetic items into - // the package scope, for example. - // - // It must be safe to call concurrently from multiple goroutines. - // - // THIS FEATURE IS DEPRECATED and will be removed shortly (Apr 2015). - PackageCreated func(*types.Package) } // A PkgSpec specifies a non-importable package to be created by Load. @@ -158,8 +115,7 @@ type PkgSpec struct { Filenames []string // names of files to be parsed } -// A Program is a Go program loaded from source or binary -// as specified by a Config. +// A Program is a Go program loaded from source as specified by a Config. type Program struct { Fset *token.FileSet // the file set for this program @@ -179,9 +135,8 @@ type Program struct { AllPackages map[*types.Package]*PackageInfo // importMap is the canonical mapping of import paths to - // packages used by the type-checker. - // It contains all Imported initial packages, but not Created - // ones, and all imported dependencies. + // packages. It contains all Imported initial packages, but not + // Created ones, and all imported dependencies. importMap map[string]*types.Package } @@ -284,7 +239,7 @@ func (conf *Config) FromArgs(args []string, xtest bool) ([]string, error) { if len(args) > 0 && strings.HasSuffix(args[0], ".go") { // Assume args is a list of a *.go files - // denoting a single ad-hoc package. + // denoting a single ad hoc package. for _, arg := range args { if !strings.HasSuffix(arg, ".go") { return nil, fmt.Errorf("named files must be .go files: %s", arg) @@ -400,18 +355,12 @@ func (prog *Program) Package(path string) *PackageInfo { // importer holds the working state of the algorithm. type importer struct { conf *Config // the client configuration - prog *Program // resulting program start time.Time // for logging - // This mutex serializes access to prog.ImportMap (aka - // TypeChecker.Packages); we also use it for AllPackages. - // - // The TypeChecker.Packages map is not really used by this - // package, but may be used by the client's Import function, - // and by clients of the returned Program. - typecheckerMu sync.Mutex + progMu sync.Mutex // guards prog + prog *Program // the resulting program - importedMu sync.Mutex + importedMu sync.Mutex // guards imported imported map[string]*importInfo // all imported packages (incl. failures) by import path // import dependency graph: graph[x][y] => x imports y @@ -472,12 +421,6 @@ func (ii *importInfo) Complete(info *PackageInfo, err error) { // 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) - } - // Create a simple default error handler for parse/type errors. if conf.TypeChecker.Error == nil { conf.TypeChecker.Error = func(e error) { fmt.Fprintln(os.Stderr, e) } @@ -508,7 +451,7 @@ func (conf *Config) Load() (*Program, error) { prog := &Program{ Fset: conf.fset(), Imported: make(map[string]*PackageInfo), - importMap: conf.TypeChecker.Packages, + importMap: make(map[string]*types.Package), AllPackages: make(map[*types.Package]*PackageInfo), } @@ -577,7 +520,7 @@ func (conf *Config) Load() (*Program, error) { info.appendError(err) } - // Ad-hoc packages are non-importable, + // Ad hoc packages are non-importable, // so no cycle check is needed. // addFiles loads dependencies in parallel. imp.addFiles(info, files, false) @@ -879,57 +822,19 @@ func (imp *importer) startLoad(path string) *importInfo { ii = &importInfo{path: path} ii.complete.L = &ii.mu imp.imported[path] = ii - - go imp.load(path, ii) + go func() { + ii.Complete(imp.load(path)) + }() } imp.importedMu.Unlock() return ii } -func (imp *importer) load(path string, ii *importInfo) { - var info *PackageInfo - var err error - // Find and create the actual package. - if _, ok := imp.conf.ImportPkgs[path]; ok || !imp.conf.ImportFromBinary { - info, err = imp.loadFromSource(path) - } else { - info, err = imp.importFromBinary(path) - } - ii.Complete(info, 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 - } - imp.typecheckerMu.Lock() - pkg, err := importfn(imp.conf.TypeChecker.Packages, path) - if pkg != nil { - imp.conf.TypeChecker.Packages[path] = pkg - } - imp.typecheckerMu.Unlock() - if err != nil { - return nil, err - } - info := &PackageInfo{Pkg: pkg} - info.Importable = true - imp.typecheckerMu.Lock() - imp.prog.AllPackages[pkg] = info - imp.typecheckerMu.Unlock() - return info, nil -} - -// loadFromSource implements package loading by parsing Go source files +// load implements package loading by parsing Go source files // located by go/build. -// The returned PackageInfo's typeCheck function must be called. // -func (imp *importer) loadFromSource(path string) (*PackageInfo, error) { +func (imp *importer) load(path string) (*PackageInfo, error) { bp, err := imp.conf.FindPackage(imp.conf.build(), path) if err != nil { return nil, err // package not found @@ -943,9 +848,9 @@ func (imp *importer) loadFromSource(path string) (*PackageInfo, error) { imp.addFiles(info, files, true) - imp.typecheckerMu.Lock() - imp.conf.TypeChecker.Packages[path] = info.Pkg - imp.typecheckerMu.Unlock() + imp.progMu.Lock() + imp.prog.importMap[path] = info.Pkg + imp.progMu.Unlock() return info, nil } @@ -985,9 +890,6 @@ func (imp *importer) addFiles(info *PackageInfo, files []*ast.File, cycleCheck b func (imp *importer) newPackageInfo(path string) *PackageInfo { pkg := types.NewPackage(path, "") - if imp.conf.PackageCreated != nil { - imp.conf.PackageCreated(pkg) - } info := &PackageInfo{ Pkg: pkg, Info: types.Info{ @@ -1013,8 +915,8 @@ func (imp *importer) newPackageInfo(path string) *PackageInfo { tc.Error = info.appendError // appendError wraps the user's Error function info.checker = types.NewChecker(&tc, imp.conf.fset(), pkg, &info.Info) - imp.typecheckerMu.Lock() + imp.progMu.Lock() imp.prog.AllPackages[pkg] = info - imp.typecheckerMu.Unlock() + imp.progMu.Unlock() return info } diff --git a/go/loader/source_test.go b/go/loader/source_test.go index f2e06be5..9f6839a7 100644 --- a/go/loader/source_test.go +++ b/go/loader/source_test.go @@ -16,6 +16,7 @@ import ( "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" ) // findInterval parses input and returns the [start, end) positions of @@ -100,7 +101,7 @@ func TestEnclosingFunction(t *testing.T) { t.Error(err) continue } - prog := ssa.Create(iprog, 0) + prog := ssautil.CreateProgram(iprog, 0) pkg := prog.Package(iprog.Created[0].Pkg) pkg.Build() diff --git a/go/pointer/example_test.go b/go/pointer/example_test.go index 5f2e9402..ba70557b 100644 --- a/go/pointer/example_test.go +++ b/go/pointer/example_test.go @@ -12,6 +12,7 @@ import ( "golang.org/x/tools/go/loader" "golang.org/x/tools/go/pointer" "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" ) // This program demonstrates how to use the pointer analysis to @@ -61,7 +62,7 @@ func main() { } // Create SSA-form program representation. - prog := ssa.Create(iprog, 0) + prog := ssautil.CreateProgram(iprog, 0) mainPkg := prog.Package(iprog.Created[0].Pkg) // Build SSA code for bodies of all functions in the whole program. diff --git a/go/pointer/pointer_test.go b/go/pointer/pointer_test.go index 1daf9c3d..2ea7ef6c 100644 --- a/go/pointer/pointer_test.go +++ b/go/pointer/pointer_test.go @@ -172,7 +172,7 @@ func doOneInput(input, filename string) bool { mainPkgInfo := iprog.Created[0].Pkg // SSA creation + building. - prog := ssa.Create(iprog, ssa.SanityCheckFunctions) + prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions) prog.BuildAll() mainpkg := prog.Package(mainPkgInfo) diff --git a/go/pointer/stdlib_test.go b/go/pointer/stdlib_test.go index 63652794..21afdf27 100644 --- a/go/pointer/stdlib_test.go +++ b/go/pointer/stdlib_test.go @@ -47,7 +47,7 @@ func TestStdlib(t *testing.T) { } // Create SSA packages. - prog := ssa.Create(iprog, 0) + prog := ssautil.CreateProgram(iprog, 0) prog.BuildAll() numPkgs := len(prog.AllPackages()) diff --git a/go/ssa/builder_test.go b/go/ssa/builder_test.go index cb30ef6f..e7ac8381 100644 --- a/go/ssa/builder_test.go +++ b/go/ssa/builder_test.go @@ -6,6 +6,9 @@ package ssa_test import ( "bytes" + "go/ast" + "go/parser" + "go/token" "reflect" "sort" "strings" @@ -15,14 +18,16 @@ import ( "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/types" + + _ "golang.org/x/tools/go/gcimporter" ) func isEmpty(f *ssa.Function) bool { return f.Blocks == nil } // Tests that programs partially loaded from gc object files contain // functions with no code for the external portions, but are otherwise ok. -func TestImportFromBinary(t *testing.T) { - test := ` +func TestBuildPackage(t *testing.T) { + input := ` package main import ( @@ -42,24 +47,22 @@ func main() { } ` - // Create a single-file main package. - conf := loader.Config{ImportFromBinary: true} - f, err := conf.ParseFile("", test) - if err != nil { - t.Error(err) - return - } - conf.CreateFromFiles("main", f) - - iprog, err := conf.Load() + // Parse the file. + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "input.go", input, 0) if err != nil { t.Error(err) return } - prog := ssa.Create(iprog, ssa.SanityCheckFunctions) - mainPkg := prog.Package(iprog.Created[0].Pkg) - mainPkg.Build() + // Build an SSA program from the parsed file. + // Load its dependencies from gc binary export data. + mainPkg, _, err := ssautil.BuildPackage(new(types.Config), fset, + types.NewPackage("main", ""), []*ast.File{f}, ssa.SanityCheckFunctions) + if err != nil { + t.Error(err) + return + } // The main package, its direct and indirect dependencies are loaded. deps := []string{ @@ -69,6 +72,7 @@ func main() { "errors", "fmt", "os", "runtime", } + prog := mainPkg.Prog all := prog.AllPackages() if len(all) <= len(deps) { t.Errorf("unexpected set of loaded packages: %q", all) @@ -211,25 +215,25 @@ func TestRuntimeTypes(t *testing.T) { }, } for _, test := range tests { - // Create a single-file main package. - conf := loader.Config{ImportFromBinary: true} - f, err := conf.ParseFile("", test.input) + // Parse the file. + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "input.go", test.input, 0) if err != nil { t.Errorf("test %q: %s", test.input[:15], err) continue } - conf.CreateFromFiles("p", f) - iprog, err := conf.Load() + // Create a single-file main package. + // Load dependencies from gc binary export data. + ssapkg, _, err := ssautil.BuildPackage(new(types.Config), fset, + types.NewPackage("p", ""), []*ast.File{f}, ssa.SanityCheckFunctions) if err != nil { - t.Errorf("test 'package %s': Load: %s", f.Name.Name, err) + t.Errorf("test %q: %s", test.input[:15], err) continue } - prog := ssa.Create(iprog, ssa.SanityCheckFunctions) - prog.BuildAll() var typstrs []string - for _, T := range prog.RuntimeTypes() { + for _, T := range ssapkg.Prog.RuntimeTypes() { typstrs = append(typstrs, T.String()) } sort.Strings(typstrs) @@ -241,7 +245,7 @@ func TestRuntimeTypes(t *testing.T) { } } -// Tests that synthesized init functions are correctly formed. +// TestInit tests that synthesized init functions are correctly formed. // Bare init functions omit calls to dependent init functions and the use of // an init guard. They are useful in cases where the client uses a different // calling convention for init functions, or cases where it is easier for a @@ -292,13 +296,13 @@ func init(): } conf.CreateFromFiles(f.Name.Name, f) - iprog, err := conf.Load() + lprog, err := conf.Load() if err != nil { t.Errorf("test 'package %s': Load: %s", f.Name.Name, err) continue } - prog := ssa.Create(iprog, test.mode) - mainPkg := prog.Package(iprog.Created[0].Pkg) + prog := ssautil.CreateProgram(lprog, test.mode) + mainPkg := prog.Package(lprog.Created[0].Pkg) prog.BuildAll() initFunc := mainPkg.Func("init") if initFunc == nil { @@ -363,13 +367,13 @@ var ( conf.CreateFromFiles(f.Name.Name, f) // Load - iprog, err := conf.Load() + lprog, err := conf.Load() if err != nil { t.Fatalf("Load: %v", err) } // Create and build SSA - prog := ssa.Create(iprog, 0) + prog := ssautil.CreateProgram(lprog, 0) prog.BuildAll() // Enumerate reachable synthetic functions diff --git a/go/ssa/create.go b/go/ssa/create.go index c13facee..88226aeb 100644 --- a/go/ssa/create.go +++ b/go/ssa/create.go @@ -14,7 +14,6 @@ import ( "os" "sync" - "golang.org/x/tools/go/loader" "golang.org/x/tools/go/types" "golang.org/x/tools/go/types/typeutil" ) @@ -40,29 +39,6 @@ func NewProgram(fset *token.FileSet, mode BuilderMode) *Program { return prog } -// Create returns a new SSA Program. An SSA Package is created for -// each transitively error-free package of lprog. -// -// Code for bodies of functions is not built until BuildAll() is called -// on the result. -// -// mode controls diagnostics and checking during SSA construction. -// -// TODO(adonovan): move this to ssautil and breaking the go/ssa -> -// go/loader dependency. -// -func Create(lprog *loader.Program, mode BuilderMode) *Program { - prog := NewProgram(lprog.Fset, mode) - - for _, info := range lprog.AllPackages { - if info.TransitivelyErrorFree { - prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable) - } - } - - return prog -} - // memberFromObject populates package pkg with a member for the // typechecker object obj. // diff --git a/go/ssa/example_test.go b/go/ssa/example_test.go index 9e6bcfda..3e095b87 100644 --- a/go/ssa/example_test.go +++ b/go/ssa/example_test.go @@ -48,7 +48,7 @@ func main() { // with similar functionality. It is located at // golang.org/x/tools/cmd/ssadump. // -func ExampleLoadPackage() { +func ExampleBuildPackage() { // Parse the source files. fset := token.NewFileSet() f, err := parser.ParseFile(fset, "hello.go", hello, parser.ParseComments) @@ -63,7 +63,7 @@ func ExampleLoadPackage() { // Type-check the package, load dependencies. // Create and build the SSA program. - hello, _, err := ssautil.LoadPackage( + hello, _, err := ssautil.BuildPackage( new(types.Config), fset, pkg, files, ssa.SanityCheckFunctions) if err != nil { fmt.Print(err) // type error in some package diff --git a/go/ssa/interp/interp_test.go b/go/ssa/interp/interp_test.go index 569d9eaa..93e1097d 100644 --- a/go/ssa/interp/interp_test.go +++ b/go/ssa/interp/interp_test.go @@ -19,6 +19,7 @@ import ( "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/interp" + "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/types" ) @@ -217,7 +218,7 @@ func run(t *testing.T, dir, input string, success successPredicate) bool { return false } - prog := ssa.Create(iprog, ssa.SanityCheckFunctions) + prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions) prog.BuildAll() var mainPkg *ssa.Package @@ -345,7 +346,7 @@ func TestNullTestmainPackage(t *testing.T) { if err != nil { t.Fatalf("CreatePackages failed: %s", err) } - prog := ssa.Create(iprog, ssa.SanityCheckFunctions) + prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions) mainPkg := prog.Package(iprog.Created[0].Pkg) if mainPkg.Func("main") != nil { t.Fatalf("unexpected main function") diff --git a/go/ssa/source_test.go b/go/ssa/source_test.go index 68b5401c..36925c47 100644 --- a/go/ssa/source_test.go +++ b/go/ssa/source_test.go @@ -20,6 +20,7 @@ import ( "golang.org/x/tools/go/exact" "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/types" ) @@ -54,7 +55,7 @@ func TestObjValueLookup(t *testing.T) { return } - prog := ssa.Create(iprog, 0 /*|ssa.PrintFunctions*/) + prog := ssautil.CreateProgram(iprog, 0 /*|ssa.PrintFunctions*/) mainInfo := iprog.Created[0] mainPkg := prog.Package(mainInfo.Pkg) mainPkg.SetDebugMode(true) @@ -204,7 +205,7 @@ func TestValueForExpr(t *testing.T) { mainInfo := iprog.Created[0] - prog := ssa.Create(iprog, 0) + prog := ssautil.CreateProgram(iprog, 0) mainPkg := prog.Package(mainInfo.Pkg) mainPkg.SetDebugMode(true) mainPkg.Build() diff --git a/go/ssa/ssautil/load.go b/go/ssa/ssautil/load.go index f3090075..c2b8ce13 100644 --- a/go/ssa/ssautil/load.go +++ b/go/ssa/ssautil/load.go @@ -25,16 +25,23 @@ import ( // mode controls diagnostics and checking during SSA construction. // func CreateProgram(lprog *loader.Program, mode ssa.BuilderMode) *ssa.Program { - // TODO(adonovan): inline and delete ssa.Create. - return ssa.Create(lprog, mode) + prog := ssa.NewProgram(lprog.Fset, mode) + + for _, info := range lprog.AllPackages { + if info.TransitivelyErrorFree { + prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable) + } + } + + return prog } -// LoadPackage builds SSA code for a single package. +// BuildPackage builds an SSA program with IR for a single package. // // It populates pkg by type-checking the specified file ASTs. All // dependencies are loaded using the importer specified by tc, which // typically loads compiler export data; SSA code cannot be built for -// those packages. LoadPackage then constructs an ssa.Program with all +// those packages. BuildPackage then constructs an ssa.Program with all // dependency packages created, and builds and returns the SSA package // corresponding to pkg. // @@ -42,30 +49,14 @@ func CreateProgram(lprog *loader.Program, mode ssa.BuilderMode) *ssa.Program { // // The operation fails if there were any type-checking or import errors. // -// LoadPackage modifies the tc.Import field. +// See ../ssa/example_test.go for an example. // -func LoadPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ssa.BuilderMode) (*ssa.Package, *types.Info, error) { +func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ssa.BuilderMode) (*ssa.Package, *types.Info, error) { if fset == nil { panic("no token.FileSet") } if pkg.Path() == "" { - panic("no package path") - } - - // client's effective import function - clientImport := tc.Import - if clientImport == nil { - clientImport = types.DefaultImport - } - - deps := make(map[*types.Package]bool) - - tc.Import = func(packages map[string]*types.Package, path string) (pkg *types.Package, err error) { - pkg, err = clientImport(packages, path) - if pkg != nil { - deps[pkg] = true - } - return + panic("package has no import path") } info := &types.Info{ @@ -80,11 +71,24 @@ func LoadPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, file return nil, nil, err } - // Create the SSA program and its packages. prog := ssa.NewProgram(fset, mode) - for dep := range deps { - prog.CreatePackage(dep, nil, nil, true) + + // Create SSA packages for all imports. + // Order is not significant. + created := make(map[*types.Package]bool) + var createAll func(pkgs []*types.Package) + createAll = func(pkgs []*types.Package) { + for _, p := range pkgs { + if !created[p] { + created[p] = true + prog.CreatePackage(p, nil, nil, true) + createAll(p.Imports()) + } + } } + createAll(pkg.Imports()) + + // Create and build the primary package. ssapkg := prog.CreatePackage(pkg, files, info, false) ssapkg.Build() return ssapkg, info, nil diff --git a/go/ssa/ssautil/load_test.go b/go/ssa/ssautil/load_test.go index 3e6a5092..458d2dc3 100644 --- a/go/ssa/ssautil/load_test.go +++ b/go/ssa/ssautil/load_test.go @@ -13,6 +13,8 @@ import ( "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/types" + + _ "golang.org/x/tools/go/gcimporter" ) const hello = `package main @@ -24,7 +26,10 @@ func main() { } ` -func TestLoadPackage(t *testing.T) { +func TestBuildPackage(t *testing.T) { + // There is a more substantial test of BuildPackage and the + // SSA program it builds in ../ssa/builder_test.go. + fset := token.NewFileSet() f, err := parser.ParseFile(fset, "hello.go", hello, 0) if err != nil { @@ -32,7 +37,7 @@ func TestLoadPackage(t *testing.T) { } pkg := types.NewPackage("hello", "") - ssapkg, _, err := ssautil.LoadPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0) + ssapkg, _, err := ssautil.BuildPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0) if err != nil { t.Fatal(err) } @@ -45,7 +50,7 @@ func TestLoadPackage(t *testing.T) { } } -func TestLoadPackage_MissingImport(t *testing.T) { +func TestBuildPackage_MissingImport(t *testing.T) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "bad.go", `package bad; import "missing"`, 0) if err != nil { @@ -53,8 +58,8 @@ func TestLoadPackage_MissingImport(t *testing.T) { } pkg := types.NewPackage("bad", "") - ssapkg, _, err := ssautil.LoadPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0) + ssapkg, _, err := ssautil.BuildPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0) if err == nil || ssapkg != nil { - t.Fatal("LoadPackage succeeded unexpectedly") + t.Fatal("BuildPackage succeeded unexpectedly") } } diff --git a/go/ssa/ssautil/switch_test.go b/go/ssa/ssautil/switch_test.go index ae110584..2acbd7e1 100644 --- a/go/ssa/ssautil/switch_test.go +++ b/go/ssa/ssautil/switch_test.go @@ -29,7 +29,7 @@ func TestSwitches(t *testing.T) { return } - prog := ssa.Create(iprog, 0) + prog := ssautil.CreateProgram(iprog, 0) mainPkg := prog.Package(iprog.Created[0].Pkg) mainPkg.Build() diff --git a/go/ssa/stdlib_test.go b/go/ssa/stdlib_test.go index 63d031eb..f64a93ac 100644 --- a/go/ssa/stdlib_test.go +++ b/go/ssa/stdlib_test.go @@ -57,7 +57,7 @@ func TestStdlib(t *testing.T) { // 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) + prog := ssautil.CreateProgram(iprog, mode) t2 := time.Now() diff --git a/go/ssa/testmain_test.go b/go/ssa/testmain_test.go index 04a41fc3..56cb6040 100644 --- a/go/ssa/testmain_test.go +++ b/go/ssa/testmain_test.go @@ -14,6 +14,7 @@ import ( "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" ) func create(t *testing.T, content string) []*ssa.Package { @@ -30,7 +31,7 @@ func create(t *testing.T, content string) []*ssa.Package { } // We needn't call Build. - return ssa.Create(iprog, ssa.SanityCheckFunctions).AllPackages() + return ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions).AllPackages() } func TestFindTests(t *testing.T) { diff --git a/godoc/analysis/analysis.go b/godoc/analysis/analysis.go index 1f59a4a8..c11ecbda 100644 --- a/godoc/analysis/analysis.go +++ b/godoc/analysis/analysis.go @@ -393,7 +393,7 @@ func Run(pta bool, result *Result) { // Create SSA-form program representation. // Only the transitively error-free packages are used. - prog := ssa.Create(iprog, ssa.GlobalDebug) + prog := ssautil.CreateProgram(iprog, ssa.GlobalDebug) // Compute the set of main packages, including testmain. allPackages := prog.AllPackages() diff --git a/oracle/callees.go b/oracle/callees.go index 4bad1f0a..f05c8b28 100644 --- a/oracle/callees.go +++ b/oracle/callees.go @@ -13,6 +13,7 @@ import ( "golang.org/x/tools/go/loader" "golang.org/x/tools/go/pointer" "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/types" "golang.org/x/tools/oracle/serial" ) @@ -38,7 +39,7 @@ func callees(q *Query) error { return err } - prog := ssa.Create(lprog, 0) + prog := ssautil.CreateProgram(lprog, 0) ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { diff --git a/oracle/callers.go b/oracle/callers.go index dcf34c39..159a403a 100644 --- a/oracle/callers.go +++ b/oracle/callers.go @@ -11,6 +11,7 @@ import ( "golang.org/x/tools/go/callgraph" "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/oracle/serial" ) @@ -36,7 +37,7 @@ func callers(q *Query) error { return err } - prog := ssa.Create(lprog, 0) + prog := ssautil.CreateProgram(lprog, 0) ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { diff --git a/oracle/callstack.go b/oracle/callstack.go index 05b6ae99..6f04b603 100644 --- a/oracle/callstack.go +++ b/oracle/callstack.go @@ -11,6 +11,7 @@ import ( "golang.org/x/tools/go/callgraph" "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/oracle/serial" ) @@ -43,7 +44,7 @@ func callstack(q *Query) error { return err } - prog := ssa.Create(lprog, 0) + prog := ssautil.CreateProgram(lprog, 0) ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { diff --git a/oracle/peers.go b/oracle/peers.go index a6c9ece8..9c2a4971 100644 --- a/oracle/peers.go +++ b/oracle/peers.go @@ -42,7 +42,7 @@ func peers(q *Query) error { return err } - prog := ssa.Create(lprog, ssa.GlobalDebug) + prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { diff --git a/oracle/pointsto.go b/oracle/pointsto.go index eacd810c..d366dd3c 100644 --- a/oracle/pointsto.go +++ b/oracle/pointsto.go @@ -14,6 +14,7 @@ import ( "golang.org/x/tools/go/loader" "golang.org/x/tools/go/pointer" "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/types" "golang.org/x/tools/oracle/serial" ) @@ -44,7 +45,7 @@ func pointsto(q *Query) error { return err } - prog := ssa.Create(lprog, ssa.GlobalDebug) + prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { diff --git a/oracle/whicherrs.go b/oracle/whicherrs.go index 3ca3fc9f..aaa60682 100644 --- a/oracle/whicherrs.go +++ b/oracle/whicherrs.go @@ -47,7 +47,7 @@ func whicherrs(q *Query) error { return err } - prog := ssa.Create(lprog, ssa.GlobalDebug) + prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil {