1
0
mirror of https://github.com/golang/go synced 2024-11-18 14:14:46 -07:00

go/ssa: updates to support go/packages

Adds ssautil.Packages function, which creates ssa.Packages
from packages.Packages.

Deprecates testmain synthesis logic now that go/packages
reports the test packages synthesized by the build system
as first-class packages.

Updates docs, examples, and tests.

Flags potential confusion around legacy concept of "importable" packages.

Change-Id: I6d9cd7c6436c715d1ef39e3e280f4af4d48ccc5a
Reviewed-on: https://go-review.googlesource.com/128675
Reviewed-by: Dominik Honnef <dominik@honnef.co>
This commit is contained in:
Alan Donovan 2018-08-08 15:08:42 -04:00
parent 62181fabb0
commit 5b5e9c877a
6 changed files with 154 additions and 32 deletions

View File

@ -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]

View File

@ -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

View File

@ -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()
}

View File

@ -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)

View File

@ -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)

View File

@ -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")