diff --git a/go/ssa/create.go b/go/ssa/create.go index 69ac12b1..85163a0c 100644 --- a/go/ssa/create.go +++ b/go/ssa/create.go @@ -251,12 +251,19 @@ func (prog *Program) AllPackages() []*Package { return pkgs } -// ImportedPackage returns the importable SSA Package whose import -// path is path, or nil if no such SSA package has been created. +// ImportedPackage returns the importable Package whose PkgPath +// is path, or nil if no such Package has been created. // -// Not all packages are importable. For example, no import -// declaration can resolve to the x_test package created by 'go test' -// or the ad-hoc main package created 'go build foo.go'. +// A parameter to CreatePackage determines whether a package should be +// considered importable. For example, no import declaration can resolve +// to the ad-hoc main package created by 'go build foo.go'. +// +// TODO(adonovan): rethink this function and the "importable" concept; +// most packages are importable. This function assumes that all +// types.Package.Path values are unique within the ssa.Program, which is +// false---yet this function remains very convenient. +// Clients should use (*Program).Package instead where possible. +// SSA doesn't really need a string-keyed map of packages. // func (prog *Program) ImportedPackage(path string) *Package { return prog.imported[path] diff --git a/go/ssa/doc.go b/go/ssa/doc.go index 2aa04f43..1a13640f 100644 --- a/go/ssa/doc.go +++ b/go/ssa/doc.go @@ -23,11 +23,13 @@ // such as multi-way branch can be reconstructed as needed; see // ssautil.Switches() for an example. // -// To construct an SSA-form program, call ssautil.CreateProgram on a -// loader.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 simplest way to create the SSA representation of a package is +// to load typed syntax trees using golang.org/x/tools/go/packages, then +// invoke the ssautil.Packages helper function. See ExampleLoadPackages +// and ExampleWholeProgram for examples. +// 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 or (*Program).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/go/ssa/example_test.go b/go/ssa/example_test.go index 31fa5613..8bbe2d63 100644 --- a/go/ssa/example_test.go +++ b/go/ssa/example_test.go @@ -11,9 +11,10 @@ import ( "go/parser" "go/token" "go/types" + "log" "os" - "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/packages" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/ssautil" ) @@ -113,26 +114,44 @@ func ExampleBuildPackage() { // return } -// This program shows how to load a main package (cmd/cover) and all its -// dependencies from source, using the loader, and then build SSA code -// for the entire program. This is what you'd typically use for a -// whole-program analysis. -// -func ExampleLoadProgram() { - // Load cmd/cover and its dependencies. - var conf loader.Config - conf.Import("cmd/cover") - lprog, err := conf.Load() +// This example builds SSA code for a set of packages using the +// x/tools/go/packages API. This is what you would typically use for a +// analysis capable of operating on a single package. +func ExampleLoadPackages() { + // Load, parse, and type-check the initial packages. + cfg := &packages.Config{Mode: packages.LoadSyntax} + initial, err := packages.Load(cfg, "fmt", "net/http") if err != nil { - fmt.Print(err) // type error in some package - return + log.Fatal(err) } - // Create SSA-form program representation. - prog := ssautil.CreateProgram(lprog, ssa.SanityCheckFunctions) + // Create SSA packages for all well-typed packages. + prog, pkgs := ssautil.Packages(initial, ssa.PrintPackages) + _ = prog - // Build SSA code for the entire cmd/cover program. - prog.Build() - - // Output: + // Build SSA code for the well-typed initial packages. + for _, p := range pkgs { + if p != nil { + p.Build() + } + } +} + +// This example builds SSA code for a set of packages plus all their dependencies, +// using the x/tools/go/packages API. +// This is what you'd typically use for a whole-program analysis. +func ExampleLoadWholeProgram() { + // Load, parse, and type-check the whole program. + cfg := packages.Config{Mode: packages.LoadAllSyntax} + initial, err := packages.Load(&cfg, "fmt", "net/http") + if err != nil { + log.Fatal(err) + } + + // Create SSA packages for all well-typed packages. + prog, pkgs := ssautil.Packages(initial, ssa.PrintPackages) + _ = pkgs + + // Build SSA code for the whole program. + prog.Build() } diff --git a/go/ssa/ssautil/load.go b/go/ssa/ssautil/load.go index 30b8053c..9a69034b 100644 --- a/go/ssa/ssautil/load.go +++ b/go/ssa/ssautil/load.go @@ -12,9 +12,57 @@ import ( "go/types" "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/packages" "golang.org/x/tools/go/ssa" ) +// Packages creates an SSA program for a set of packages loaded from +// source syntax using the golang.org/x/tools/go/packages.Load function. +// It creates and returns an SSA package for each well-typed package in +// the initial list. The resulting list of packages has the same length +// as initial, and contains a nil if SSA could not be constructed for +// the corresponding initial package. +// +// Code for bodies of functions is not built until Build is called +// on the resulting Program. +// +// The mode parameter controls diagnostics and checking during SSA construction. +// +func Packages(initial []*packages.Package, mode ssa.BuilderMode) (*ssa.Program, []*ssa.Package) { + var fset *token.FileSet + if len(initial) > 0 { + fset = initial[0].Fset + } + + prog := ssa.NewProgram(fset, mode) + seen := make(map[*packages.Package]*ssa.Package) + var create func(p *packages.Package) *ssa.Package + create = func(p *packages.Package) *ssa.Package { + ssapkg, ok := seen[p] + if !ok { + if p.Types == nil || p.IllTyped { + // not well typed + seen[p] = nil + return nil + } + + ssapkg = prog.CreatePackage(p.Types, p.Syntax, p.TypesInfo, true) + seen[p] = ssapkg + + for _, imp := range p.Imports { + create(imp) + } + } + return ssapkg + } + + var ssapkgs []*ssa.Package + for _, p := range initial { + ssapkgs = append(ssapkgs, create(p)) + } + return prog, ssapkgs +} + // CreateProgram returns a new program in SSA form, given a program // loaded from source. An SSA package is created for each transitively // error-free package of lprog. @@ -22,7 +70,10 @@ import ( // Code for bodies of functions is not built until Build is called // on the result. // -// mode controls diagnostics and checking during SSA construction. +// The mode parameter controls diagnostics and checking during SSA construction. +// +// Deprecated: use golang.org/x/tools/go/packages and the Packages +// function instead; see ssa.ExampleLoadPackages. // func CreateProgram(lprog *loader.Program, mode ssa.BuilderMode) *ssa.Program { prog := ssa.NewProgram(lprog.Fset, mode) diff --git a/go/ssa/ssautil/load_test.go b/go/ssa/ssautil/load_test.go index 8ccd4637..5a1efe62 100644 --- a/go/ssa/ssautil/load_test.go +++ b/go/ssa/ssautil/load_test.go @@ -5,14 +5,17 @@ package ssautil_test import ( + "bytes" "go/ast" "go/importer" "go/parser" "go/token" "go/types" "os" + "strings" "testing" + "golang.org/x/tools/go/packages" "golang.org/x/tools/go/ssa/ssautil" ) @@ -49,6 +52,42 @@ func TestBuildPackage(t *testing.T) { } } +func TestPackages(t *testing.T) { + cfg := &packages.Config{Mode: packages.LoadSyntax} + initial, err := packages.Load(cfg, "bytes") + if err != nil { + t.Fatal(err) + } + + prog, pkgs := ssautil.Packages(initial, 0) + bytesNewBuffer := pkgs[0].Func("NewBuffer") + bytesNewBuffer.Pkg.Build() + + // We'll dump the SSA of bytes.NewBuffer because it is small and stable. + out := new(bytes.Buffer) + bytesNewBuffer.WriteTo(out) + + // For determinism, sanitize the location. + location := prog.Fset.Position(bytesNewBuffer.Pos()).String() + got := strings.Replace(out.String(), location, "$GOROOT/src/bytes/buffer.go:1", -1) + + want := ` +# Name: bytes.NewBuffer +# Package: bytes +# Location: $GOROOT/src/bytes/buffer.go:1 +func NewBuffer(buf []byte) *Buffer: +0: entry P:0 S:0 + t0 = new Buffer (complit) *Buffer + t1 = &t0.buf [#0] *[]byte + *t1 = buf + return t0 + +`[1:] + if got != want { + t.Errorf("bytes.NewBuffer SSA = <<%s>>, want <<%s>>", got, want) + } +} + func TestBuildPackage_MissingImport(t *testing.T) { fset := token.NewFileSet() f, err := parser.ParseFile(fset, "bad.go", `package bad; import "missing"`, 0) diff --git a/go/ssa/testmain.go b/go/ssa/testmain.go index ea232ada..8ec15ba5 100644 --- a/go/ssa/testmain.go +++ b/go/ssa/testmain.go @@ -8,8 +8,8 @@ package ssa // tests of the supplied packages. // It is closely coupled to $GOROOT/src/cmd/go/test.go and $GOROOT/src/testing. // -// TODO(adonovan): this file no longer needs to live in the ssa package. -// Move it to ssautil. +// TODO(adonovan): throws this all away now that x/tools/go/packages +// provides access to the actual synthetic test main files. import ( "bytes" @@ -26,6 +26,8 @@ import ( // FindTests returns the Test, Benchmark, and Example functions // (as defined by "go test") defined in the specified package, // and its TestMain function, if any. +// +// Deprecated: use x/tools/go/packages to access synthetic testmain packages. func FindTests(pkg *Package) (tests, benchmarks, examples []*Function, main *Function) { prog := pkg.Prog @@ -109,6 +111,8 @@ func isTest(name, prefix string) bool { // // Subsequent calls to prog.AllPackages include the new package. // The package pkg must belong to the program prog. +// +// Deprecated: use x/tools/go/packages to access synthetic testmain packages. func (prog *Program) CreateTestMainPackage(pkg *Package) *Package { if pkg.Prog != prog { log.Fatal("Package does not belong to Program")