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:
parent
62181fabb0
commit
5b5e9c877a
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user