go.tools/importer: generalize command-line syntax.
Motivation: pointer analysis tools (like the oracle) want the user to specify a set of initial packages, like 'go test'. This change enables the user to specify a set of packages on the command line using importer.LoadInitialPackages(args). Each argument is interpreted as either: - a comma-separated list of *.go source files together comprising one non-importable ad-hoc package. e.g. "src/pkg/net/http/triv.go" gives us [main]. - an import path, denoting both the imported package and its non-importable external test package, if any. e.g. "fmt" gives us [fmt, fmt_test]. Current type-checker limitations mean that only the first import path may contribute tests: multiple packages augmented by *_test.go files could create import cycles, which 'go test' avoids by building a separate executable for each one. That approach is less attractive for static analysis. Details: (many files touched, but importer.go is the crux) importer: - PackageInfo.Importable boolean indicates whether package is importable. - un-expose Importer.Packages; expose AllPackages() instead. - CreatePackageFromArgs has become LoadInitialPackages. - imports() moved to util.go, renamed importsOf(). - InitialPackagesUsage usage message exported to clients. - the package name for ad-hoc packages now comes from the 'package' decl, not "main". ssa.Program: - added CreatePackages() method - PackagesByPath un-exposed, renamed 'imported'. - expose AllPackages and ImportedPackage accessors. oracle: - describe: explain and workaround a go/types bug. Misc: - Removed various unnecessary error.Error() calls in Printf args. R=crawshaw CC=golang-dev https://golang.org/cl/13579043
This commit is contained in:
parent
33f988691e
commit
3f2f9a7e70
|
|
@ -25,6 +25,7 @@ import (
|
|||
"runtime"
|
||||
"runtime/pprof"
|
||||
|
||||
"code.google.com/p/go.tools/importer"
|
||||
"code.google.com/p/go.tools/oracle"
|
||||
)
|
||||
|
||||
|
|
@ -41,13 +42,13 @@ var ptalogFlag = flag.String("ptalog", "",
|
|||
var formatFlag = flag.String("format", "plain", "Output format: 'plain' or 'json'.")
|
||||
|
||||
const usage = `Go source code oracle.
|
||||
Usage: oracle [<flag> ...] [<file.go> ...] [<arg> ...]
|
||||
Usage: oracle [<flag> ...] <args> ...
|
||||
Use -help flag to display options.
|
||||
|
||||
Examples:
|
||||
% oracle -pos=hello.go:#123 hello.go
|
||||
% oracle -pos=hello.go:#123-#456 hello.go
|
||||
`
|
||||
` + importer.InitialPackagesUsage
|
||||
|
||||
var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file")
|
||||
|
||||
|
|
@ -76,7 +77,7 @@ func main() {
|
|||
var ptalog io.Writer
|
||||
if *ptalogFlag != "" {
|
||||
if f, err := os.Create(*ptalogFlag); err != nil {
|
||||
log.Fatalf(err.Error())
|
||||
log.Fatal(err.Error())
|
||||
} else {
|
||||
buf := bufio.NewWriter(f)
|
||||
ptalog = buf
|
||||
|
|
@ -106,7 +107,7 @@ func main() {
|
|||
// Ask the oracle.
|
||||
res, err := oracle.Query(args, *modeFlag, *posFlag, ptalog, &build.Default)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +116,7 @@ func main() {
|
|||
case "json":
|
||||
b, err := json.Marshal(res)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "JSON error: %s\n", err.Error())
|
||||
fmt.Fprintf(os.Stderr, "JSON error: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
|
|
|
|||
|
|
@ -123,22 +123,36 @@ func main() {
|
|||
|
||||
// Load, parse and type-check the program.
|
||||
imp := importer.New(&impctx)
|
||||
info, args, err := importer.CreatePackageFromArgs(imp, args)
|
||||
infos, args, err := imp.LoadInitialPackages(args)
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Create and build SSA-form program representation.
|
||||
prog := ssa.NewProgram(imp.Fset, mode)
|
||||
for _, info := range imp.Packages {
|
||||
prog.CreatePackage(info).SetDebugMode(debugMode)
|
||||
if err := prog.CreatePackages(imp); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if debugMode {
|
||||
for _, pkg := range prog.AllPackages() {
|
||||
pkg.SetDebugMode(true)
|
||||
}
|
||||
}
|
||||
prog.BuildAll()
|
||||
|
||||
prog.Package(info.Pkg).CreateTestMainFunction() // TODO(adonovan): remove hack
|
||||
|
||||
// Run the interpreter.
|
||||
// Run the interpreter on the first package with a main function.
|
||||
if *runFlag {
|
||||
interp.Interpret(prog.Package(info.Pkg), interpMode, info.Pkg.Path(), args)
|
||||
var main *ssa.Package
|
||||
for _, info := range infos {
|
||||
pkg := prog.Package(info.Pkg)
|
||||
if pkg.Func("main") != nil || pkg.CreateTestMainFunction() != nil {
|
||||
main = pkg
|
||||
break
|
||||
}
|
||||
}
|
||||
if main == nil {
|
||||
log.Fatal("No main function")
|
||||
}
|
||||
interp.Interpret(main, interpMode, main.Object.Path(), args)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,49 +6,83 @@
|
|||
// type-checks packages of Go code plus their transitive closure, and
|
||||
// retains both the ASTs and the derived facts.
|
||||
//
|
||||
// TODO(adonovan): document and test this package better, with examples.
|
||||
// Currently it's covered by the ssa/ tests.
|
||||
// CONCEPTS AND TERMINOLOGY
|
||||
//
|
||||
// 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 src/pkg/net/http/triv.go.
|
||||
//
|
||||
// EXTERNAL TEST packages are those comprised of a set of *_test.go
|
||||
// files all with the same 'package foo_test' declaration, all in the
|
||||
// same directory. (go/build.Package calls these files XTestFiles.)
|
||||
//
|
||||
// An IMPORTABLE package is one that can be referred to by some import
|
||||
// spec. Ad-hoc packages and external test packages are non-importable.
|
||||
// The importer and its clients must be careful not to assume that
|
||||
// the import path of a package may be used for a name-based lookup.
|
||||
// For example, a pointer analysis scope may consist of two initial
|
||||
// (ad-hoc) packages both called "main".
|
||||
//
|
||||
// An AUGMENTED package is an importable package P plus all the
|
||||
// *_test.go files with same 'package foo' declaration as P.
|
||||
// (go/build.Package calls these files TestFiles.)
|
||||
// An external test package may depend upon members of the augmented
|
||||
// package that are not in the unaugmented package, such as functions
|
||||
// that expose internals. (See bufio/export_test.go for an example.)
|
||||
// So, the importer must ensure that for each external test package
|
||||
// it loads, it also augments the corresponding non-test package.
|
||||
//
|
||||
// The import graph over n unaugmented packages must be acyclic; the
|
||||
// import graph over n-1 unaugmented packages plus one augmented
|
||||
// package must also be acyclic. ('go test' relies on this.) But the
|
||||
// import graph over n augmented packages may contain cycles, and
|
||||
// currently, go/types is incapable of handling such inputs, so the
|
||||
// Importer will only augment (and create an external test package
|
||||
// for) the first import path specified on the command-line.
|
||||
//
|
||||
// TODO(adonovan): more tests.
|
||||
//
|
||||
package importer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/token"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"code.google.com/p/go.tools/go/exact"
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
)
|
||||
|
||||
// An Importer's methods are not thread-safe unless specified.
|
||||
// An Importer's exported methods are not thread-safe.
|
||||
type Importer struct {
|
||||
config *Config // the client configuration
|
||||
Fset *token.FileSet // position info for all files seen
|
||||
Packages map[string]*PackageInfo // successfully imported packages keyed by import path
|
||||
errors map[string]error // cache of errors by import path
|
||||
|
||||
mu sync.Mutex // guards 'packages' map
|
||||
packages map[string]*pkgInfo // all attempted imported packages, keyed by import path
|
||||
Fset *token.FileSet // position info for all files seen
|
||||
config *Config // the client configuration
|
||||
augment map[string]bool // packages to be augmented by TestFiles when imported
|
||||
allPackagesMu sync.Mutex // guards 'allPackages' during internal concurrency
|
||||
allPackages []*PackageInfo // all packages, including non-importable ones
|
||||
importedMu sync.Mutex // guards 'imported'
|
||||
imported map[string]*importInfo // all imported packages (incl. failures) by import path
|
||||
}
|
||||
|
||||
// pkgInfo holds internal per-package information.
|
||||
type pkgInfo struct {
|
||||
info *PackageInfo // success info
|
||||
err error // failure info
|
||||
// importInfo holds internal information about each import path.
|
||||
type importInfo struct {
|
||||
path string // import path
|
||||
info *PackageInfo // results of typechecking (including type errors)
|
||||
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.
|
||||
//
|
||||
type Config struct {
|
||||
// TypeChecker contains options relating to the type checker.
|
||||
// The Importer will override any user-supplied values for its
|
||||
// Error and Import fields; other fields will be passed
|
||||
// through to the type checker.
|
||||
// through to the type checker. All callbacks must be thread-safe.
|
||||
TypeChecker types.Config
|
||||
|
||||
// If Build is non-nil, it is used to satisfy imports.
|
||||
|
|
@ -68,151 +102,165 @@ type Config struct {
|
|||
//
|
||||
func New(config *Config) *Importer {
|
||||
imp := &Importer{
|
||||
config: config,
|
||||
Fset: token.NewFileSet(),
|
||||
packages: make(map[string]*pkgInfo),
|
||||
Packages: make(map[string]*PackageInfo),
|
||||
errors: make(map[string]error),
|
||||
config: config,
|
||||
augment: make(map[string]bool),
|
||||
imported: make(map[string]*importInfo),
|
||||
}
|
||||
// TODO(adonovan): get typechecker to supply us with a source
|
||||
// position, then pass errors back to the application
|
||||
// (e.g. oracle).
|
||||
imp.config.TypeChecker.Error = func(e error) { fmt.Fprintln(os.Stderr, e) }
|
||||
imp.config.TypeChecker.Import = imp.doImport
|
||||
return imp
|
||||
}
|
||||
|
||||
// doImport loads the typechecker package identified by path
|
||||
// Implements the types.Importer prototype.
|
||||
// AllPackages returns a new slice containing all packages loaded by
|
||||
// importer imp.
|
||||
//
|
||||
func (imp *Importer) doImport(imports map[string]*types.Package, path string) (pkg *types.Package, err error) {
|
||||
func (imp *Importer) AllPackages() []*PackageInfo {
|
||||
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.
|
||||
// It implements the types.Importer prototype.
|
||||
//
|
||||
// imports is the import map of the importing package, later
|
||||
// accessible as types.Package.Imports(). If non-nil, doImport will
|
||||
// update it to include this import. (It may be nil in recursive
|
||||
// calls for prefetching.)
|
||||
//
|
||||
// It returns an error if a package could not be created
|
||||
// (e.g. go/build or parse error), but type errors are reported via
|
||||
// the types.Config.Error callback (the first of which is also saved
|
||||
// in the package's PackageInfo).
|
||||
//
|
||||
// Idempotent and thread-safe, but assumes that no two concurrent
|
||||
// calls will provide the same 'imports' map.
|
||||
//
|
||||
func (imp *Importer) doImport(imports map[string]*types.Package, path string) (*types.Package, error) {
|
||||
// Package unsafe is handled specially, and has no PackageInfo.
|
||||
// TODO(adonovan): a fake empty package would make things simpler.
|
||||
if path == "unsafe" {
|
||||
return types.Unsafe, nil
|
||||
}
|
||||
|
||||
// Load the source/binary for 'path', type-check it, construct
|
||||
// a PackageInfo and update our map (imp.Packages) and the
|
||||
// type-checker's map (imports).
|
||||
// TODO(adonovan): make it the type-checker's job to
|
||||
// consult/update the 'imports' map. Then we won't need it.
|
||||
var info *PackageInfo
|
||||
if imp.config.Build != nil {
|
||||
info, err = imp.LoadPackage(path)
|
||||
} else {
|
||||
if info, ok := imp.Packages[path]; ok {
|
||||
imports[path] = info.Pkg
|
||||
pkg = info.Pkg
|
||||
return // positive cache hit
|
||||
}
|
||||
|
||||
if err = imp.errors[path]; err != nil {
|
||||
return // negative cache hit
|
||||
}
|
||||
|
||||
if pkg, err = types.GcImport(imports, path); err == nil {
|
||||
info = &PackageInfo{Pkg: pkg}
|
||||
imp.Packages[path] = info
|
||||
}
|
||||
info, err := imp.doImport0(imports, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
// Cache success.
|
||||
pkg = info.Pkg
|
||||
imports[path] = pkg
|
||||
return pkg, nil
|
||||
if imports != nil {
|
||||
// Update the package's imports map, whether success or failure.
|
||||
//
|
||||
// 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().
|
||||
imports[path] = info.Pkg
|
||||
}
|
||||
|
||||
// Cache failure
|
||||
imp.errors[path] = err
|
||||
return nil, err
|
||||
return info.Pkg, nil
|
||||
}
|
||||
|
||||
// imports returns the set of paths imported by the specified files.
|
||||
func imports(p string, files []*ast.File) map[string]bool {
|
||||
imports := make(map[string]bool)
|
||||
outer:
|
||||
for _, file := range files {
|
||||
for _, decl := range file.Decls {
|
||||
if decl, ok := decl.(*ast.GenDecl); ok {
|
||||
if decl.Tok != token.IMPORT {
|
||||
break outer // stop at the first non-import
|
||||
}
|
||||
for _, spec := range decl.Specs {
|
||||
spec := spec.(*ast.ImportSpec)
|
||||
if path, _ := strconv.Unquote(spec.Path.Value); path != "C" {
|
||||
imports[path] = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break outer // stop at the first non-import
|
||||
}
|
||||
}
|
||||
}
|
||||
return imports
|
||||
}
|
||||
|
||||
// LoadPackage loads the package of the specified import path,
|
||||
// performs type-checking, and returns the corresponding
|
||||
// PackageInfo.
|
||||
//
|
||||
// Precondition: Importer.config.Build != nil.
|
||||
// Idempotent and thread-safe.
|
||||
//
|
||||
// TODO(adonovan): fix: violated in call from CreatePackageFromArgs!
|
||||
// TODO(adonovan): rethink this API.
|
||||
//
|
||||
func (imp *Importer) LoadPackage(importPath string) (*PackageInfo, error) {
|
||||
if imp.config.Build == nil {
|
||||
panic("Importer.LoadPackage without a go/build.Config")
|
||||
}
|
||||
|
||||
imp.mu.Lock()
|
||||
pi, ok := imp.packages[importPath]
|
||||
// Like doImport, but returns a PackageInfo.
|
||||
// Precondition: path != "unsafe".
|
||||
func (imp *Importer) doImport0(imports map[string]*types.Package, path string) (*PackageInfo, error) {
|
||||
imp.importedMu.Lock()
|
||||
ii, ok := imp.imported[path]
|
||||
if !ok {
|
||||
// First request for this pkgInfo:
|
||||
// create a new one in !ready state.
|
||||
pi = &pkgInfo{ready: make(chan struct{})}
|
||||
imp.packages[importPath] = pi
|
||||
imp.mu.Unlock()
|
||||
ii = &importInfo{path: path, ready: make(chan struct{})}
|
||||
imp.imported[path] = ii
|
||||
}
|
||||
imp.importedMu.Unlock()
|
||||
|
||||
// Load/parse the package.
|
||||
if files, err := loadPackage(imp.config.Build, imp.Fset, importPath); err == nil {
|
||||
// Kick off asynchronous I/O and parsing for the imports.
|
||||
for path := range imports(importPath, files) {
|
||||
go func(path string) { imp.LoadPackage(path) }(path)
|
||||
}
|
||||
|
||||
// Type-check the package.
|
||||
pi.info, pi.err = imp.CreateSourcePackage(importPath, files)
|
||||
if !ok {
|
||||
// Find and create the actual package.
|
||||
if imp.config.Build != nil {
|
||||
imp.importSource(path, ii)
|
||||
} else {
|
||||
pi.err = err
|
||||
imp.importBinary(imports, ii)
|
||||
}
|
||||
ii.info.Importable = true
|
||||
|
||||
close(pi.ready) // enter ready state and wake up waiters
|
||||
close(ii.ready) // enter ready state and wake up waiters
|
||||
} else {
|
||||
imp.mu.Unlock()
|
||||
|
||||
<-pi.ready // wait for ready condition.
|
||||
<-ii.ready // wait for ready condition
|
||||
}
|
||||
|
||||
// Invariant: pi is ready.
|
||||
// Invariant: ii is ready.
|
||||
|
||||
if pi.err != nil {
|
||||
return nil, pi.err
|
||||
}
|
||||
return pi.info, nil
|
||||
return ii.info, ii.err
|
||||
}
|
||||
|
||||
// CreateSourcePackage invokes the type-checker on files and returns a
|
||||
// PackageInfo containing the resulting type-checker package, the
|
||||
// ASTs, and other type information.
|
||||
// importBinary implements package loading from object files from the
|
||||
// gc compiler.
|
||||
//
|
||||
func (imp *Importer) importBinary(imports map[string]*types.Package, ii *importInfo) {
|
||||
pkg, err := types.GcImport(imports, ii.path)
|
||||
if pkg != nil {
|
||||
ii.info = &PackageInfo{Pkg: pkg}
|
||||
imp.addPackage(ii.info)
|
||||
} else {
|
||||
ii.err = err
|
||||
}
|
||||
}
|
||||
|
||||
// importSource implements package loading by parsing Go source files
|
||||
// located by go/build.
|
||||
//
|
||||
func (imp *Importer) importSource(path string, ii *importInfo) {
|
||||
which := "g" // load *.go files
|
||||
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.
|
||||
ii.info = imp.typeCheck(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 {
|
||||
ii.err = err
|
||||
}
|
||||
}
|
||||
|
||||
// typeCheck invokes the type-checker on files and returns a
|
||||
// PackageInfo containing the resulting types.Package, the ASTs, and
|
||||
// other type information.
|
||||
//
|
||||
// The order of files determines the package initialization order.
|
||||
//
|
||||
// importPath is the full name under which this package is known, such
|
||||
// as appears in an import declaration. e.g. "sync/atomic".
|
||||
// path is the full name under which this package is known, such as
|
||||
// appears in an import declaration. e.g. "sync/atomic". It need not
|
||||
// be unique; for example, it is possible to construct two distinct
|
||||
// packages both named "main".
|
||||
//
|
||||
// Postcondition: for result (info, err), info.Err == err.
|
||||
// The resulting package is added to imp.allPackages, but is not
|
||||
// importable unless it is inserted in the imp.imported map.
|
||||
//
|
||||
func (imp *Importer) CreateSourcePackage(importPath string, files []*ast.File) (*PackageInfo, error) {
|
||||
// This function always succeeds, but the package may contain type
|
||||
// errors; the first of these is recorded in PackageInfo.Err.
|
||||
//
|
||||
func (imp *Importer) typeCheck(path string, files []*ast.File) *PackageInfo {
|
||||
info := &PackageInfo{
|
||||
Files: files,
|
||||
Info: types.Info{
|
||||
|
|
@ -224,7 +272,221 @@ func (imp *Importer) CreateSourcePackage(importPath string, files []*ast.File) (
|
|||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
},
|
||||
}
|
||||
info.Pkg, info.Err = imp.config.TypeChecker.Check(importPath, imp.Fset, files, &info.Info)
|
||||
imp.Packages[importPath] = info
|
||||
return info, info.Err
|
||||
info.Pkg, info.Err = imp.config.TypeChecker.Check(path, imp.Fset, files, &info.Info)
|
||||
imp.addPackage(info)
|
||||
return info
|
||||
}
|
||||
|
||||
// LoadMainPackage creates and type-checks a package called "main" from
|
||||
// the specified list of parsed files, importing its dependencies.
|
||||
//
|
||||
// The resulting package is not importable, i.e. no 'import' spec can
|
||||
// resolve to it. LoadMainPackage is provided as an aid to testing.
|
||||
//
|
||||
// LoadMainPackage never fails, but the resulting package may contain
|
||||
// type errors.
|
||||
//
|
||||
func (imp *Importer) LoadMainPackage(files ...*ast.File) *PackageInfo {
|
||||
return imp.typeCheck("main", files)
|
||||
}
|
||||
|
||||
// InitialPackagesUsage is a partial usage message that client
|
||||
// applications may wish to include in their -help output.
|
||||
const InitialPackagesUsage = `
|
||||
<args> is a list of arguments denoting a set of initial pacakges.
|
||||
Each argument may take one of two forms:
|
||||
|
||||
1. A comma-separated list of *.go source files.
|
||||
|
||||
All of the specified files are loaded, parsed and type-checked
|
||||
as a single package. The name of the package is taken from the
|
||||
files' package declarations, which must all be equal. All the
|
||||
files must belong to the same directory.
|
||||
|
||||
2. An import path.
|
||||
|
||||
The package's directory is found relative to the $GOROOT and
|
||||
$GOPATH using similar logic to 'go build', and the *.go files in
|
||||
that directory are loaded and parsed, and type-checked as a single
|
||||
package.
|
||||
|
||||
In addition, all *_test.go files in the directory are then loaded
|
||||
and parsed. Those files whose package declaration equals that of
|
||||
the non-*_test.go files are included in the primary package. Test
|
||||
files whose package declaration ends with "_test" are type-checked
|
||||
as another package, the 'external' test package, so that a single
|
||||
import path may denote two packages. This behaviour may be
|
||||
disabled by prefixing the import path with "notest:",
|
||||
e.g. "notest:fmt".
|
||||
|
||||
Due to current limitations in the type-checker, only the first
|
||||
import path of the command line will contribute any tests.
|
||||
|
||||
A '--' argument terminates the list of packages.
|
||||
`
|
||||
|
||||
// LoadInitialPackages interprets args as a set of packages, loads
|
||||
// those packages and their dependencies, and returns them.
|
||||
//
|
||||
// It is intended for use in command-line interfaces that require a
|
||||
// set of initial packages to be specified; see InitialPackagesUsage
|
||||
// message for details.
|
||||
//
|
||||
// The second result parameter returns the list of unconsumed
|
||||
// arguments.
|
||||
//
|
||||
// It is an error to specify no packages.
|
||||
//
|
||||
// Precondition: LoadInitialPackages cannot be called after any
|
||||
// previous calls to Load* on the same importer.
|
||||
//
|
||||
func (imp *Importer) LoadInitialPackages(args []string) ([]*PackageInfo, []string, error) {
|
||||
// The "augmentation" mechanism requires that we mark all
|
||||
// packages to be augmented before we import a single one.
|
||||
if len(imp.allPackages) > 0 {
|
||||
return nil, nil, errors.New("LoadInitialPackages called on non-pristine Importer")
|
||||
}
|
||||
|
||||
// We use two passes. The first parses the files for each
|
||||
// non-importable package and discovers the set of importable
|
||||
// packages that require augmentation by in-package _test.go
|
||||
// files. The second creates the ad-hoc packages and imports
|
||||
// the importable ones.
|
||||
//
|
||||
// This is necessary to ensure that all packages requiring
|
||||
// augmentation are known before before any package is
|
||||
// imported.
|
||||
|
||||
// Pass 1: parse the sets of files for each package.
|
||||
var pkgs []*initialPkg
|
||||
for len(args) > 0 {
|
||||
arg := args[0]
|
||||
args = args[1:]
|
||||
if arg == "--" {
|
||||
break // consume "--" and return the remaining args
|
||||
}
|
||||
|
||||
if strings.HasSuffix(arg, ".go") {
|
||||
// Assume arg is a comma-separated list of *.go files
|
||||
// comprising a single package.
|
||||
pkg, err := initialPackageFromFiles(imp.Fset, arg)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pkgs = append(pkgs, pkg)
|
||||
|
||||
} else {
|
||||
// Assume arg is a directory name denoting a
|
||||
// package, perhaps plus an external test
|
||||
// package unless prefixed by "notest:".
|
||||
path := strings.TrimPrefix(arg, "notest:")
|
||||
|
||||
if path == "unsafe" {
|
||||
continue // ignore; has no PackageInfo
|
||||
}
|
||||
|
||||
pkg := &initialPkg{
|
||||
path: path,
|
||||
importable: true,
|
||||
}
|
||||
pkgs = append(pkgs, pkg)
|
||||
|
||||
if path != arg {
|
||||
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
|
||||
// checker design, we can augment at most one package.
|
||||
if len(imp.augment) > 0 {
|
||||
continue // don't attempt a second
|
||||
}
|
||||
|
||||
// Load the external test package.
|
||||
xtestFiles, err := parsePackageFiles(imp.config.Build, imp.Fset, path, "x")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(xtestFiles) > 0 {
|
||||
pkgs = append(pkgs, &initialPkg{
|
||||
path: path + "_test",
|
||||
importable: false,
|
||||
files: xtestFiles,
|
||||
})
|
||||
}
|
||||
|
||||
// Mark the non-xtest package for augmentation with
|
||||
// in-package *_test.go files when we import it below.
|
||||
imp.augment[pkg.path] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: type-check each set of files to make a package.
|
||||
var infos []*PackageInfo
|
||||
for _, pkg := range pkgs {
|
||||
var info *PackageInfo
|
||||
if pkg.importable {
|
||||
// import package
|
||||
var err error
|
||||
info, err = imp.doImport0(nil, pkg.path)
|
||||
if err != nil {
|
||||
return nil, nil, err // e.g. parse error (but not type error)
|
||||
}
|
||||
} else {
|
||||
// create package
|
||||
info = imp.typeCheck(pkg.path, pkg.files)
|
||||
}
|
||||
infos = append(infos, info)
|
||||
}
|
||||
|
||||
if len(pkgs) == 0 {
|
||||
return nil, nil, errors.New("no *.go source files nor packages were specified")
|
||||
}
|
||||
|
||||
return infos, args, nil
|
||||
}
|
||||
|
||||
type initialPkg struct {
|
||||
path string // the package's import path
|
||||
importable bool // add package to import map false for main and xtests)
|
||||
files []*ast.File // set of files (non-importable packages only)
|
||||
}
|
||||
|
||||
// initialPackageFromFiles returns an initialPkg, given a
|
||||
// comma-separated list of *.go source files belonging to the same
|
||||
// directory and possessing the same 'package decl'.
|
||||
//
|
||||
func initialPackageFromFiles(fset *token.FileSet, arg string) (*initialPkg, error) {
|
||||
filenames := strings.Split(arg, ",")
|
||||
for _, filename := range filenames {
|
||||
if !strings.HasSuffix(filename, ".go") {
|
||||
return nil, fmt.Errorf("not a *.go source file: %q", filename)
|
||||
}
|
||||
}
|
||||
|
||||
files, err := ParseFiles(fset, ".", filenames...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Take the package name from the 'package decl' in each file,
|
||||
// all of which must match.
|
||||
pkgname := files[0].Name.Name
|
||||
for i, file := range files[1:] {
|
||||
if pn := file.Name.Name; pn != pkgname {
|
||||
err := fmt.Errorf("can't load package: found packages %s (%s) and %s (%s)",
|
||||
pkgname, filenames[0], pn, filenames[i])
|
||||
return nil, err
|
||||
}
|
||||
// TODO(adonovan): check dirnames are equal, like 'go build' does.
|
||||
}
|
||||
|
||||
return &initialPkg{
|
||||
path: pkgname,
|
||||
importable: false,
|
||||
files: files,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
//
|
||||
type PackageInfo struct {
|
||||
Pkg *types.Package
|
||||
Importable bool // true if 'import "Pkg.Path()"' would resolve to this
|
||||
Err error // non-nil if the package had static errors
|
||||
Files []*ast.File // abstract syntax for the package's files
|
||||
types.Info // type-checker deductions.
|
||||
|
|
|
|||
|
|
@ -640,7 +640,7 @@ func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
|
|||
// The result is (nil, nil, false) if not found.
|
||||
//
|
||||
func (imp *Importer) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) {
|
||||
for _, info := range imp.Packages {
|
||||
for _, info := range imp.allPackages {
|
||||
for _, f := range info.Files {
|
||||
if !tokenFileContainsPos(imp.Fset.File(f.Package), start) {
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -249,17 +249,13 @@ func TestEnclosingFunction(t *testing.T) {
|
|||
t.Errorf("EnclosingFunction(%q) not exact", test.substr)
|
||||
continue
|
||||
}
|
||||
info, err := imp.CreateSourcePackage("main", []*ast.File{f})
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
mainInfo := imp.LoadMainPackage(f)
|
||||
prog := ssa.NewProgram(imp.Fset, 0)
|
||||
if err := prog.CreatePackages(imp); err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
prog := ssa.NewProgram(imp.Fset, 0)
|
||||
for _, info := range imp.Packages {
|
||||
prog.CreatePackage(info)
|
||||
}
|
||||
pkg := prog.Package(info.Pkg)
|
||||
pkg := prog.Package(mainInfo.Pkg)
|
||||
pkg.Build()
|
||||
|
||||
name := "(none)"
|
||||
|
|
|
|||
|
|
@ -8,55 +8,16 @@ package importer
|
|||
// and used by it.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// CreatePackageFromArgs builds an initial Package from a list of
|
||||
// command-line arguments.
|
||||
// If args is a list of *.go files, they are parsed and type-checked.
|
||||
// If args is a Go package import path, that package is imported.
|
||||
// The rest result contains the suffix of args that were not consumed.
|
||||
//
|
||||
// This utility is provided to facilitate construction of command-line
|
||||
// tools with a consistent user interface.
|
||||
//
|
||||
func CreatePackageFromArgs(imp *Importer, args []string) (info *PackageInfo, rest []string, err error) {
|
||||
switch {
|
||||
case len(args) == 0:
|
||||
return nil, nil, errors.New("no *.go source files nor package name was specified.")
|
||||
|
||||
case strings.HasSuffix(args[0], ".go"):
|
||||
// % tool a.go b.go ...
|
||||
// Leading consecutive *.go arguments constitute main package.
|
||||
i := 1
|
||||
for ; i < len(args) && strings.HasSuffix(args[i], ".go"); i++ {
|
||||
}
|
||||
var files []*ast.File
|
||||
files, err = ParseFiles(imp.Fset, ".", args[:i]...)
|
||||
rest = args[i:]
|
||||
if err == nil {
|
||||
info, err = imp.CreateSourcePackage("main", files)
|
||||
}
|
||||
|
||||
default:
|
||||
// % tool my/package ...
|
||||
// First argument is import path of main package.
|
||||
pkgname := args[0]
|
||||
info, err = imp.LoadPackage(pkgname)
|
||||
rest = args[1:]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var cwd string
|
||||
|
||||
func init() {
|
||||
|
|
@ -67,9 +28,15 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
// loadPackage ascertains which files belong to package path, then
|
||||
// loads, parses and returns them.
|
||||
func loadPackage(ctxt *build.Context, fset *token.FileSet, path string) (files []*ast.File, err error) {
|
||||
// parsePackageFiles enumerates the files belonging to package path,
|
||||
// then loads, parses and returns them.
|
||||
//
|
||||
// 'which' is a list of flags indicating which files to include:
|
||||
// 'g': include non-test *.go source files (GoFiles)
|
||||
// 't': include in-package *_test.go source files (TestGoFiles)
|
||||
// 'x': include external *_test.go source files. (XTestGoFiles)
|
||||
//
|
||||
func parsePackageFiles(ctxt *build.Context, fset *token.FileSet, path string, which string) ([]*ast.File, error) {
|
||||
// Set the "!cgo" go/build tag, preferring (dummy) Go to
|
||||
// native C implementations of net.cgoLookupHost et al.
|
||||
ctxt2 := *ctxt
|
||||
|
|
@ -82,9 +49,25 @@ func loadPackage(ctxt *build.Context, fset *token.FileSet, path string) (files [
|
|||
return nil, nil // empty directory
|
||||
}
|
||||
if err != nil {
|
||||
return // import failed
|
||||
return nil, err // import failed
|
||||
}
|
||||
return ParseFiles(fset, bp.Dir, bp.GoFiles...)
|
||||
|
||||
var filenames []string
|
||||
for _, c := range which {
|
||||
var s []string
|
||||
switch c {
|
||||
case 'g':
|
||||
s = bp.GoFiles
|
||||
case 't':
|
||||
s = bp.TestGoFiles
|
||||
case 'x':
|
||||
s = bp.XTestGoFiles
|
||||
default:
|
||||
panic(c)
|
||||
}
|
||||
filenames = append(filenames, s...)
|
||||
}
|
||||
return ParseFiles(fset, bp.Dir, filenames...)
|
||||
}
|
||||
|
||||
// ParseFiles parses the Go source files files within directory dir
|
||||
|
|
@ -132,3 +115,27 @@ func unparen(e ast.Expr) ast.Expr {
|
|||
func unreachable() {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// importsOf returns the set of paths imported by the specified files.
|
||||
func importsOf(p string, files []*ast.File) map[string]bool {
|
||||
imports := make(map[string]bool)
|
||||
outer:
|
||||
for _, file := range files {
|
||||
for _, decl := range file.Decls {
|
||||
if decl, ok := decl.(*ast.GenDecl); ok {
|
||||
if decl.Tok != token.IMPORT {
|
||||
break outer // stop at the first non-import
|
||||
}
|
||||
for _, spec := range decl.Specs {
|
||||
spec := spec.(*ast.ImportSpec)
|
||||
if path, _ := strconv.Unquote(spec.Path.Value); path != "C" {
|
||||
imports[path] = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break outer // stop at the first non-import
|
||||
}
|
||||
}
|
||||
}
|
||||
return imports
|
||||
}
|
||||
|
|
|
|||
|
|
@ -693,22 +693,41 @@ func (r *describeTypeResult) toJSON(res *json.Result, fset *token.FileSet) {
|
|||
|
||||
func describePackage(o *oracle, path []ast.Node) (*describePackageResult, error) {
|
||||
var description string
|
||||
var pkg *ssa.Package
|
||||
var importPath string
|
||||
switch n := path[0].(type) {
|
||||
case *ast.ImportSpec:
|
||||
// importPath = o.queryPkgInfo.ObjectOf(n.Name).(*types.Package).Path()
|
||||
// description = "import of package " + importPath
|
||||
// TODO(gri): o.queryPkgInfo.ObjectOf(n.Name) may be nil.
|
||||
// e.g. "fmt" import in cmd/oracle/main.go. Why?
|
||||
// Workaround:
|
||||
// Most ImportSpecs have no .Name Ident so we can't
|
||||
// use ObjectOf.
|
||||
// We could use the types.Info.Implicits mechanism,
|
||||
// but it's easier just to look it up by name.
|
||||
description = "import of package " + n.Path.Value
|
||||
importPath, _ = strconv.Unquote(n.Path.Value)
|
||||
pkg = o.prog.ImportedPackage(importPath)
|
||||
|
||||
case *ast.Ident:
|
||||
importPath = o.queryPkgInfo.ObjectOf(n).(*types.Package).Path()
|
||||
obj := o.queryPkgInfo.ObjectOf(n).(*types.Package)
|
||||
importPath = obj.Path()
|
||||
|
||||
if _, isDef := path[1].(*ast.File); isDef {
|
||||
// e.g. package id
|
||||
pkg = o.prog.Package(obj)
|
||||
description = fmt.Sprintf("definition of package %q", importPath)
|
||||
} else {
|
||||
// e.g. import id
|
||||
// or id.F()
|
||||
|
||||
// TODO(gri): go/types internally creates a new
|
||||
// Package object for each import, so the packages
|
||||
// for 'package x' and 'import "x"' differ!
|
||||
//
|
||||
// Here, this should be an invariant, but is not:
|
||||
// o.prog.ImportedPackage(obj.Path()).Pkg == obj
|
||||
//
|
||||
// So we must use the name of the non-canonical package
|
||||
// to do another lookup.
|
||||
pkg = o.prog.ImportedPackage(importPath)
|
||||
|
||||
description = fmt.Sprintf("reference to package %q", importPath)
|
||||
}
|
||||
if importPath == "" {
|
||||
|
|
@ -722,8 +741,9 @@ func describePackage(o *oracle, path []ast.Node) (*describePackageResult, error)
|
|||
}
|
||||
|
||||
var members []*describeMember
|
||||
// NB: package "unsafe" has no object.
|
||||
if pkg := o.prog.PackagesByPath[importPath]; pkg != nil {
|
||||
// NB: "unsafe" has no ssa.Package
|
||||
// TODO(adonovan): simplify by using types.Packages not ssa.Packages.
|
||||
if pkg != nil {
|
||||
// Compute set of exported package members in lexicographic order.
|
||||
var names []string
|
||||
for name := range pkg.Members {
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
|
|||
minfo, ok := modes[mode]
|
||||
if !ok {
|
||||
if mode == "" {
|
||||
return nil, errors.New("you must specify a -mode to perform")
|
||||
return nil, errors.New("you must specify a -mode of query to perform")
|
||||
}
|
||||
return nil, fmt.Errorf("invalid mode type: %q", mode)
|
||||
}
|
||||
|
|
@ -155,9 +155,12 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
|
|||
|
||||
// Load/parse/type-check program from args.
|
||||
start := time.Now()
|
||||
initialPkgInfo, _, err := importer.CreatePackageFromArgs(imp, args)
|
||||
initialPkgInfos, args, err := imp.LoadInitialPackages(args)
|
||||
if err != nil {
|
||||
return nil, err // I/O, parser or type error
|
||||
return nil, err // I/O or parser error
|
||||
}
|
||||
if len(args) > 0 {
|
||||
return nil, fmt.Errorf("surplus arguments: %q", args)
|
||||
}
|
||||
o.timers["load/parse/type"] = time.Since(start)
|
||||
|
||||
|
|
@ -184,21 +187,26 @@ func Query(args []string, mode, pos string, ptalog io.Writer, buildContext *buil
|
|||
if minfo.needs&SSA != 0 {
|
||||
start = time.Now()
|
||||
|
||||
// All packages.
|
||||
for _, info := range imp.Packages {
|
||||
o.prog.CreatePackage(info) // create ssa.Package
|
||||
// Create SSA packages.
|
||||
if err := o.prog.CreatePackages(imp); err != nil {
|
||||
return nil, o.errorf(false, "%s", err)
|
||||
}
|
||||
|
||||
// Initial package (specified on command line)
|
||||
initialPkg := o.prog.Package(initialPkgInfo.Pkg)
|
||||
// Initial packages (specified on command line)
|
||||
for _, info := range initialPkgInfos {
|
||||
initialPkg := o.prog.Package(info.Pkg)
|
||||
|
||||
// Add package to the pointer analysis scope.
|
||||
if initialPkg.Func("main") == nil {
|
||||
if initialPkg.CreateTestMainFunction() == nil {
|
||||
return nil, o.errorf(false, "analysis scope has no main() entry points")
|
||||
// Add package to the pointer analysis scope.
|
||||
if initialPkg.Func("main") == nil {
|
||||
// TODO(adonovan): to simulate 'go test' more faithfully, we
|
||||
// should build a single synthetic testmain package,
|
||||
// not synthetic main functions to many packages.
|
||||
if initialPkg.CreateTestMainFunction() == nil {
|
||||
return nil, o.errorf(false, "analysis scope has no main() entry points")
|
||||
}
|
||||
}
|
||||
o.config.Mains = append(o.config.Mains, initialPkg)
|
||||
}
|
||||
o.config.Mains = append(o.config.Mains, initialPkg)
|
||||
|
||||
// Query package.
|
||||
if o.queryPkgInfo != nil {
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
{
|
||||
"mode": "describe",
|
||||
"describe": {
|
||||
"desc": "definition of package \"main\"",
|
||||
"desc": "definition of package \"describe\"",
|
||||
"pos": "testdata/src/main/describe-json.go:1:9",
|
||||
"detail": "package",
|
||||
"package": {
|
||||
"path": "main",
|
||||
"path": "describe",
|
||||
"members": [
|
||||
{
|
||||
"name": "C",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
-------- @describe pkgdecl --------
|
||||
definition of package "main"
|
||||
definition of package "describe"
|
||||
type C int
|
||||
method (*describe.C) f()
|
||||
type D struct{}
|
||||
|
|
@ -126,7 +126,7 @@ binary - operation of constant value -2
|
|||
|
||||
-------- @describe map-lookup,ok --------
|
||||
index expression of type (*int, bool)
|
||||
no points-to information: can't locate SSA Value for expression in main.main
|
||||
no points-to information: can't locate SSA Value for expression in describe.main
|
||||
|
||||
-------- @describe mapval --------
|
||||
reference to var mapval *int
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ defined here
|
|||
reference to var p *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
main.a
|
||||
imports.a
|
||||
|
||||
-------- @describe ref-pkg --------
|
||||
reference to package "lib"
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ This channel of type chan *int may be:
|
|||
reference to var rA *int
|
||||
defined here
|
||||
value may point to these labels:
|
||||
main.a2
|
||||
peers.a2
|
||||
a1
|
||||
|
||||
-------- @peers peer-recv-chB --------
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@ func Analyze(config *Config) CallGraphNode {
|
|||
work: makeMapWorklist(),
|
||||
}
|
||||
|
||||
if reflect := a.prog.PackagesByPath["reflect"]; reflect != nil {
|
||||
if reflect := a.prog.ImportedPackage("reflect"); reflect != nil {
|
||||
a.reflectValueObj = reflect.Object.Scope().Lookup("Value")
|
||||
a.reflectRtypeObj = reflect.Object.Scope().Lookup("rtype")
|
||||
a.reflectRtype = types.NewPointer(a.reflectRtypeObj.Type())
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ package pointer_test
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
|
|
@ -171,21 +170,18 @@ func doOneInput(input, filename string) bool {
|
|||
if err != nil {
|
||||
// TODO(adonovan): err is a scanner error list;
|
||||
// display all errors not just first?
|
||||
fmt.Println(err.Error())
|
||||
fmt.Println(err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Type checking.
|
||||
info, err := imp.CreateSourcePackage("main", []*ast.File{f})
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return false
|
||||
}
|
||||
// Load main package and its dependencies.
|
||||
info := imp.LoadMainPackage(f)
|
||||
|
||||
// SSA creation + building.
|
||||
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
||||
for _, info := range imp.Packages {
|
||||
prog.CreatePackage(info)
|
||||
if err := prog.CreatePackages(imp); err != nil {
|
||||
fmt.Println(err)
|
||||
return false
|
||||
}
|
||||
prog.BuildAll()
|
||||
|
||||
|
|
@ -522,7 +518,7 @@ func TestInput(t *testing.T) {
|
|||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Errorf("os.Getwd: %s", err.Error())
|
||||
t.Errorf("os.Getwd: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -535,7 +531,7 @@ func TestInput(t *testing.T) {
|
|||
for _, filename := range inputs {
|
||||
content, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Errorf("couldn't read file '%s': %s", filename, err.Error())
|
||||
t.Errorf("couldn't read file '%s': %s", filename, err)
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2305,7 +2305,7 @@ func (b *builder) buildFuncDecl(pkg *Package, decl *ast.FuncDecl) {
|
|||
//
|
||||
func (prog *Program) BuildAll() {
|
||||
var wg sync.WaitGroup
|
||||
for _, p := range prog.PackagesByPath {
|
||||
for _, p := range prog.packages {
|
||||
if prog.mode&BuildSerially != 0 {
|
||||
p.Build()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
package ssa_test
|
||||
|
||||
import (
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
"code.google.com/p/go.tools/importer"
|
||||
"code.google.com/p/go.tools/ssa"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
"code.google.com/p/go.tools/importer"
|
||||
"code.google.com/p/go.tools/ssa"
|
||||
)
|
||||
|
||||
func isEmpty(f *ssa.Function) bool { return f.Blocks == nil }
|
||||
|
|
@ -38,7 +38,7 @@ func main() {
|
|||
w.Write(nil) // interface invoke of external declared method
|
||||
}
|
||||
`
|
||||
imp := importer.New(new(importer.Config)) // no Loader; uses GC importer
|
||||
imp := importer.New(new(importer.Config)) // no go/build.Context; uses GC importer
|
||||
|
||||
f, err := parser.ParseFile(imp.Fset, "<input>", test, parser.DeclarationErrors)
|
||||
if err != nil {
|
||||
|
|
@ -46,26 +46,24 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
info, err := imp.CreateSourcePackage("main", []*ast.File{f})
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
mainInfo := imp.LoadMainPackage(f)
|
||||
|
||||
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
||||
for _, info := range imp.Packages {
|
||||
prog.CreatePackage(info)
|
||||
if err := prog.CreatePackages(imp); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
mainPkg := prog.Package(info.Pkg)
|
||||
mainPkg := prog.Package(mainInfo.Pkg)
|
||||
mainPkg.Build()
|
||||
|
||||
// Only the main package and its immediate dependencies are loaded.
|
||||
deps := []string{"bytes", "io", "testing"}
|
||||
if len(prog.PackagesByPath) != 1+len(deps) {
|
||||
t.Errorf("unexpected set of loaded packages: %q", prog.PackagesByPath)
|
||||
all := prog.AllPackages()
|
||||
if len(all) != 1+len(deps) {
|
||||
t.Errorf("unexpected set of loaded packages: %q", all)
|
||||
}
|
||||
for _, path := range deps {
|
||||
pkg, _ := prog.PackagesByPath[path]
|
||||
pkg := prog.ImportedPackage(path)
|
||||
if pkg == nil {
|
||||
t.Errorf("package not loaded: %q", path)
|
||||
continue
|
||||
|
|
@ -97,11 +95,6 @@ func main() {
|
|||
if !isExt {
|
||||
t.Fatalf("unexpected name type in main package: %s", mem)
|
||||
}
|
||||
if _, ok := mem.Type().Underlying().(*types.Interface); ok {
|
||||
// TODO(adonovan): workaround bug in types.MethodSet
|
||||
// whereby mset(*I) is nonempty if I is an interface.
|
||||
continue
|
||||
}
|
||||
mset := types.NewPointer(mem.Type()).MethodSet()
|
||||
for i, n := 0, mset.Len(); i < n; i++ {
|
||||
m := prog.Method(mset.At(i))
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"go/ast"
|
||||
"go/token"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"code.google.com/p/go.tools/go/types"
|
||||
"code.google.com/p/go.tools/importer"
|
||||
|
|
@ -40,7 +41,7 @@ const (
|
|||
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
|
||||
prog := &Program{
|
||||
Fset: fset,
|
||||
PackagesByPath: make(map[string]*Package),
|
||||
imported: make(map[string]*Package),
|
||||
packages: make(map[*types.Package]*Package),
|
||||
builtins: make(map[types.Object]*Builtin),
|
||||
boundMethodWrappers: make(map[*types.Func]*Function),
|
||||
|
|
@ -246,7 +247,9 @@ func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
|
|||
p.DumpTo(os.Stderr)
|
||||
}
|
||||
|
||||
prog.PackagesByPath[info.Pkg.Path()] = p
|
||||
if info.Importable {
|
||||
prog.imported[info.Pkg.Path()] = p
|
||||
}
|
||||
prog.packages[p.Object] = p
|
||||
|
||||
if prog.mode&SanityCheckFunctions != 0 {
|
||||
|
|
@ -255,3 +258,48 @@ func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
|
|||
|
||||
return p
|
||||
}
|
||||
|
||||
// CreatePackages creates SSA Packages for all error-free packages
|
||||
// loaded by the specified Importer.
|
||||
//
|
||||
// If all packages were error-free, it is safe to call
|
||||
// prog.BuildAll(), and nil is returned. Otherwise an error is
|
||||
// returned.
|
||||
//
|
||||
func (prog *Program) CreatePackages(imp *importer.Importer) error {
|
||||
var errpkgs []string
|
||||
for _, info := range imp.AllPackages() {
|
||||
if info.Err != nil {
|
||||
errpkgs = append(errpkgs, info.Pkg.Path())
|
||||
} else {
|
||||
prog.CreatePackage(info)
|
||||
}
|
||||
}
|
||||
if errpkgs != nil {
|
||||
return fmt.Errorf("couldn't create these SSA packages due to type errors: %s",
|
||||
strings.Join(errpkgs, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllPackages returns a new slice containing all packages in the
|
||||
// program prog in unspecified order.
|
||||
//
|
||||
func (prog *Program) AllPackages() []*Package {
|
||||
pkgs := make([]*Package, 0, len(prog.packages))
|
||||
for _, pkg := range prog.packages {
|
||||
pkgs = append(pkgs, pkg)
|
||||
}
|
||||
return pkgs
|
||||
}
|
||||
|
||||
// ImportedPackage returns the importable SSA Package whose import
|
||||
// path is path, or nil if no such SSA 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'.
|
||||
//
|
||||
func (prog *Program) ImportedPackage(path string) *Package {
|
||||
return prog.imported[path]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ package ssa_test
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"os"
|
||||
|
|
@ -48,24 +47,21 @@ func main() {
|
|||
// Parse the input file.
|
||||
file, err := parser.ParseFile(imp.Fset, "hello.go", hello, parser.DeclarationErrors)
|
||||
if err != nil {
|
||||
fmt.Print(err.Error()) // parse error
|
||||
fmt.Print(err) // parse error
|
||||
return
|
||||
}
|
||||
|
||||
// Create a "main" package containing one file.
|
||||
info, err := imp.CreateSourcePackage("main", []*ast.File{file})
|
||||
if err != nil {
|
||||
fmt.Print(err.Error()) // type error
|
||||
return
|
||||
}
|
||||
mainInfo := imp.LoadMainPackage(file)
|
||||
|
||||
// Create SSA-form program representation.
|
||||
var mode ssa.BuilderMode
|
||||
prog := ssa.NewProgram(imp.Fset, mode)
|
||||
for _, info := range imp.Packages {
|
||||
prog.CreatePackage(info)
|
||||
if err := prog.CreatePackages(imp); err != nil {
|
||||
fmt.Print(err) // type error in some package
|
||||
return
|
||||
}
|
||||
mainPkg := prog.Package(info.Pkg)
|
||||
mainPkg := prog.Package(mainInfo.Pkg)
|
||||
|
||||
// Print out the package.
|
||||
mainPkg.DumpTo(os.Stdout)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package ssa/interp defines an interpreter for the SSA
|
||||
// representation of Go programs.
|
||||
//
|
||||
|
|
@ -390,10 +394,11 @@ func prepareCall(fr *frame, call *ssa.CallCommon) (fn value, args []value) {
|
|||
if recv.t == nil {
|
||||
panic("method invoked on nil interface")
|
||||
}
|
||||
fn = lookupMethod(fr.i, recv.t, call.Method)
|
||||
if fn == nil {
|
||||
if f := lookupMethod(fr.i, recv.t, call.Method); f == nil {
|
||||
// Unreachable in well-typed programs.
|
||||
panic(fmt.Sprintf("method set for dynamic type %v does not contain %s", recv.t, call.Method))
|
||||
} else {
|
||||
fn = f
|
||||
}
|
||||
args = append(args, copyVal(recv.v))
|
||||
}
|
||||
|
|
@ -545,7 +550,7 @@ func Interpret(mainpkg *ssa.Package, mode Mode, filename string, args []string)
|
|||
}
|
||||
initReflect(i)
|
||||
|
||||
for importPath, pkg := range i.prog.PackagesByPath {
|
||||
for _, pkg := range i.prog.AllPackages() {
|
||||
// Initialize global storage.
|
||||
for _, m := range pkg.Members {
|
||||
switch v := m.(type) {
|
||||
|
|
@ -556,7 +561,7 @@ func Interpret(mainpkg *ssa.Package, mode Mode, filename string, args []string)
|
|||
}
|
||||
|
||||
// Ad-hoc initialization for magic system variables.
|
||||
switch importPath {
|
||||
switch pkg.Object.Path() {
|
||||
case "syscall":
|
||||
var envs []value
|
||||
for _, s := range os.Environ() {
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ func run(t *testing.T, dir, input string) bool {
|
|||
}
|
||||
|
||||
imp := importer.New(&importer.Config{Build: &build.Default})
|
||||
// TODO(adonovan): use LoadInitialPackages, then un-export ParseFiles.
|
||||
files, err := importer.ParseFiles(imp.Fset, ".", inputs...)
|
||||
if err != nil {
|
||||
t.Errorf("ssa.ParseFiles(%s) failed: %s", inputs, err.Error())
|
||||
|
|
@ -184,19 +185,16 @@ func run(t *testing.T, dir, input string) 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)
|
||||
info, err := imp.CreateSourcePackage("main", files)
|
||||
if err != nil {
|
||||
t.Errorf("importer.CreateSourcePackage(%s) failed: %s", inputs, err)
|
||||
return false
|
||||
}
|
||||
mainInfo := imp.LoadMainPackage(files...)
|
||||
|
||||
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
||||
for _, info := range imp.Packages {
|
||||
prog.CreatePackage(info)
|
||||
if err := prog.CreatePackages(imp); err != nil {
|
||||
t.Errorf("CreatePackages failed: %s", err)
|
||||
return false
|
||||
}
|
||||
prog.BuildAll()
|
||||
|
||||
mainPkg := prog.Package(info.Pkg)
|
||||
mainPkg := prog.Package(mainInfo.Pkg)
|
||||
mainPkg.CreateTestMainFunction() // (no-op if main already exists)
|
||||
|
||||
hint = fmt.Sprintf("To trace execution, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump; ./ssadump -build=C -run --interp=T %s\n", input)
|
||||
|
|
|
|||
|
|
@ -46,17 +46,14 @@ func TestObjValueLookup(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
info, err := imp.CreateSourcePackage("main", []*ast.File{f})
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
mainInfo := imp.LoadMainPackage(f)
|
||||
|
||||
prog := ssa.NewProgram(imp.Fset, 0 /*|ssa.LogFunctions*/)
|
||||
for _, info := range imp.Packages {
|
||||
prog.CreatePackage(info)
|
||||
if err := prog.CreatePackages(imp); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
mainPkg := prog.Package(info.Pkg)
|
||||
mainPkg := prog.Package(mainInfo.Pkg)
|
||||
mainPkg.SetDebugMode(true)
|
||||
mainPkg.Build()
|
||||
|
||||
|
|
@ -66,7 +63,7 @@ func TestObjValueLookup(t *testing.T) {
|
|||
ast.Inspect(f, func(n ast.Node) bool {
|
||||
if id, ok := n.(*ast.Ident); ok {
|
||||
ids = append(ids, id)
|
||||
if obj := info.ObjectOf(id); obj != nil {
|
||||
if obj := mainInfo.ObjectOf(id); obj != nil {
|
||||
objs[obj] = true
|
||||
}
|
||||
}
|
||||
|
|
@ -87,7 +84,7 @@ func TestObjValueLookup(t *testing.T) {
|
|||
// Check invariants for var objects.
|
||||
// The result varies based on the specific Ident.
|
||||
for _, id := range ids {
|
||||
if obj, ok := info.ObjectOf(id).(*types.Var); ok {
|
||||
if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok {
|
||||
ref, _ := importer.PathEnclosingInterval(f, id.Pos(), id.Pos())
|
||||
pos := imp.Fset.Position(id.Pos())
|
||||
exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
|
||||
|
|
@ -197,17 +194,14 @@ func TestValueForExpr(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
info, err := imp.CreateSourcePackage("main", []*ast.File{f})
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
mainInfo := imp.LoadMainPackage(f)
|
||||
|
||||
prog := ssa.NewProgram(imp.Fset, 0)
|
||||
for _, info := range imp.Packages {
|
||||
prog.CreatePackage(info)
|
||||
if err := prog.CreatePackages(imp); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
mainPkg := prog.Package(info.Pkg)
|
||||
mainPkg := prog.Package(mainInfo.Pkg)
|
||||
mainPkg.SetDebugMode(true)
|
||||
mainPkg.Build()
|
||||
|
||||
|
|
@ -251,8 +245,8 @@ func TestValueForExpr(t *testing.T) {
|
|||
t.Errorf("%s: got value %q, want %q", position, got, want)
|
||||
}
|
||||
if v != nil {
|
||||
if !types.IsIdentical(v.Type(), info.TypeOf(e)) {
|
||||
t.Errorf("%s: got type %s, want %s", position, info.TypeOf(e), v.Type())
|
||||
if !types.IsIdentical(v.Type(), mainInfo.TypeOf(e)) {
|
||||
t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), v.Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
ssa/ssa.go
10
ssa/ssa.go
|
|
@ -22,11 +22,11 @@ import (
|
|||
// A Program is a partial or complete Go program converted to SSA form.
|
||||
//
|
||||
type Program struct {
|
||||
Fset *token.FileSet // position information for the files of this Program
|
||||
PackagesByPath map[string]*Package // all loaded Packages, keyed by import path
|
||||
packages map[*types.Package]*Package // all loaded Packages, keyed by object
|
||||
builtins map[types.Object]*Builtin // all built-in functions, keyed by typechecker objects.
|
||||
mode BuilderMode // set of mode bits for SSA construction
|
||||
Fset *token.FileSet // position information for the files of this Program
|
||||
imported map[string]*Package // all importable Packages, keyed by import path
|
||||
packages map[*types.Package]*Package // all loaded Packages, keyed by object
|
||||
builtins map[types.Object]*Builtin // all built-in functions, keyed by typechecker objects.
|
||||
mode BuilderMode // set of mode bits for SSA construction
|
||||
|
||||
methodsMu sync.Mutex // guards the following maps:
|
||||
methodSets typemap.M // maps type to its concrete methodSet
|
||||
|
|
|
|||
|
|
@ -55,13 +55,11 @@ func TestStdlib(t *testing.T) {
|
|||
// Load, parse and type-check the program.
|
||||
t0 := time.Now()
|
||||
|
||||
var hasErrors bool
|
||||
imp := importer.New(&impctx)
|
||||
for _, importPath := range allPackages() {
|
||||
if _, err := imp.LoadPackage(importPath); err != nil {
|
||||
t.Errorf("LoadPackage(%s): %s", importPath, err)
|
||||
hasErrors = true
|
||||
}
|
||||
|
||||
if _, _, err := imp.LoadInitialPackages(allPackages()); err != nil {
|
||||
t.Errorf("LoadInitialPackages failed: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
t1 := time.Now()
|
||||
|
|
@ -73,25 +71,26 @@ func TestStdlib(t *testing.T) {
|
|||
|
||||
// Create SSA packages.
|
||||
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
||||
for _, info := range imp.Packages {
|
||||
if info.Err == nil {
|
||||
prog.CreatePackage(info).SetDebugMode(debugMode)
|
||||
}
|
||||
if err := prog.CreatePackages(imp); err != nil {
|
||||
t.Errorf("CreatePackages failed: %s", err)
|
||||
return
|
||||
}
|
||||
// Enable debug mode globally.
|
||||
for _, info := range imp.AllPackages() {
|
||||
prog.Package(info.Pkg).SetDebugMode(debugMode)
|
||||
}
|
||||
|
||||
t2 := time.Now()
|
||||
|
||||
// Build SSA IR... if it's safe.
|
||||
if !hasErrors {
|
||||
prog.BuildAll()
|
||||
}
|
||||
prog.BuildAll()
|
||||
|
||||
t3 := time.Now()
|
||||
|
||||
runtime.GC()
|
||||
runtime.ReadMemStats(&memstats)
|
||||
|
||||
numPkgs := len(prog.PackagesByPath)
|
||||
numPkgs := len(prog.AllPackages())
|
||||
if want := 140; numPkgs < want {
|
||||
t.Errorf("Loaded only %d packages, want at least %d", numPkgs, want)
|
||||
}
|
||||
|
|
@ -108,9 +107,7 @@ func TestStdlib(t *testing.T) {
|
|||
t.Log("GOMAXPROCS: ", runtime.GOMAXPROCS(0))
|
||||
t.Log("Load/parse/typecheck: ", t1.Sub(t0))
|
||||
t.Log("SSA create: ", t2.Sub(t1))
|
||||
if !hasErrors {
|
||||
t.Log("SSA build: ", t3.Sub(t2))
|
||||
}
|
||||
t.Log("SSA build: ", t3.Sub(t2))
|
||||
|
||||
// SSA stats:
|
||||
t.Log("#Packages: ", numPkgs)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func (pkg *Package) CreateTestMainFunction() *Function {
|
|||
Pkg: pkg,
|
||||
}
|
||||
|
||||
testingPkg := pkg.Prog.PackagesByPath["testing"]
|
||||
testingPkg := pkg.Prog.ImportedPackage("testing")
|
||||
if testingPkg == nil {
|
||||
// If it doesn't import "testing", it can't be a test.
|
||||
// TODO(adonovan): but it might contain Examples.
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ type visitor struct {
|
|||
}
|
||||
|
||||
func (visit *visitor) program() {
|
||||
for _, pkg := range visit.prog.PackagesByPath {
|
||||
for _, pkg := range visit.prog.AllPackages() {
|
||||
for _, mem := range pkg.Members {
|
||||
switch mem := mem.(type) {
|
||||
case *Function:
|
||||
|
|
|
|||
Loading…
Reference in New Issue