go.tools/ssa: fix crash in SSA builder when using GCImporter to satisfy imports (ssadump -build=G).

Packages were not being created for all types.Packages,
specifically, indirectly imported packages were missing.
(*Program).CreatePackages now iterates over the type-checker's
package map too.

Also: removed all concurrency from importer.  I think it was
incorrect (and hard to fix).

Also: change LoadInitialPackages so that all named packages
are loaded from source.  This happens regardless of whether
GCImporter is used to satisfy imports.

Details:
- importer.Config.SourceImports flag determines whether to
  load all packages from *.go source.
  (Before, this was indicated by Config.Build != nil.)
- importer.Config.Build field effectively defaults to
  &go/build.Default.  A zero importer.Config is now usable.
- importer.Importer.Config field is now exported.
- LoadPackage renamed to ImportPackage since the resulting
  packages may come from GCImporter (and be incomplete).
- doImport and ImportPackage fused.

Fixes golang/go#7028

R=gri, axwalk
CC=golang-codereviews
https://golang.org/cl/48770043
This commit is contained in:
Alan Donovan 2014-01-09 14:11:54 -05:00
parent df3357d07f
commit 3d82e7e94a
15 changed files with 129 additions and 193 deletions

View File

@ -73,7 +73,10 @@ func main() {
flag.Parse() flag.Parse()
args := flag.Args() args := flag.Args()
impctx := importer.Config{Build: &build.Default} impctx := importer.Config{
Build: &build.Default,
SourceImports: true,
}
// TODO(adonovan): make go/types choose its default Sizes from // TODO(adonovan): make go/types choose its default Sizes from
// build.Default or a specified *build.Context. // build.Default or a specified *build.Context.
var wordSize int64 = 8 var wordSize int64 = 8
@ -103,7 +106,7 @@ func main() {
case 'N': case 'N':
mode |= ssa.NaiveForm mode |= ssa.NaiveForm
case 'G': case 'G':
impctx.Build = nil impctx.SourceImports = false
case 'L': case 'L':
mode |= ssa.BuildSerially mode |= ssa.BuildSerially
default: default:
@ -147,8 +150,8 @@ func main() {
// The interpreter needs the runtime package. // The interpreter needs the runtime package.
if *runFlag { if *runFlag {
if _, err := imp.LoadPackage("runtime"); err != nil { if _, err := imp.ImportPackage("runtime"); err != nil {
log.Fatalf("LoadPackage(runtime) failed: %s", err) log.Fatalf("ImportPackage(runtime) failed: %s", err)
} }
} }

View File

@ -50,7 +50,6 @@ import (
"go/token" "go/token"
"os" "os"
"strings" "strings"
"sync"
"code.google.com/p/go.tools/astutil" "code.google.com/p/go.tools/astutil"
"code.google.com/p/go.tools/go/exact" "code.google.com/p/go.tools/go/exact"
@ -60,32 +59,28 @@ import (
// An Importer's exported methods are not thread-safe. // An Importer's exported methods are not thread-safe.
type Importer struct { type Importer struct {
Fset *token.FileSet // position info for all files seen Fset *token.FileSet // position info for all files seen
config *Config // the client configuration, unmodified Config *Config // the client configuration, unmodified
importfn types.Importer // client's type import function importfn types.Importer // client's type import function
augment map[string]bool // packages to be augmented by TestFiles when imported srcpkgs map[string]bool // for each package to load from source, whether to augment
allPackagesMu sync.Mutex // guards 'allPackages' during internal concurrency allPackages []*PackageInfo // all packages, including non-importable ones
allPackages []*PackageInfo // all packages, including non-importable ones imported map[string]*importInfo // all imported packages (incl. failures) by import path
importedMu sync.Mutex // guards 'imported'
imported map[string]*importInfo // all imported packages (incl. failures) by import path
} }
// importInfo holds internal information about each import path. // importInfo holds internal information about each import path.
type importInfo struct { type importInfo struct {
path string // import path path string // import path
info *PackageInfo // results of typechecking (including type errors) info *PackageInfo // results of typechecking (including type errors)
err error // reason for failure to construct a package err error // reason for failure to construct a package
ready chan struct{} // channel close is notification of ready state
} }
// Config specifies the configuration for the importer. // Config specifies the configuration for the importer.
// The zero value for Config is a ready-to-use default configuration.
type Config struct { type Config struct {
// TypeChecker contains options relating to the type checker. // TypeChecker contains options relating to the type checker.
// //
// The supplied IgnoreFuncBodies is not used; the effective // The supplied IgnoreFuncBodies is not used; the effective
// value comes from the TypeCheckFuncBodies func below. // value comes from the TypeCheckFuncBodies func below.
//
// All callbacks must be thread-safe.
TypeChecker types.Config TypeChecker types.Config
// TypeCheckFuncBodies is a predicate over package import // TypeCheckFuncBodies is a predicate over package import
@ -94,23 +89,35 @@ type Config struct {
// its function bodies; this can be used to quickly load // its function bodies; this can be used to quickly load
// dependencies from source. If nil, all func bodies are type // dependencies from source. If nil, all func bodies are type
// checked. // checked.
//
// Must be thread-safe.
//
TypeCheckFuncBodies func(string) bool TypeCheckFuncBodies func(string) bool
// If Build is non-nil, it is used to satisfy imports. // SourceImports determines whether to satisfy all imports by
// loading Go source code.
// //
// If it is nil, binary object files produced by the gc // If false, the TypeChecker.Import mechanism will be used
// compiler will be loaded instead of source code for all // instead. Since that typically supplies only the types of
// imported packages. Such files supply only the types of
// package-level declarations and values of constants, but no // package-level declarations and values of constants, but no
// code, so this mode will not yield a whole program. It is // code, it will not yield a whole program. It is intended
// intended for analyses that perform intraprocedural analysis // for analyses that perform intraprocedural analysis of a
// of a single package. // single package.
//
// The importer's initial packages are always loaded from
// 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 Build *build.Context
} }
// build returns the effective build context.
func (c *Config) build() *build.Context {
if c.Build != nil {
return c.Build
}
return &build.Default
}
// New returns a new, empty Importer using configuration options // New returns a new, empty Importer using configuration options
// specified by config. // specified by config.
// //
@ -129,27 +136,27 @@ func New(config *Config) *Importer {
imp := &Importer{ imp := &Importer{
Fset: token.NewFileSet(), Fset: token.NewFileSet(),
config: config, Config: config,
importfn: importfn, importfn: importfn,
augment: make(map[string]bool), srcpkgs: make(map[string]bool),
imported: make(map[string]*importInfo), imported: make(map[string]*importInfo),
} }
return imp return imp
} }
// AllPackages returns a new slice containing all packages loaded by // AllPackages returns a new slice containing all complete packages
// importer imp. // loaded by importer imp.
//
// 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 { func (imp *Importer) AllPackages() []*PackageInfo {
return append([]*PackageInfo(nil), imp.allPackages...) return append([]*PackageInfo(nil), imp.allPackages...)
} }
func (imp *Importer) addPackage(info *PackageInfo) {
imp.allPackagesMu.Lock()
imp.allPackages = append(imp.allPackages, info)
imp.allPackagesMu.Unlock()
}
// doImport imports the package denoted by path. // doImport imports the package denoted by path.
// It implements the types.Importer prototype. // It implements the types.Importer prototype.
// //
@ -163,40 +170,7 @@ func (imp *Importer) addPackage(info *PackageInfo) {
// the types.Config.Error callback (the first of which is also saved // the types.Config.Error callback (the first of which is also saved
// in the package's PackageInfo). // in the package's PackageInfo).
// //
// Idempotent and thread-safe. // Idempotent.
//
//
// TODO(gri): The imports map (an alias for TypeChecker.Packages) must
// not be concurrently accessed. Today, the only (non-test) accesses
// of this map are in the client-supplied implementation of the
// importer function, i.e. the function below. But this is a fragile
// API because if go/types ever starts to access this map, it and its
// clients will have to agree to use the same mutex.
// Two better ideas:
//
// (1) require that the importer functions be stateful and have this
// map be part of that internal state.
// Pro: good encapsulation.
// Con: we can't have different importers collaborate, e.g.
// we can't use a source importer for some packages and
// GcImport for others since they'd each have a distinct map.
//
// (2) have there be a single map in go/types.Config, but expose
// lookup and update behind an interface and pass that interface
// to the importer implementations.
// Pro: sharing of the map among importers.
//
// This is idempotent but still doesn't address the issue of
// atomicity: when loading packages concurrently, we want to avoid
// the benign but suboptimal situation of two goroutines both
// importing "fmt", finding it not present, doing all the work, and
// double-updating the map.
// The interface to the map needs to express the idea that when a
// caller requests an import from the map and finds it not present,
// then it (and no other goroutine) becomes responsible for loading
// the package and updating the map; other goroutines should block
// until then. That's exactly what doImport0 below does; I think
// some of that logic should migrate into go/types.check.resolveFiles.
// //
func (imp *Importer) doImport(imports map[string]*types.Package, path string) (*types.Package, error) { func (imp *Importer) doImport(imports map[string]*types.Package, path string) (*types.Package, error) {
// Package unsafe is handled specially, and has no PackageInfo. // Package unsafe is handled specially, and has no PackageInfo.
@ -205,99 +179,67 @@ func (imp *Importer) doImport(imports map[string]*types.Package, path string) (*
return types.Unsafe, nil return types.Unsafe, nil
} }
info, err := imp.doImport0(imports, path) info, err := imp.ImportPackage(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if imports != nil { // Update the type checker's package map on success.
// Update the package's imports map, whether success or failure. imports[path] = info.Pkg
//
// types.Package.Imports() is used by PackageInfo.Imports and
// thence by ssa.builder.
// TODO(gri): given that it doesn't specify whether it
// contains direct or transitive dependencies, it probably
// shouldn't be exposed. This package can make its own
// arrangements to implement PackageInfo.Imports().
importsMu.Lock()
imports[path] = info.Pkg
importsMu.Unlock()
}
return info.Pkg, nil return info.Pkg, nil
} }
var importsMu sync.Mutex // hack; see comment at doImport // ImportPackage imports the package whose import path is path, plus
// its necessary dependencies.
// Like doImport, but returns a PackageInfo. //
// Precondition: path != "unsafe". // Precondition: path != "unsafe".
func (imp *Importer) doImport0(imports map[string]*types.Package, path string) (*PackageInfo, error) { //
imp.importedMu.Lock() func (imp *Importer) ImportPackage(path string) (*PackageInfo, error) {
ii, ok := imp.imported[path] ii, ok := imp.imported[path]
if !ok { if !ok {
ii = &importInfo{path: path, ready: make(chan struct{})} ii = &importInfo{path: path}
imp.imported[path] = ii imp.imported[path] = ii
}
imp.importedMu.Unlock()
if !ok {
// Find and create the actual package. // Find and create the actual package.
if imp.config.Build != nil { if augment, ok := imp.srcpkgs[ii.path]; ok || imp.Config.SourceImports {
imp.importSource(path, ii) which := "g" // load *.go files
if augment {
which = "gt" // augment package by in-package *_test.go files
}
imp.loadFromSource(ii, which)
} else { } else {
imp.importBinary(imports, ii) imp.loadFromBinary(ii)
} }
if ii.info != nil { if ii.info != nil {
ii.info.Importable = true ii.info.Importable = true
} }
close(ii.ready) // enter ready state and wake up waiters
} else {
<-ii.ready // wait for ready condition
} }
// Invariant: ii is ready.
return ii.info, ii.err return ii.info, ii.err
} }
// importBinary implements package loading from the client-supplied // loadFromBinary implements package loading from the client-supplied
// external source, e.g. object files from the gc compiler. // external source, e.g. object files from the gc compiler.
// //
func (imp *Importer) importBinary(imports map[string]*types.Package, ii *importInfo) { func (imp *Importer) loadFromBinary(ii *importInfo) {
pkg, err := imp.importfn(imports, ii.path) pkg, err := imp.importfn(imp.Config.TypeChecker.Packages, ii.path)
if pkg != nil { if pkg != nil {
ii.info = &PackageInfo{Pkg: pkg} ii.info = &PackageInfo{Pkg: pkg}
imp.addPackage(ii.info) imp.allPackages = append(imp.allPackages, ii.info)
} else { } else {
ii.err = err ii.err = err
} }
} }
// importSource implements package loading by parsing Go source files // loadFromSource implements package loading by parsing Go source files
// located by go/build. // located by go/build. which indicates which files to include in the
// package.
// //
func (imp *Importer) importSource(path string, ii *importInfo) { func (imp *Importer) loadFromSource(ii *importInfo, which string) {
which := "g" // load *.go files if files, err := parsePackageFiles(imp.Config.build(), imp.Fset, ii.path, which); err == nil {
if imp.augment[path] {
which = "gt" // augment package by in-package *_test.go files
}
if files, err := parsePackageFiles(imp.config.Build, imp.Fset, path, which); err == nil {
// Prefetch the imports asynchronously.
for path := range importsOf(path, files) {
go func(path string) { imp.doImport(nil, path) }(path)
}
// Type-check the package. // Type-check the package.
ii.info = imp.CreatePackage(path, files...) ii.info = imp.CreatePackage(ii.path, files...)
// We needn't wait for the prefetching goroutines to
// finish. Each one either runs quickly and populates
// the imported map, in which case the type checker
// will wait for the map entry to become ready; or, it
// runs slowly, even after we return, but then becomes
// just another map waiter, in which case it won't
// mutate anything.
} else { } else {
ii.err = err ii.err = err
} }
@ -335,9 +277,9 @@ func (imp *Importer) CreatePackage(path string, files ...*ast.File) *PackageInfo
} }
// Use a copy of the types.Config so we can vary IgnoreFuncBodies. // Use a copy of the types.Config so we can vary IgnoreFuncBodies.
tc := imp.config.TypeChecker tc := imp.Config.TypeChecker
tc.IgnoreFuncBodies = false tc.IgnoreFuncBodies = false
if f := imp.config.TypeCheckFuncBodies; f != nil { if f := imp.Config.TypeCheckFuncBodies; f != nil {
tc.IgnoreFuncBodies = !f(path) tc.IgnoreFuncBodies = !f(path)
} }
if tc.Error == nil { if tc.Error == nil {
@ -345,7 +287,7 @@ func (imp *Importer) CreatePackage(path string, files ...*ast.File) *PackageInfo
} }
tc.Import = imp.doImport // doImport wraps the user's importfn, effectively tc.Import = imp.doImport // doImport wraps the user's importfn, effectively
info.Pkg, info.Err = tc.Check(path, imp.Fset, files, &info.Info) info.Pkg, info.Err = tc.Check(path, imp.Fset, files, &info.Info)
imp.addPackage(info) imp.allPackages = append(imp.allPackages, info)
return info return info
} }
@ -418,6 +360,7 @@ func (imp *Importer) LoadInitialPackages(args []string) ([]*PackageInfo, []strin
// Pass 1: parse the sets of files for each package. // Pass 1: parse the sets of files for each package.
var pkgs []*initialPkg var pkgs []*initialPkg
var seenAugmented bool
for len(args) > 0 { for len(args) > 0 {
arg := args[0] arg := args[0]
args = args[1:] args = args[1:]
@ -444,28 +387,24 @@ func (imp *Importer) LoadInitialPackages(args []string) ([]*PackageInfo, []strin
continue // ignore; has no PackageInfo continue // ignore; has no PackageInfo
} }
pkg := &initialPkg{ pkgs = append(pkgs, &initialPkg{
path: path, path: path,
importable: true, importable: true,
} })
pkgs = append(pkgs, pkg) imp.srcpkgs[path] = false // unaugmented source package
if path != arg { if path != arg {
continue // had "notest:" prefix continue // had "notest:" prefix
} }
if imp.config.Build == nil {
continue // can't locate *_test.go files
}
// TODO(adonovan): due to limitations of the current type // TODO(adonovan): due to limitations of the current type
// checker design, we can augment at most one package. // checker design, we can augment at most one package.
if len(imp.augment) > 0 { if seenAugmented {
continue // don't attempt a second continue // don't attempt a second
} }
// Load the external test package. // Load the external test package.
xtestFiles, err := parsePackageFiles(imp.config.Build, imp.Fset, path, "x") xtestFiles, err := parsePackageFiles(imp.Config.build(), imp.Fset, path, "x")
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -479,24 +418,22 @@ func (imp *Importer) LoadInitialPackages(args []string) ([]*PackageInfo, []strin
// Mark the non-xtest package for augmentation with // Mark the non-xtest package for augmentation with
// in-package *_test.go files when we import it below. // in-package *_test.go files when we import it below.
imp.augment[pkg.path] = true imp.srcpkgs[path] = true
seenAugmented = true
} }
} }
// Pass 2: type-check each set of files to make a package. // Pass 2: type-check each set of files to make a package.
var infos []*PackageInfo var infos []*PackageInfo
imports := make(map[string]*types.Package) // keep importBinary happy
for _, pkg := range pkgs { for _, pkg := range pkgs {
var info *PackageInfo var info *PackageInfo
if pkg.importable { if pkg.importable {
// import package
var err error var err error
info, err = imp.doImport0(imports, pkg.path) info, err = imp.ImportPackage(pkg.path)
if err != nil { if err != nil {
return nil, nil, err // e.g. parse error (but not type error) return nil, nil, err // e.g. parse error (but not type error)
} }
} else { } else {
// create package
info = imp.CreatePackage(pkg.path, pkg.files...) info = imp.CreatePackage(pkg.path, pkg.files...)
} }
infos = append(infos, info) infos = append(infos, info)
@ -509,17 +446,9 @@ func (imp *Importer) LoadInitialPackages(args []string) ([]*PackageInfo, []strin
return infos, args, nil return infos, args, nil
} }
// LoadPackage loads and type-checks the package whose import path is
// path, plus its necessary dependencies.
//
func (imp *Importer) LoadPackage(path string) (*PackageInfo, error) {
imports := make(map[string]*types.Package) // keep importBinary happy
return imp.doImport0(imports, path)
}
type initialPkg struct { type initialPkg struct {
path string // the package's import path path string // the package's import path
importable bool // add package to import map false for main and xtests) importable bool // add package to import map (false for main and xtests)
files []*ast.File // set of files (non-importable packages only) files []*ast.File // set of files (non-importable packages only)
} }

View File

@ -6,18 +6,15 @@ package importer_test
import ( import (
"fmt" "fmt"
"go/build"
"testing" "testing"
"code.google.com/p/go.tools/importer" "code.google.com/p/go.tools/importer"
) )
func TestLoadInitialPackages(t *testing.T) { func TestLoadInitialPackages(t *testing.T) {
ctxt := &importer.Config{Build: &build.Default}
// Failed load: bad first import path causes parsePackageFiles to fail. // Failed load: bad first import path causes parsePackageFiles to fail.
args := []string{"nosuchpkg", "errors"} args := []string{"nosuchpkg", "errors"}
if _, _, err := importer.New(ctxt).LoadInitialPackages(args); err == nil { if _, _, err := importer.New(&importer.Config{}).LoadInitialPackages(args); err == nil {
t.Errorf("LoadInitialPackages(%q) succeeded, want failure", args) t.Errorf("LoadInitialPackages(%q) succeeded, want failure", args)
} else { } else {
// cannot find package: ok. // cannot find package: ok.
@ -25,7 +22,7 @@ func TestLoadInitialPackages(t *testing.T) {
// Failed load: bad second import path proceeds to doImport0, which fails. // Failed load: bad second import path proceeds to doImport0, which fails.
args = []string{"errors", "nosuchpkg"} args = []string{"errors", "nosuchpkg"}
if _, _, err := importer.New(ctxt).LoadInitialPackages(args); err == nil { if _, _, err := importer.New(&importer.Config{}).LoadInitialPackages(args); err == nil {
t.Errorf("LoadInitialPackages(%q) succeeded, want failure", args) t.Errorf("LoadInitialPackages(%q) succeeded, want failure", args)
} else { } else {
// cannot find package: ok // cannot find package: ok
@ -33,7 +30,7 @@ func TestLoadInitialPackages(t *testing.T) {
// Successful load. // Successful load.
args = []string{"fmt", "errors", "testdata/a.go,testdata/b.go", "--", "surplus"} args = []string{"fmt", "errors", "testdata/a.go,testdata/b.go", "--", "surplus"}
imp := importer.New(ctxt) imp := importer.New(&importer.Config{})
infos, rest, err := imp.LoadInitialPackages(args) infos, rest, err := imp.LoadInitialPackages(args)
if err != nil { if err != nil {
t.Errorf("LoadInitialPackages(%q) failed: %s", args, err) t.Errorf("LoadInitialPackages(%q) failed: %s", args, err)

View File

@ -82,7 +82,7 @@ func TestEnclosingFunction(t *testing.T) {
"900", "func@2.27"}, "900", "func@2.27"},
} }
for _, test := range tests { for _, test := range tests {
imp := importer.New(new(importer.Config)) // (NB: no go/build.Config) imp := importer.New(&importer.Config{})
f, start, end := findInterval(t, imp.Fset, test.input, test.substr) f, start, end := findInterval(t, imp.Fset, test.input, test.substr)
if f == nil { if f == nil {
continue continue

View File

@ -192,7 +192,7 @@ func (res *Result) Serial() *serial.Result {
// Clients that intend to perform multiple queries against the same // Clients that intend to perform multiple queries against the same
// analysis scope should use this pattern instead: // analysis scope should use this pattern instead:
// //
// imp := importer.New(&importer.Config{Build: buildContext}) // imp := importer.New(&importer.Config{Build: buildContext, SourceImports: true})
// o, err := oracle.New(imp, args, nil) // o, err := oracle.New(imp, args, nil)
// if err != nil { ... } // if err != nil { ... }
// for ... { // for ... {
@ -219,7 +219,7 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
return nil, fmt.Errorf("invalid mode type: %q", mode) return nil, fmt.Errorf("invalid mode type: %q", mode)
} }
impcfg := importer.Config{Build: buildContext} impcfg := importer.Config{Build: buildContext, SourceImports: true}
// For queries needing only a single typed package, // For queries needing only a single typed package,
// reduce the analysis scope to that package. // reduce the analysis scope to that package.

View File

@ -6,7 +6,6 @@ package pointer_test
import ( import (
"fmt" "fmt"
"go/build"
"go/parser" "go/parser"
"sort" "sort"
@ -41,8 +40,7 @@ func main() {
} }
` `
// Construct an importer. // Construct an importer.
// Imports will be loaded as if by 'go build'. imp := importer.New(&importer.Config{SourceImports: true})
imp := importer.New(&importer.Config{Build: &build.Default})
// Parse the input file. // Parse the input file.
file, err := parser.ParseFile(imp.Fset, "myprog.go", myprog, 0) file, err := parser.ParseFile(imp.Fset, "myprog.go", myprog, 0)

View File

@ -12,7 +12,6 @@ import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"go/build"
"go/parser" "go/parser"
"go/token" "go/token"
"io/ioutil" "io/ioutil"
@ -152,7 +151,7 @@ func findProbe(prog *ssa.Program, probes map[*ssa.CallCommon]pointer.Pointer, e
} }
func doOneInput(input, filename string) bool { func doOneInput(input, filename string) bool {
impctx := &importer.Config{Build: &build.Default} impctx := &importer.Config{SourceImports: true}
imp := importer.New(impctx) imp := importer.New(impctx)
// Parsing. // Parsing.

View File

@ -810,9 +810,6 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
c.Method = obj c.Method = obj
} else { } else {
// "Call"-mode call. // "Call"-mode call.
// TODO(adonovan): fix: in -build=G
// mode, declaredFunc panics for
// cross-package calls.
c.Value = fn.Prog.declaredFunc(obj) c.Value = fn.Prog.declaredFunc(obj)
c.Args = append(c.Args, v) c.Args = append(c.Args, v)
} }

View File

@ -40,7 +40,7 @@ func main() {
w.Write(nil) // interface invoke of external declared method w.Write(nil) // interface invoke of external declared method
} }
` `
imp := importer.New(new(importer.Config)) // no go/build.Context; uses GC importer imp := importer.New(&importer.Config{})
f, err := parser.ParseFile(imp.Fset, "<input>", test, 0) f, err := parser.ParseFile(imp.Fset, "<input>", test, 0)
if err != nil { if err != nil {
@ -58,10 +58,16 @@ func main() {
mainPkg := prog.Package(mainInfo.Pkg) mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.Build() mainPkg.Build()
// Only the main package and its immediate dependencies are loaded. // The main package, its direct and indirect dependencies are loaded.
deps := []string{"bytes", "io", "testing"} deps := []string{
// directly imported dependencies:
"bytes", "io", "testing",
// indirect dependencies (partial list):
"errors", "fmt", "os", "runtime",
}
all := prog.AllPackages() all := prog.AllPackages()
if len(all) != 1+len(deps) { if len(all) <= len(deps) {
t.Errorf("unexpected set of loaded packages: %q", all) t.Errorf("unexpected set of loaded packages: %q", all)
} }
for _, path := range deps { for _, path := range deps {
@ -198,7 +204,7 @@ func TestTypesWithMethodSets(t *testing.T) {
}, },
} }
for i, test := range tests { for i, test := range tests {
imp := importer.New(new(importer.Config)) // no go/build.Context; uses GC importer imp := importer.New(&importer.Config{})
f, err := parser.ParseFile(imp.Fset, "<input>", test.input, 0) f, err := parser.ParseFile(imp.Fset, "<input>", test.input, 0)
if err != nil { if err != nil {

View File

@ -250,6 +250,8 @@ func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
// //
func (prog *Program) CreatePackages(imp *importer.Importer) error { func (prog *Program) CreatePackages(imp *importer.Importer) error {
var errpkgs []string var errpkgs []string
// Create source packages and directly imported packages.
for _, info := range imp.AllPackages() { for _, info := range imp.AllPackages() {
if info.Err != nil { if info.Err != nil {
errpkgs = append(errpkgs, info.Pkg.Path()) errpkgs = append(errpkgs, info.Pkg.Path())
@ -257,6 +259,15 @@ func (prog *Program) CreatePackages(imp *importer.Importer) error {
prog.CreatePackage(info) 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 { if errpkgs != nil {
return fmt.Errorf("couldn't create these SSA packages due to type errors: %s", return fmt.Errorf("couldn't create these SSA packages due to type errors: %s",
strings.Join(errpkgs, ", ")) strings.Join(errpkgs, ", "))

View File

@ -6,7 +6,6 @@ package ssa_test
import ( import (
"fmt" "fmt"
"go/build"
"go/parser" "go/parser"
"os" "os"
@ -42,8 +41,8 @@ func main() {
fmt.Println(message) fmt.Println(message)
} }
` `
// Construct an importer. Imports will be loaded as if by 'go build'. // Construct an importer.
imp := importer.New(&importer.Config{Build: &build.Default}) imp := importer.New(&importer.Config{})
// Parse the input file. // Parse the input file.
file, err := parser.ParseFile(imp.Fset, "hello.go", hello, 0) file, err := parser.ParseFile(imp.Fset, "hello.go", hello, 0)

View File

@ -168,7 +168,7 @@ func run(t *testing.T, dir, input string, success successPredicate) bool {
inputs = append(inputs, dir+i) inputs = append(inputs, dir+i)
} }
imp := importer.New(&importer.Config{Build: &build.Default}) imp := importer.New(&importer.Config{SourceImports: true})
// TODO(adonovan): use LoadInitialPackages, then un-export ParseFiles. // TODO(adonovan): use LoadInitialPackages, then un-export ParseFiles.
// Then add the following packages' tests, which pass: // Then add the following packages' tests, which pass:
// "flag", "unicode", "unicode/utf8", "testing", "log", "path". // "flag", "unicode", "unicode/utf8", "testing", "log", "path".
@ -194,8 +194,8 @@ 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) 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...) mainInfo := imp.CreatePackage(files[0].Name.Name, files...)
if _, err := imp.LoadPackage("runtime"); err != nil { if _, err := imp.ImportPackage("runtime"); err != nil {
t.Errorf("LoadPackage(runtime) failed: %s", err) t.Errorf("ImportPackage(runtime) failed: %s", err)
} }
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
@ -321,7 +321,7 @@ func TestTestmainPackage(t *testing.T) {
// CreateTestMainPackage should return nil if there were no tests. // CreateTestMainPackage should return nil if there were no tests.
func TestNullTestmainPackage(t *testing.T) { func TestNullTestmainPackage(t *testing.T) {
imp := importer.New(&importer.Config{Build: &build.Default}) imp := importer.New(&importer.Config{})
files, err := importer.ParseFiles(imp.Fset, ".", "testdata/b_test.go") files, err := importer.ParseFiles(imp.Fset, ".", "testdata/b_test.go")
if err != nil { if err != nil {
t.Fatalf("ParseFiles failed: %s", err) t.Fatalf("ParseFiles failed: %s", err)

View File

@ -24,7 +24,7 @@ import (
) )
func TestObjValueLookup(t *testing.T) { func TestObjValueLookup(t *testing.T) {
imp := importer.New(new(importer.Config)) // (uses GCImporter) imp := importer.New(&importer.Config{})
f, err := parser.ParseFile(imp.Fset, "testdata/objlookup.go", nil, parser.ParseComments) f, err := parser.ParseFile(imp.Fset, "testdata/objlookup.go", nil, parser.ParseComments)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
@ -186,7 +186,7 @@ func checkVarValue(t *testing.T, prog *ssa.Program, pkg *ssa.Package, ref []ast.
// Ensure that, in debug mode, we can determine the ssa.Value // Ensure that, in debug mode, we can determine the ssa.Value
// corresponding to every ast.Expr. // corresponding to every ast.Expr.
func TestValueForExpr(t *testing.T) { func TestValueForExpr(t *testing.T) {
imp := importer.New(new(importer.Config)) // (uses GCImporter) imp := importer.New(&importer.Config{})
f, err := parser.ParseFile(imp.Fset, "testdata/valueforexpr.go", nil, parser.ParseComments) f, err := parser.ParseFile(imp.Fset, "testdata/valueforexpr.go", nil, parser.ParseComments)
if err != nil { if err != nil {
t.Error(err) t.Error(err)

View File

@ -15,7 +15,7 @@ import (
) )
func TestSwitches(t *testing.T) { func TestSwitches(t *testing.T) {
imp := importer.New(new(importer.Config)) // (uses GCImporter) imp := importer.New(&importer.Config{})
f, err := parser.ParseFile(imp.Fset, "testdata/switches.go", nil, parser.ParseComments) f, err := parser.ParseFile(imp.Fset, "testdata/switches.go", nil, parser.ParseComments)
if err != nil { if err != nil {
t.Error(err) t.Error(err)

View File

@ -10,7 +10,6 @@ package ssa_test
// Run test with GOMAXPROCS=8. // Run test with GOMAXPROCS=8.
import ( import (
"go/build"
"go/token" "go/token"
"os" "os"
"path/filepath" "path/filepath"
@ -52,12 +51,10 @@ func allPackages() []string {
} }
func TestStdlib(t *testing.T) { func TestStdlib(t *testing.T) {
impctx := importer.Config{Build: &build.Default}
// Load, parse and type-check the program. // Load, parse and type-check the program.
t0 := time.Now() t0 := time.Now()
imp := importer.New(&impctx) imp := importer.New(&importer.Config{})
if _, _, err := imp.LoadInitialPackages(allPackages()); err != nil { if _, _, err := imp.LoadInitialPackages(allPackages()); err != nil {
t.Errorf("LoadInitialPackages failed: %s", err) t.Errorf("LoadInitialPackages failed: %s", err)