diff --git a/go/ssa/ssautil/load14.go b/go/ssa/ssautil/load14.go deleted file mode 100644 index 752a9d9a43..0000000000 --- a/go/ssa/ssautil/load14.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2015 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. - -// +build !go1.5 - -package ssautil - -// This file defines utility functions for constructing programs in SSA form. - -import ( - "go/ast" - "go/token" - - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/types" -) - -// CreateProgram returns a new program in SSA form, given a program -// loaded from source. An SSA package is created for each transitively -// error-free package of lprog. -// -// Code for bodies of functions is not built until BuildAll() is called -// on the result. -// -// mode controls diagnostics and checking during SSA construction. -// -func CreateProgram(lprog *loader.Program, mode ssa.BuilderMode) *ssa.Program { - prog := ssa.NewProgram(lprog.Fset, mode) - - for _, info := range lprog.AllPackages { - if info.TransitivelyErrorFree { - prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable) - } - } - - return prog -} - -// BuildPackage builds an SSA program with IR for a single package. -// -// It populates pkg by type-checking the specified file ASTs. All -// dependencies are loaded using the importer specified by tc, which -// typically loads compiler export data; SSA code cannot be built for -// those packages. BuildPackage then constructs an ssa.Program with all -// dependency packages created, and builds and returns the SSA package -// corresponding to pkg. -// -// The caller must have set pkg.Path() to the import path. -// -// The operation fails if there were any type-checking or import errors. -// -// See ../ssa/example_test.go for an example. -// -func BuildPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ssa.BuilderMode) (*ssa.Package, *types.Info, error) { - if fset == nil { - panic("no token.FileSet") - } - if pkg.Path() == "" { - panic("package has no import path") - } - - info := &types.Info{ - Types: make(map[ast.Expr]types.TypeAndValue), - Defs: make(map[*ast.Ident]types.Object), - Uses: make(map[*ast.Ident]types.Object), - Implicits: make(map[ast.Node]types.Object), - Scopes: make(map[ast.Node]*types.Scope), - Selections: make(map[*ast.SelectorExpr]*types.Selection), - } - if err := types.NewChecker(tc, fset, pkg, info).Files(files); err != nil { - return nil, nil, err - } - - prog := ssa.NewProgram(fset, mode) - - // Create SSA packages for all imports. - // Order is not significant. - created := make(map[*types.Package]bool) - var createAll func(pkgs []*types.Package) - createAll = func(pkgs []*types.Package) { - for _, p := range pkgs { - if !created[p] { - created[p] = true - prog.CreatePackage(p, nil, nil, true) - createAll(p.Imports()) - } - } - } - createAll(pkg.Imports()) - - // Create and build the primary package. - ssapkg := prog.CreatePackage(pkg, files, info, false) - ssapkg.Build() - return ssapkg, info, nil -} diff --git a/go/ssa/ssautil/load14_test.go b/go/ssa/ssautil/load14_test.go deleted file mode 100644 index 41955ec508..0000000000 --- a/go/ssa/ssautil/load14_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2015 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. - -// +build !go1.5 - -package ssautil_test - -import ( - "go/ast" - "go/parser" - "go/token" - "os" - "testing" - - "golang.org/x/tools/go/ssa/ssautil" - "golang.org/x/tools/go/types" - - _ "golang.org/x/tools/go/gcimporter" -) - -const hello = `package main - -import "fmt" - -func main() { - fmt.Println("Hello, world") -} -` - -func TestBuildPackage(t *testing.T) { - // There is a more substantial test of BuildPackage and the - // SSA program it builds in ../ssa/builder_test.go. - - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "hello.go", hello, 0) - if err != nil { - t.Fatal(err) - } - - pkg := types.NewPackage("hello", "") - ssapkg, _, err := ssautil.BuildPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0) - if err != nil { - t.Fatal(err) - } - if pkg.Name() != "main" { - t.Errorf("pkg.Name() = %s, want main", pkg.Name()) - } - if ssapkg.Func("main") == nil { - ssapkg.WriteTo(os.Stderr) - t.Errorf("ssapkg has no main function") - } -} - -func TestBuildPackage_MissingImport(t *testing.T) { - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "bad.go", `package bad; import "missing"`, 0) - if err != nil { - t.Fatal(err) - } - - pkg := types.NewPackage("bad", "") - ssapkg, _, err := ssautil.BuildPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0) - if err == nil || ssapkg != nil { - t.Fatal("BuildPackage succeeded unexpectedly") - } -} diff --git a/go/ssa/ssautil/switch14.go b/go/ssa/ssautil/switch14.go deleted file mode 100644 index b2f7f21e92..0000000000 --- a/go/ssa/ssautil/switch14.go +++ /dev/null @@ -1,236 +0,0 @@ -// 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. - -// +build !go1.5 - -package ssautil - -// This file implements discovery of switch and type-switch constructs -// from low-level control flow. -// -// Many techniques exist for compiling a high-level switch with -// constant cases to efficient machine code. The optimal choice will -// depend on the data type, the specific case values, the code in the -// body of each case, and the hardware. -// Some examples: -// - a lookup table (for a switch that maps constants to constants) -// - a computed goto -// - a binary tree -// - a perfect hash -// - a two-level switch (to partition constant strings by their first byte). - -import ( - "bytes" - "fmt" - "go/token" - - "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/types" -) - -// A ConstCase represents a single constant comparison. -// It is part of a Switch. -type ConstCase struct { - Block *ssa.BasicBlock // block performing the comparison - Body *ssa.BasicBlock // body of the case - Value *ssa.Const // case comparand -} - -// A TypeCase represents a single type assertion. -// It is part of a Switch. -type TypeCase struct { - Block *ssa.BasicBlock // block performing the type assert - Body *ssa.BasicBlock // body of the case - Type types.Type // case type - Binding ssa.Value // value bound by this case -} - -// A Switch is a logical high-level control flow operation -// (a multiway branch) discovered by analysis of a CFG containing -// only if/else chains. It is not part of the ssa.Instruction set. -// -// One of ConstCases and TypeCases has length >= 2; -// the other is nil. -// -// In a value switch, the list of cases may contain duplicate constants. -// A type switch may contain duplicate types, or types assignable -// to an interface type also in the list. -// TODO(adonovan): eliminate such duplicates. -// -type Switch struct { - Start *ssa.BasicBlock // block containing start of if/else chain - X ssa.Value // the switch operand - ConstCases []ConstCase // ordered list of constant comparisons - TypeCases []TypeCase // ordered list of type assertions - Default *ssa.BasicBlock // successor if all comparisons fail -} - -func (sw *Switch) String() string { - // We represent each block by the String() of its - // first Instruction, e.g. "print(42:int)". - var buf bytes.Buffer - if sw.ConstCases != nil { - fmt.Fprintf(&buf, "switch %s {\n", sw.X.Name()) - for _, c := range sw.ConstCases { - fmt.Fprintf(&buf, "case %s: %s\n", c.Value, c.Body.Instrs[0]) - } - } else { - fmt.Fprintf(&buf, "switch %s.(type) {\n", sw.X.Name()) - for _, c := range sw.TypeCases { - fmt.Fprintf(&buf, "case %s %s: %s\n", - c.Binding.Name(), c.Type, c.Body.Instrs[0]) - } - } - if sw.Default != nil { - fmt.Fprintf(&buf, "default: %s\n", sw.Default.Instrs[0]) - } - fmt.Fprintf(&buf, "}") - return buf.String() -} - -// Switches examines the control-flow graph of fn and returns the -// set of inferred value and type switches. A value switch tests an -// ssa.Value for equality against two or more compile-time constant -// values. Switches involving link-time constants (addresses) are -// ignored. A type switch type-asserts an ssa.Value against two or -// more types. -// -// The switches are returned in dominance order. -// -// The resulting switches do not necessarily correspond to uses of the -// 'switch' keyword in the source: for example, a single source-level -// switch statement with non-constant cases may result in zero, one or -// many Switches, one per plural sequence of constant cases. -// Switches may even be inferred from if/else- or goto-based control flow. -// (In general, the control flow constructs of the source program -// cannot be faithfully reproduced from the SSA representation.) -// -func Switches(fn *ssa.Function) []Switch { - // Traverse the CFG in dominance order, so we don't - // enter an if/else-chain in the middle. - var switches []Switch - seen := make(map[*ssa.BasicBlock]bool) // TODO(adonovan): opt: use ssa.blockSet - for _, b := range fn.DomPreorder() { - if x, k := isComparisonBlock(b); x != nil { - // Block b starts a switch. - sw := Switch{Start: b, X: x} - valueSwitch(&sw, k, seen) - if len(sw.ConstCases) > 1 { - switches = append(switches, sw) - } - } - - if y, x, T := isTypeAssertBlock(b); y != nil { - // Block b starts a type switch. - sw := Switch{Start: b, X: x} - typeSwitch(&sw, y, T, seen) - if len(sw.TypeCases) > 1 { - switches = append(switches, sw) - } - } - } - return switches -} - -func valueSwitch(sw *Switch, k *ssa.Const, seen map[*ssa.BasicBlock]bool) { - b := sw.Start - x := sw.X - for x == sw.X { - if seen[b] { - break - } - seen[b] = true - - sw.ConstCases = append(sw.ConstCases, ConstCase{ - Block: b, - Body: b.Succs[0], - Value: k, - }) - b = b.Succs[1] - if len(b.Instrs) > 2 { - // Block b contains not just 'if x == k', - // so it may have side effects that - // make it unsafe to elide. - break - } - if len(b.Preds) != 1 { - // Block b has multiple predecessors, - // so it cannot be treated as a case. - break - } - x, k = isComparisonBlock(b) - } - sw.Default = b -} - -func typeSwitch(sw *Switch, y ssa.Value, T types.Type, seen map[*ssa.BasicBlock]bool) { - b := sw.Start - x := sw.X - for x == sw.X { - if seen[b] { - break - } - seen[b] = true - - sw.TypeCases = append(sw.TypeCases, TypeCase{ - Block: b, - Body: b.Succs[0], - Type: T, - Binding: y, - }) - b = b.Succs[1] - if len(b.Instrs) > 4 { - // Block b contains not just - // {TypeAssert; Extract #0; Extract #1; If} - // so it may have side effects that - // make it unsafe to elide. - break - } - if len(b.Preds) != 1 { - // Block b has multiple predecessors, - // so it cannot be treated as a case. - break - } - y, x, T = isTypeAssertBlock(b) - } - sw.Default = b -} - -// isComparisonBlock returns the operands (v, k) if a block ends with -// a comparison v==k, where k is a compile-time constant. -// -func isComparisonBlock(b *ssa.BasicBlock) (v ssa.Value, k *ssa.Const) { - if n := len(b.Instrs); n >= 2 { - if i, ok := b.Instrs[n-1].(*ssa.If); ok { - if binop, ok := i.Cond.(*ssa.BinOp); ok && binop.Block() == b && binop.Op == token.EQL { - if k, ok := binop.Y.(*ssa.Const); ok { - return binop.X, k - } - if k, ok := binop.X.(*ssa.Const); ok { - return binop.Y, k - } - } - } - } - return -} - -// isTypeAssertBlock returns the operands (y, x, T) if a block ends with -// a type assertion "if y, ok := x.(T); ok {". -// -func isTypeAssertBlock(b *ssa.BasicBlock) (y, x ssa.Value, T types.Type) { - if n := len(b.Instrs); n >= 4 { - if i, ok := b.Instrs[n-1].(*ssa.If); ok { - if ext1, ok := i.Cond.(*ssa.Extract); ok && ext1.Block() == b && ext1.Index == 1 { - if ta, ok := ext1.Tuple.(*ssa.TypeAssert); ok && ta.Block() == b { - // hack: relies upon instruction ordering. - if ext0, ok := b.Instrs[n-3].(*ssa.Extract); ok { - return ext0, ta.X, ta.AssertedType - } - } - } - } - } - return -} diff --git a/godoc/analysis/analysis14.go b/godoc/analysis/analysis14.go deleted file mode 100644 index ca35b41086..0000000000 --- a/godoc/analysis/analysis14.go +++ /dev/null @@ -1,622 +0,0 @@ -// Copyright 2014 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. - -// +build !go1.5 - -// Package analysis performs type and pointer analysis -// and generates mark-up for the Go source view. -// -// The Run method populates a Result object by running type and -// (optionally) pointer analysis. The Result object is thread-safe -// and at all times may be accessed by a serving thread, even as it is -// progressively populated as analysis facts are derived. -// -// The Result is a mapping from each godoc file URL -// (e.g. /src/fmt/print.go) to information about that file. The -// information is a list of HTML markup links and a JSON array of -// structured data values. Some of the links call client-side -// JavaScript functions that index this array. -// -// The analysis computes mark-up for the following relations: -// -// IMPORTS: for each ast.ImportSpec, the package that it denotes. -// -// RESOLUTION: for each ast.Ident, its kind and type, and the location -// of its definition. -// -// METHOD SETS, IMPLEMENTS: for each ast.Ident defining a named type, -// its method-set, the set of interfaces it implements or is -// implemented by, and its size/align values. -// -// CALLERS, CALLEES: for each function declaration ('func' token), its -// callers, and for each call-site ('(' token), its callees. -// -// CALLGRAPH: the package docs include an interactive viewer for the -// intra-package call graph of "fmt". -// -// CHANNEL PEERS: for each channel operation make/<-/close, the set of -// other channel ops that alias the same channel(s). -// -// ERRORS: for each locus of a frontend (scanner/parser/type) error, the -// location is highlighted in red and hover text provides the compiler -// error message. -// -package analysis // import "golang.org/x/tools/godoc/analysis" - -import ( - "fmt" - "go/build" - "go/scanner" - "go/token" - "html" - "io" - "log" - "os" - "path/filepath" - "runtime" - "sort" - "strings" - "sync" - - "golang.org/x/tools/go/exact" - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/pointer" - "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/ssa/ssautil" - "golang.org/x/tools/go/types" -) - -// -- links ------------------------------------------------------------ - -// A Link is an HTML decoration of the bytes [Start, End) of a file. -// Write is called before/after those bytes to emit the mark-up. -type Link interface { - Start() int - End() int - Write(w io.Writer, _ int, start bool) // the godoc.LinkWriter signature -} - -// An element. -type aLink struct { - start, end int // =godoc.Segment - title string // hover text - onclick string // JS code (NB: trusted) - href string // URL (NB: trusted) -} - -func (a aLink) Start() int { return a.start } -func (a aLink) End() int { return a.end } -func (a aLink) Write(w io.Writer, _ int, start bool) { - if start { - fmt.Fprintf(w, `") - } else { - fmt.Fprintf(w, "") - } -} - -// An element. -type errorLink struct { - start int - msg string -} - -func (e errorLink) Start() int { return e.start } -func (e errorLink) End() int { return e.start + 1 } - -func (e errorLink) Write(w io.Writer, _ int, start bool) { - // causes havoc, not sure why, so use . - if start { - fmt.Fprintf(w, ``, html.EscapeString(e.msg)) - } else { - fmt.Fprintf(w, "") - } -} - -// -- fileInfo --------------------------------------------------------- - -// FileInfo holds analysis information for the source file view. -// Clients must not mutate it. -type FileInfo struct { - Data []interface{} // JSON serializable values - Links []Link // HTML link markup -} - -// A fileInfo is the server's store of hyperlinks and JSON data for a -// particular file. -type fileInfo struct { - mu sync.Mutex - data []interface{} // JSON objects - links []Link - sorted bool - hasErrors bool // TODO(adonovan): surface this in the UI -} - -// addLink adds a link to the Go source file fi. -func (fi *fileInfo) addLink(link Link) { - fi.mu.Lock() - fi.links = append(fi.links, link) - fi.sorted = false - if _, ok := link.(errorLink); ok { - fi.hasErrors = true - } - fi.mu.Unlock() -} - -// addData adds the structured value x to the JSON data for the Go -// source file fi. Its index is returned. -func (fi *fileInfo) addData(x interface{}) int { - fi.mu.Lock() - index := len(fi.data) - fi.data = append(fi.data, x) - fi.mu.Unlock() - return index -} - -// get returns the file info in external form. -// Callers must not mutate its fields. -func (fi *fileInfo) get() FileInfo { - var r FileInfo - // Copy slices, to avoid races. - fi.mu.Lock() - r.Data = append(r.Data, fi.data...) - if !fi.sorted { - sort.Sort(linksByStart(fi.links)) - fi.sorted = true - } - r.Links = append(r.Links, fi.links...) - fi.mu.Unlock() - return r -} - -// PackageInfo holds analysis information for the package view. -// Clients must not mutate it. -type PackageInfo struct { - CallGraph []*PCGNodeJSON - CallGraphIndex map[string]int - Types []*TypeInfoJSON -} - -type pkgInfo struct { - mu sync.Mutex - callGraph []*PCGNodeJSON - callGraphIndex map[string]int // keys are (*ssa.Function).RelString() - types []*TypeInfoJSON // type info for exported types -} - -func (pi *pkgInfo) setCallGraph(callGraph []*PCGNodeJSON, callGraphIndex map[string]int) { - pi.mu.Lock() - pi.callGraph = callGraph - pi.callGraphIndex = callGraphIndex - pi.mu.Unlock() -} - -func (pi *pkgInfo) addType(t *TypeInfoJSON) { - pi.mu.Lock() - pi.types = append(pi.types, t) - pi.mu.Unlock() -} - -// get returns the package info in external form. -// Callers must not mutate its fields. -func (pi *pkgInfo) get() PackageInfo { - var r PackageInfo - // Copy slices, to avoid races. - pi.mu.Lock() - r.CallGraph = append(r.CallGraph, pi.callGraph...) - r.CallGraphIndex = pi.callGraphIndex - r.Types = append(r.Types, pi.types...) - pi.mu.Unlock() - return r -} - -// -- Result ----------------------------------------------------------- - -// Result contains the results of analysis. -// The result contains a mapping from filenames to a set of HTML links -// and JavaScript data referenced by the links. -type Result struct { - mu sync.Mutex // guards maps (but not their contents) - status string // global analysis status - fileInfos map[string]*fileInfo // keys are godoc file URLs - pkgInfos map[string]*pkgInfo // keys are import paths -} - -// fileInfo returns the fileInfo for the specified godoc file URL, -// constructing it as needed. Thread-safe. -func (res *Result) fileInfo(url string) *fileInfo { - res.mu.Lock() - fi, ok := res.fileInfos[url] - if !ok { - if res.fileInfos == nil { - res.fileInfos = make(map[string]*fileInfo) - } - fi = new(fileInfo) - res.fileInfos[url] = fi - } - res.mu.Unlock() - return fi -} - -// Status returns a human-readable description of the current analysis status. -func (res *Result) Status() string { - res.mu.Lock() - defer res.mu.Unlock() - return res.status -} - -func (res *Result) setStatusf(format string, args ...interface{}) { - res.mu.Lock() - res.status = fmt.Sprintf(format, args...) - log.Printf(format, args...) - res.mu.Unlock() -} - -// FileInfo returns new slices containing opaque JSON values and the -// HTML link markup for the specified godoc file URL. Thread-safe. -// Callers must not mutate the elements. -// It returns "zero" if no data is available. -// -func (res *Result) FileInfo(url string) (fi FileInfo) { - return res.fileInfo(url).get() -} - -// pkgInfo returns the pkgInfo for the specified import path, -// constructing it as needed. Thread-safe. -func (res *Result) pkgInfo(importPath string) *pkgInfo { - res.mu.Lock() - pi, ok := res.pkgInfos[importPath] - if !ok { - if res.pkgInfos == nil { - res.pkgInfos = make(map[string]*pkgInfo) - } - pi = new(pkgInfo) - res.pkgInfos[importPath] = pi - } - res.mu.Unlock() - return pi -} - -// PackageInfo returns new slices of JSON values for the callgraph and -// type info for the specified package. Thread-safe. -// Callers must not mutate its fields. -// PackageInfo returns "zero" if no data is available. -// -func (res *Result) PackageInfo(importPath string) PackageInfo { - return res.pkgInfo(importPath).get() -} - -// -- analysis --------------------------------------------------------- - -type analysis struct { - result *Result - prog *ssa.Program - ops []chanOp // all channel ops in program - allNamed []*types.Named // all named types in the program - ptaConfig pointer.Config - path2url map[string]string // maps openable path to godoc file URL (/src/fmt/print.go) - pcgs map[*ssa.Package]*packageCallGraph -} - -// fileAndOffset returns the file and offset for a given pos. -func (a *analysis) fileAndOffset(pos token.Pos) (fi *fileInfo, offset int) { - return a.fileAndOffsetPosn(a.prog.Fset.Position(pos)) -} - -// fileAndOffsetPosn returns the file and offset for a given position. -func (a *analysis) fileAndOffsetPosn(posn token.Position) (fi *fileInfo, offset int) { - url := a.path2url[posn.Filename] - return a.result.fileInfo(url), posn.Offset -} - -// posURL returns the URL of the source extent [pos, pos+len). -func (a *analysis) posURL(pos token.Pos, len int) string { - if pos == token.NoPos { - return "" - } - posn := a.prog.Fset.Position(pos) - url := a.path2url[posn.Filename] - return fmt.Sprintf("%s?s=%d:%d#L%d", - url, posn.Offset, posn.Offset+len, posn.Line) -} - -// ---------------------------------------------------------------------- - -// Run runs program analysis and computes the resulting markup, -// populating *result in a thread-safe manner, first with type -// information then later with pointer analysis information if -// enabled by the pta flag. -// -func Run(pta bool, result *Result) { - conf := loader.Config{ - AllowErrors: true, - } - - // Silence the default error handler. - // Don't print all errors; we'll report just - // one per errant package later. - conf.TypeChecker.Error = func(e error) {} - - var roots, args []string // roots[i] ends with os.PathSeparator - - // Enumerate packages in $GOROOT. - root := filepath.Join(runtime.GOROOT(), "src") + string(os.PathSeparator) - roots = append(roots, root) - args = allPackages(root) - log.Printf("GOROOT=%s: %s\n", root, args) - - // Enumerate packages in $GOPATH. - for i, dir := range filepath.SplitList(build.Default.GOPATH) { - root := filepath.Join(dir, "src") + string(os.PathSeparator) - roots = append(roots, root) - pkgs := allPackages(root) - log.Printf("GOPATH[%d]=%s: %s\n", i, root, pkgs) - args = append(args, pkgs...) - } - - // Uncomment to make startup quicker during debugging. - //args = []string{"golang.org/x/tools/cmd/godoc"} - //args = []string{"fmt"} - - if _, err := conf.FromArgs(args, true); err != nil { - // TODO(adonovan): degrade gracefully, not fail totally. - // (The crippling case is a parse error in an external test file.) - result.setStatusf("Analysis failed: %s.", err) // import error - return - } - - result.setStatusf("Loading and type-checking packages...") - iprog, err := conf.Load() - if iprog != nil { - // Report only the first error of each package. - for _, info := range iprog.AllPackages { - for _, err := range info.Errors { - fmt.Fprintln(os.Stderr, err) - break - } - } - log.Printf("Loaded %d packages.", len(iprog.AllPackages)) - } - if err != nil { - result.setStatusf("Loading failed: %s.\n", err) - return - } - - // Create SSA-form program representation. - // Only the transitively error-free packages are used. - prog := ssautil.CreateProgram(iprog, ssa.GlobalDebug) - - // Compute the set of main packages, including testmain. - allPackages := prog.AllPackages() - var mainPkgs []*ssa.Package - if testmain := prog.CreateTestMainPackage(allPackages...); testmain != nil { - mainPkgs = append(mainPkgs, testmain) - if p := testmain.Const("packages"); p != nil { - log.Printf("Tested packages: %v", exact.StringVal(p.Value.Value)) - } - } - for _, pkg := range allPackages { - if pkg.Pkg.Name() == "main" && pkg.Func("main") != nil { - mainPkgs = append(mainPkgs, pkg) - } - } - log.Print("Transitively error-free main packages: ", mainPkgs) - - // Build SSA code for bodies of all functions in the whole program. - result.setStatusf("Constructing SSA form...") - prog.Build() - log.Print("SSA construction complete") - - a := analysis{ - result: result, - prog: prog, - pcgs: make(map[*ssa.Package]*packageCallGraph), - } - - // Build a mapping from openable filenames to godoc file URLs, - // i.e. "/src/" plus path relative to GOROOT/src or GOPATH[i]/src. - a.path2url = make(map[string]string) - for _, info := range iprog.AllPackages { - nextfile: - for _, f := range info.Files { - if f.Pos() == 0 { - continue // e.g. files generated by cgo - } - abs := iprog.Fset.File(f.Pos()).Name() - // Find the root to which this file belongs. - for _, root := range roots { - rel := strings.TrimPrefix(abs, root) - if len(rel) < len(abs) { - a.path2url[abs] = "/src/" + filepath.ToSlash(rel) - continue nextfile - } - } - - log.Printf("Can't locate file %s (package %q) beneath any root", - abs, info.Pkg.Path()) - } - } - - // Add links for scanner, parser, type-checker errors. - // TODO(adonovan): fix: these links can overlap with - // identifier markup, causing the renderer to emit some - // characters twice. - errors := make(map[token.Position][]string) - for _, info := range iprog.AllPackages { - for _, err := range info.Errors { - switch err := err.(type) { - case types.Error: - posn := a.prog.Fset.Position(err.Pos) - errors[posn] = append(errors[posn], err.Msg) - case scanner.ErrorList: - for _, e := range err { - errors[e.Pos] = append(errors[e.Pos], e.Msg) - } - default: - log.Printf("Package %q has error (%T) without position: %v\n", - info.Pkg.Path(), err, err) - } - } - } - for posn, errs := range errors { - fi, offset := a.fileAndOffsetPosn(posn) - fi.addLink(errorLink{ - start: offset, - msg: strings.Join(errs, "\n"), - }) - } - - // ---------- type-based analyses ---------- - - // Compute the all-pairs IMPLEMENTS relation. - // Collect all named types, even local types - // (which can have methods via promotion) - // and the built-in "error". - errorType := types.Universe.Lookup("error").Type().(*types.Named) - a.allNamed = append(a.allNamed, errorType) - for _, info := range iprog.AllPackages { - for _, obj := range info.Defs { - if obj, ok := obj.(*types.TypeName); ok { - a.allNamed = append(a.allNamed, obj.Type().(*types.Named)) - } - } - } - log.Print("Computing implements relation...") - facts := computeImplements(&a.prog.MethodSets, a.allNamed) - - // Add the type-based analysis results. - log.Print("Extracting type info...") - for _, info := range iprog.AllPackages { - a.doTypeInfo(info, facts) - } - - a.visitInstrs(pta) - - result.setStatusf("Type analysis complete.") - - if pta { - a.pointer(mainPkgs) - } -} - -// visitInstrs visits all SSA instructions in the program. -func (a *analysis) visitInstrs(pta bool) { - log.Print("Visit instructions...") - for fn := range ssautil.AllFunctions(a.prog) { - for _, b := range fn.Blocks { - for _, instr := range b.Instrs { - // CALLEES (static) - // (Dynamic calls require pointer analysis.) - // - // We use the SSA representation to find the static callee, - // since in many cases it does better than the - // types.Info.{Refs,Selection} information. For example: - // - // defer func(){}() // static call to anon function - // f := func(){}; f() // static call to anon function - // f := fmt.Println; f() // static call to named function - // - // The downside is that we get no static callee information - // for packages that (transitively) contain errors. - if site, ok := instr.(ssa.CallInstruction); ok { - if callee := site.Common().StaticCallee(); callee != nil { - // TODO(adonovan): callgraph: elide wrappers. - // (Do static calls ever go to wrappers?) - if site.Common().Pos() != token.NoPos { - a.addCallees(site, []*ssa.Function{callee}) - } - } - } - - if !pta { - continue - } - - // CHANNEL PEERS - // Collect send/receive/close instructions in the whole ssa.Program. - for _, op := range chanOps(instr) { - a.ops = append(a.ops, op) - a.ptaConfig.AddQuery(op.ch) // add channel ssa.Value to PTA query - } - } - } - } - log.Print("Visit instructions complete") -} - -// pointer runs the pointer analysis. -func (a *analysis) pointer(mainPkgs []*ssa.Package) { - // Run the pointer analysis and build the complete callgraph. - a.ptaConfig.Mains = mainPkgs - a.ptaConfig.BuildCallGraph = true - a.ptaConfig.Reflection = false // (for now) - - a.result.setStatusf("Pointer analysis running...") - - ptares, err := pointer.Analyze(&a.ptaConfig) - if err != nil { - // If this happens, it indicates a bug. - a.result.setStatusf("Pointer analysis failed: %s.", err) - return - } - log.Print("Pointer analysis complete.") - - // Add the results of pointer analysis. - - a.result.setStatusf("Computing channel peers...") - a.doChannelPeers(ptares.Queries) - a.result.setStatusf("Computing dynamic call graph edges...") - a.doCallgraph(ptares.CallGraph) - - a.result.setStatusf("Analysis complete.") -} - -type linksByStart []Link - -func (a linksByStart) Less(i, j int) bool { return a[i].Start() < a[j].Start() } -func (a linksByStart) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a linksByStart) Len() int { return len(a) } - -// allPackages returns a new sorted slice of all packages beneath the -// specified package root directory, e.g. $GOROOT/src or $GOPATH/src. -// Derived from from go/ssa/stdlib_test.go -// root must end with os.PathSeparator. -// -// TODO(adonovan): use buildutil.AllPackages when the tree thaws. -func allPackages(root string) []string { - var pkgs []string - filepath.Walk(root, func(path string, info os.FileInfo, err error) error { - if info == nil { - return nil // non-existent root directory? - } - if !info.IsDir() { - return nil // not a directory - } - // Prune the search if we encounter any of these names: - base := filepath.Base(path) - if base == "testdata" || strings.HasPrefix(base, ".") { - return filepath.SkipDir - } - pkg := filepath.ToSlash(strings.TrimPrefix(path, root)) - switch pkg { - case "builtin": - return filepath.SkipDir - case "": - return nil // ignore root of tree - } - pkgs = append(pkgs, pkg) - return nil - }) - return pkgs -} diff --git a/godoc/analysis/callgraph14.go b/godoc/analysis/callgraph14.go deleted file mode 100644 index 2692d7e7b9..0000000000 --- a/godoc/analysis/callgraph14.go +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright 2014 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. - -// +build !go1.5 - -package analysis - -// This file computes the CALLERS and CALLEES relations from the call -// graph. CALLERS/CALLEES information is displayed in the lower pane -// when a "func" token or ast.CallExpr.Lparen is clicked, respectively. - -import ( - "fmt" - "go/ast" - "go/token" - "log" - "math/big" - "sort" - - "golang.org/x/tools/go/callgraph" - "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/types" -) - -// doCallgraph computes the CALLEES and CALLERS relations. -func (a *analysis) doCallgraph(cg *callgraph.Graph) { - log.Print("Deleting synthetic nodes...") - // TODO(adonovan): opt: DeleteSyntheticNodes is asymptotically - // inefficient and can be (unpredictably) slow. - cg.DeleteSyntheticNodes() - log.Print("Synthetic nodes deleted") - - // Populate nodes of package call graphs (PCGs). - for _, n := range cg.Nodes { - a.pcgAddNode(n.Func) - } - // Within each PCG, sort funcs by name. - for _, pcg := range a.pcgs { - pcg.sortNodes() - } - - calledFuncs := make(map[ssa.CallInstruction]map[*ssa.Function]bool) - callingSites := make(map[*ssa.Function]map[ssa.CallInstruction]bool) - for _, n := range cg.Nodes { - for _, e := range n.Out { - if e.Site == nil { - continue // a call from a synthetic node such as - } - - // Add (site pos, callee) to calledFuncs. - // (Dynamic calls only.) - callee := e.Callee.Func - - a.pcgAddEdge(n.Func, callee) - - if callee.Synthetic != "" { - continue // call of a package initializer - } - - if e.Site.Common().StaticCallee() == nil { - // dynamic call - // (CALLEES information for static calls - // is computed using SSA information.) - lparen := e.Site.Common().Pos() - if lparen != token.NoPos { - fns := calledFuncs[e.Site] - if fns == nil { - fns = make(map[*ssa.Function]bool) - calledFuncs[e.Site] = fns - } - fns[callee] = true - } - } - - // Add (callee, site) to callingSites. - fns := callingSites[callee] - if fns == nil { - fns = make(map[ssa.CallInstruction]bool) - callingSites[callee] = fns - } - fns[e.Site] = true - } - } - - // CALLEES. - log.Print("Callees...") - for site, fns := range calledFuncs { - var funcs funcsByPos - for fn := range fns { - funcs = append(funcs, fn) - } - sort.Sort(funcs) - - a.addCallees(site, funcs) - } - - // CALLERS - log.Print("Callers...") - for callee, sites := range callingSites { - pos := funcToken(callee) - if pos == token.NoPos { - log.Printf("CALLERS: skipping %s: no pos", callee) - continue - } - - var this *types.Package // for relativizing names - if callee.Pkg != nil { - this = callee.Pkg.Pkg - } - - // Compute sites grouped by parent, with text and URLs. - sitesByParent := make(map[*ssa.Function]sitesByPos) - for site := range sites { - fn := site.Parent() - sitesByParent[fn] = append(sitesByParent[fn], site) - } - var funcs funcsByPos - for fn := range sitesByParent { - funcs = append(funcs, fn) - } - sort.Sort(funcs) - - v := callersJSON{ - Callee: callee.String(), - Callers: []callerJSON{}, // (JS wants non-nil) - } - for _, fn := range funcs { - caller := callerJSON{ - Func: prettyFunc(this, fn), - Sites: []anchorJSON{}, // (JS wants non-nil) - } - sites := sitesByParent[fn] - sort.Sort(sites) - for _, site := range sites { - pos := site.Common().Pos() - if pos != token.NoPos { - caller.Sites = append(caller.Sites, anchorJSON{ - Text: fmt.Sprintf("%d", a.prog.Fset.Position(pos).Line), - Href: a.posURL(pos, len("(")), - }) - } - } - v.Callers = append(v.Callers, caller) - } - - fi, offset := a.fileAndOffset(pos) - fi.addLink(aLink{ - start: offset, - end: offset + len("func"), - title: fmt.Sprintf("%d callers", len(sites)), - onclick: fmt.Sprintf("onClickCallers(%d)", fi.addData(v)), - }) - } - - // PACKAGE CALLGRAPH - log.Print("Package call graph...") - for pkg, pcg := range a.pcgs { - // Maps (*ssa.Function).RelString() to index in JSON CALLGRAPH array. - index := make(map[string]int) - - // Treat exported functions (and exported methods of - // exported named types) as roots even if they aren't - // actually called from outside the package. - for i, n := range pcg.nodes { - if i == 0 || n.fn.Object() == nil || !n.fn.Object().Exported() { - continue - } - recv := n.fn.Signature.Recv() - if recv == nil || deref(recv.Type()).(*types.Named).Obj().Exported() { - roots := &pcg.nodes[0].edges - roots.SetBit(roots, i, 1) - } - index[n.fn.RelString(pkg.Pkg)] = i - } - - json := a.pcgJSON(pcg) - - // TODO(adonovan): pkg.Path() is not unique! - // It is possible to declare a non-test package called x_test. - a.result.pkgInfo(pkg.Pkg.Path()).setCallGraph(json, index) - } -} - -// addCallees adds client data and links for the facts that site calls fns. -func (a *analysis) addCallees(site ssa.CallInstruction, fns []*ssa.Function) { - v := calleesJSON{ - Descr: site.Common().Description(), - Callees: []anchorJSON{}, // (JS wants non-nil) - } - var this *types.Package // for relativizing names - if p := site.Parent().Package(); p != nil { - this = p.Pkg - } - - for _, fn := range fns { - v.Callees = append(v.Callees, anchorJSON{ - Text: prettyFunc(this, fn), - Href: a.posURL(funcToken(fn), len("func")), - }) - } - - fi, offset := a.fileAndOffset(site.Common().Pos()) - fi.addLink(aLink{ - start: offset, - end: offset + len("("), - title: fmt.Sprintf("%d callees", len(v.Callees)), - onclick: fmt.Sprintf("onClickCallees(%d)", fi.addData(v)), - }) -} - -// -- utilities -------------------------------------------------------- - -// stable order within packages but undefined across packages. -type funcsByPos []*ssa.Function - -func (a funcsByPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() } -func (a funcsByPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a funcsByPos) Len() int { return len(a) } - -type sitesByPos []ssa.CallInstruction - -func (a sitesByPos) Less(i, j int) bool { return a[i].Common().Pos() < a[j].Common().Pos() } -func (a sitesByPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a sitesByPos) Len() int { return len(a) } - -func funcToken(fn *ssa.Function) token.Pos { - switch syntax := fn.Syntax().(type) { - case *ast.FuncLit: - return syntax.Type.Func - case *ast.FuncDecl: - return syntax.Type.Func - } - return token.NoPos -} - -// prettyFunc pretty-prints fn for the user interface. -// TODO(adonovan): return HTML so we have more markup freedom. -func prettyFunc(this *types.Package, fn *ssa.Function) string { - if fn.Parent() != nil { - return fmt.Sprintf("%s in %s", - types.TypeString(fn.Signature, types.RelativeTo(this)), - prettyFunc(this, fn.Parent())) - } - if fn.Synthetic != "" && fn.Name() == "init" { - // (This is the actual initializer, not a declared 'func init'). - if fn.Pkg.Pkg == this { - return "package initializer" - } - return fmt.Sprintf("%q package initializer", fn.Pkg.Pkg.Path()) - } - return fn.RelString(this) -} - -// -- intra-package callgraph ------------------------------------------ - -// pcgNode represents a node in the package call graph (PCG). -type pcgNode struct { - fn *ssa.Function - pretty string // cache of prettyFunc(fn) - edges big.Int // set of callee func indices -} - -// A packageCallGraph represents the intra-package edges of the global call graph. -// The zeroth node indicates "all external functions". -type packageCallGraph struct { - nodeIndex map[*ssa.Function]int // maps func to node index (a small int) - nodes []*pcgNode // maps node index to node -} - -// sortNodes populates pcg.nodes in name order and updates the nodeIndex. -func (pcg *packageCallGraph) sortNodes() { - nodes := make([]*pcgNode, 0, len(pcg.nodeIndex)) - nodes = append(nodes, &pcgNode{fn: nil, pretty: ""}) - for fn := range pcg.nodeIndex { - nodes = append(nodes, &pcgNode{ - fn: fn, - pretty: prettyFunc(fn.Pkg.Pkg, fn), - }) - } - sort.Sort(pcgNodesByPretty(nodes[1:])) - for i, n := range nodes { - pcg.nodeIndex[n.fn] = i - } - pcg.nodes = nodes -} - -func (pcg *packageCallGraph) addEdge(caller, callee *ssa.Function) { - var callerIndex int - if caller.Pkg == callee.Pkg { - // intra-package edge - callerIndex = pcg.nodeIndex[caller] - if callerIndex < 1 { - panic(caller) - } - } - edges := &pcg.nodes[callerIndex].edges - edges.SetBit(edges, pcg.nodeIndex[callee], 1) -} - -func (a *analysis) pcgAddNode(fn *ssa.Function) { - if fn.Pkg == nil { - return - } - pcg, ok := a.pcgs[fn.Pkg] - if !ok { - pcg = &packageCallGraph{nodeIndex: make(map[*ssa.Function]int)} - a.pcgs[fn.Pkg] = pcg - } - pcg.nodeIndex[fn] = -1 -} - -func (a *analysis) pcgAddEdge(caller, callee *ssa.Function) { - if callee.Pkg != nil { - a.pcgs[callee.Pkg].addEdge(caller, callee) - } -} - -// pcgJSON returns a new slice of callgraph JSON values. -func (a *analysis) pcgJSON(pcg *packageCallGraph) []*PCGNodeJSON { - var nodes []*PCGNodeJSON - for _, n := range pcg.nodes { - - // TODO(adonovan): why is there no good way to iterate - // over the set bits of a big.Int? - var callees []int - nbits := n.edges.BitLen() - for j := 0; j < nbits; j++ { - if n.edges.Bit(j) == 1 { - callees = append(callees, j) - } - } - - var pos token.Pos - if n.fn != nil { - pos = funcToken(n.fn) - } - nodes = append(nodes, &PCGNodeJSON{ - Func: anchorJSON{ - Text: n.pretty, - Href: a.posURL(pos, len("func")), - }, - Callees: callees, - }) - } - return nodes -} - -type pcgNodesByPretty []*pcgNode - -func (a pcgNodesByPretty) Less(i, j int) bool { return a[i].pretty < a[j].pretty } -func (a pcgNodesByPretty) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a pcgNodesByPretty) Len() int { return len(a) } diff --git a/godoc/analysis/implements14.go b/godoc/analysis/implements14.go deleted file mode 100644 index 0ad10089fd..0000000000 --- a/godoc/analysis/implements14.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2014 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. - -// +build !go1.5 - -package analysis - -// This file computes the "implements" relation over all pairs of -// named types in the program. (The mark-up is done by typeinfo.go.) - -// TODO(adonovan): do we want to report implements(C, I) where C and I -// belong to different packages and at least one is not exported? - -import ( - "sort" - - "golang.org/x/tools/go/types" - "golang.org/x/tools/go/types/typeutil" -) - -// computeImplements computes the "implements" relation over all pairs -// of named types in allNamed. -func computeImplements(cache *typeutil.MethodSetCache, allNamed []*types.Named) map[*types.Named]implementsFacts { - // Information about a single type's method set. - type msetInfo struct { - typ types.Type - mset *types.MethodSet - mask1, mask2 uint64 - } - - initMsetInfo := func(info *msetInfo, typ types.Type) { - info.typ = typ - info.mset = cache.MethodSet(typ) - for i := 0; i < info.mset.Len(); i++ { - name := info.mset.At(i).Obj().Name() - info.mask1 |= 1 << methodBit(name[0]) - info.mask2 |= 1 << methodBit(name[len(name)-1]) - } - } - - // satisfies(T, U) reports whether type T satisfies type U. - // U must be an interface. - // - // Since there are thousands of types (and thus millions of - // pairs of types) and types.Assignable(T, U) is relatively - // expensive, we compute assignability directly from the - // method sets. (At least one of T and U must be an - // interface.) - // - // We use a trick (thanks gri!) related to a Bloom filter to - // quickly reject most tests, which are false. For each - // method set, we precompute a mask, a set of bits, one per - // distinct initial byte of each method name. Thus the mask - // for io.ReadWriter would be {'R','W'}. AssignableTo(T, U) - // cannot be true unless mask(T)&mask(U)==mask(U). - // - // As with a Bloom filter, we can improve precision by testing - // additional hashes, e.g. using the last letter of each - // method name, so long as the subset mask property holds. - // - // When analyzing the standard library, there are about 1e6 - // calls to satisfies(), of which 0.6% return true. With a - // 1-hash filter, 95% of calls avoid the expensive check; with - // a 2-hash filter, this grows to 98.2%. - satisfies := func(T, U *msetInfo) bool { - return T.mask1&U.mask1 == U.mask1 && - T.mask2&U.mask2 == U.mask2 && - containsAllIdsOf(T.mset, U.mset) - } - - // Information about a named type N, and perhaps also *N. - type namedInfo struct { - isInterface bool - base msetInfo // N - ptr msetInfo // *N, iff N !isInterface - } - - var infos []namedInfo - - // Precompute the method sets and their masks. - for _, N := range allNamed { - var info namedInfo - initMsetInfo(&info.base, N) - _, info.isInterface = N.Underlying().(*types.Interface) - if !info.isInterface { - initMsetInfo(&info.ptr, types.NewPointer(N)) - } - - if info.base.mask1|info.ptr.mask1 == 0 { - continue // neither N nor *N has methods - } - - infos = append(infos, info) - } - - facts := make(map[*types.Named]implementsFacts) - - // Test all pairs of distinct named types (T, U). - // TODO(adonovan): opt: compute (U, T) at the same time. - for t := range infos { - T := &infos[t] - var to, from, fromPtr []types.Type - for u := range infos { - if t == u { - continue - } - U := &infos[u] - switch { - case T.isInterface && U.isInterface: - if satisfies(&U.base, &T.base) { - to = append(to, U.base.typ) - } - if satisfies(&T.base, &U.base) { - from = append(from, U.base.typ) - } - case T.isInterface: // U concrete - if satisfies(&U.base, &T.base) { - to = append(to, U.base.typ) - } else if satisfies(&U.ptr, &T.base) { - to = append(to, U.ptr.typ) - } - case U.isInterface: // T concrete - if satisfies(&T.base, &U.base) { - from = append(from, U.base.typ) - } else if satisfies(&T.ptr, &U.base) { - fromPtr = append(fromPtr, U.base.typ) - } - } - } - - // Sort types (arbitrarily) to avoid nondeterminism. - sort.Sort(typesByString(to)) - sort.Sort(typesByString(from)) - sort.Sort(typesByString(fromPtr)) - - facts[T.base.typ.(*types.Named)] = implementsFacts{to, from, fromPtr} - } - - return facts -} - -type implementsFacts struct { - to []types.Type // named or ptr-to-named types assignable to interface T - from []types.Type // named interfaces assignable from T - fromPtr []types.Type // named interfaces assignable only from *T -} - -type typesByString []types.Type - -func (p typesByString) Len() int { return len(p) } -func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() } -func (p typesByString) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - -// methodBit returns the index of x in [a-zA-Z], or 52 if not found. -func methodBit(x byte) uint64 { - switch { - case 'a' <= x && x <= 'z': - return uint64(x - 'a') - case 'A' <= x && x <= 'Z': - return uint64(26 + x - 'A') - } - return 52 // all other bytes -} - -// containsAllIdsOf reports whether the method identifiers of T are a -// superset of those in U. If U belongs to an interface type, the -// result is equal to types.Assignable(T, U), but is cheaper to compute. -// -// TODO(gri): make this a method of *types.MethodSet. -// -func containsAllIdsOf(T, U *types.MethodSet) bool { - t, tlen := 0, T.Len() - u, ulen := 0, U.Len() - for t < tlen && u < ulen { - tMeth := T.At(t).Obj() - uMeth := U.At(u).Obj() - tId := tMeth.Id() - uId := uMeth.Id() - if tId > uId { - // U has a method T lacks: fail. - return false - } - if tId < uId { - // T has a method U lacks: ignore it. - t++ - continue - } - // U and T both have a method of this Id. Check types. - if !types.Identical(tMeth.Type(), uMeth.Type()) { - return false // type mismatch - } - u++ - t++ - } - return u == ulen -} diff --git a/godoc/analysis/peers14.go b/godoc/analysis/peers14.go deleted file mode 100644 index ba5e8d6308..0000000000 --- a/godoc/analysis/peers14.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright 2014 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. - -// +build !go1.5 - -package analysis - -// This file computes the channel "peers" relation over all pairs of -// channel operations in the program. The peers are displayed in the -// lower pane when a channel operation (make, <-, close) is clicked. - -// TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too, -// then enable reflection in PTA. - -import ( - "fmt" - "go/token" - - "golang.org/x/tools/go/pointer" - "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/types" -) - -func (a *analysis) doChannelPeers(ptsets map[ssa.Value]pointer.Pointer) { - addSendRecv := func(j *commJSON, op chanOp) { - j.Ops = append(j.Ops, commOpJSON{ - Op: anchorJSON{ - Text: op.mode, - Href: a.posURL(op.pos, op.len), - }, - Fn: prettyFunc(nil, op.fn), - }) - } - - // Build an undirected bipartite multigraph (binary relation) - // of MakeChan ops and send/recv/close ops. - // - // TODO(adonovan): opt: use channel element types to partition - // the O(n^2) problem into subproblems. - aliasedOps := make(map[*ssa.MakeChan][]chanOp) - opToMakes := make(map[chanOp][]*ssa.MakeChan) - for _, op := range a.ops { - // Combine the PT sets from all contexts. - var makes []*ssa.MakeChan // aliased ops - ptr, ok := ptsets[op.ch] - if !ok { - continue // e.g. channel op in dead code - } - for _, label := range ptr.PointsTo().Labels() { - makechan, ok := label.Value().(*ssa.MakeChan) - if !ok { - continue // skip intrinsically-created channels for now - } - if makechan.Pos() == token.NoPos { - continue // not possible? - } - makes = append(makes, makechan) - aliasedOps[makechan] = append(aliasedOps[makechan], op) - } - opToMakes[op] = makes - } - - // Now that complete relation is built, build links for ops. - for _, op := range a.ops { - v := commJSON{ - Ops: []commOpJSON{}, // (JS wants non-nil) - } - ops := make(map[chanOp]bool) - for _, makechan := range opToMakes[op] { - v.Ops = append(v.Ops, commOpJSON{ - Op: anchorJSON{ - Text: "made", - Href: a.posURL(makechan.Pos()-token.Pos(len("make")), - len("make")), - }, - Fn: makechan.Parent().RelString(op.fn.Package().Pkg), - }) - for _, op := range aliasedOps[makechan] { - ops[op] = true - } - } - for op := range ops { - addSendRecv(&v, op) - } - - // Add links for each aliased op. - fi, offset := a.fileAndOffset(op.pos) - fi.addLink(aLink{ - start: offset, - end: offset + op.len, - title: "show channel ops", - onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)), - }) - } - // Add links for makechan ops themselves. - for makechan, ops := range aliasedOps { - v := commJSON{ - Ops: []commOpJSON{}, // (JS wants non-nil) - } - for _, op := range ops { - addSendRecv(&v, op) - } - - fi, offset := a.fileAndOffset(makechan.Pos()) - fi.addLink(aLink{ - start: offset - len("make"), - end: offset, - title: "show channel ops", - onclick: fmt.Sprintf("onClickComm(%d)", fi.addData(v)), - }) - } -} - -// -- utilities -------------------------------------------------------- - -// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), close(), or a SelectState. -// Derived from oracle/peers.go. -type chanOp struct { - ch ssa.Value - mode string // sent|received|closed - pos token.Pos - len int - fn *ssa.Function -} - -// chanOps returns a slice of all the channel operations in the instruction. -// Derived from oracle/peers.go. -func chanOps(instr ssa.Instruction) []chanOp { - fn := instr.Parent() - var ops []chanOp - switch instr := instr.(type) { - case *ssa.UnOp: - if instr.Op == token.ARROW { - // TODO(adonovan): don't assume <-ch; could be 'range ch'. - ops = append(ops, chanOp{instr.X, "received", instr.Pos(), len("<-"), fn}) - } - case *ssa.Send: - ops = append(ops, chanOp{instr.Chan, "sent", instr.Pos(), len("<-"), fn}) - case *ssa.Select: - for _, st := range instr.States { - mode := "received" - if st.Dir == types.SendOnly { - mode = "sent" - } - ops = append(ops, chanOp{st.Chan, mode, st.Pos, len("<-"), fn}) - } - case ssa.CallInstruction: - call := instr.Common() - if blt, ok := call.Value.(*ssa.Builtin); ok && blt.Name() == "close" { - pos := instr.Common().Pos() - ops = append(ops, chanOp{call.Args[0], "closed", pos - token.Pos(len("close")), len("close("), fn}) - } - } - return ops -} diff --git a/godoc/analysis/typeinfo14.go b/godoc/analysis/typeinfo14.go deleted file mode 100644 index d10abcecd2..0000000000 --- a/godoc/analysis/typeinfo14.go +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright 2014 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. - -// +build !go1.5 - -package analysis - -// This file computes the markup for information from go/types: -// IMPORTS, identifier RESOLUTION, METHOD SETS, size/alignment, and -// the IMPLEMENTS relation. -// -// IMPORTS links connect import specs to the documentation for the -// imported package. -// -// RESOLUTION links referring identifiers to their defining -// identifier, and adds tooltips for kind and type. -// -// METHOD SETS, size/alignment, and the IMPLEMENTS relation are -// displayed in the lower pane when a type's defining identifier is -// clicked. - -import ( - "fmt" - "reflect" - "strconv" - "strings" - - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/types" - "golang.org/x/tools/go/types/typeutil" -) - -// TODO(adonovan): audit to make sure it's safe on ill-typed packages. - -// TODO(adonovan): use same Sizes as loader.Config. -var sizes = types.StdSizes{8, 8} - -func (a *analysis) doTypeInfo(info *loader.PackageInfo, implements map[*types.Named]implementsFacts) { - // We must not assume the corresponding SSA packages were - // created (i.e. were transitively error-free). - - // IMPORTS - for _, f := range info.Files { - // Package decl. - fi, offset := a.fileAndOffset(f.Name.Pos()) - fi.addLink(aLink{ - start: offset, - end: offset + len(f.Name.Name), - title: "Package docs for " + info.Pkg.Path(), - // TODO(adonovan): fix: we're putting the untrusted Path() - // into a trusted field. What's the appropriate sanitizer? - href: "/pkg/" + info.Pkg.Path(), - }) - - // Import specs. - for _, imp := range f.Imports { - // Remove quotes. - L := int(imp.End()-imp.Path.Pos()) - len(`""`) - path, _ := strconv.Unquote(imp.Path.Value) - fi, offset := a.fileAndOffset(imp.Path.Pos()) - fi.addLink(aLink{ - start: offset + 1, - end: offset + 1 + L, - title: "Package docs for " + path, - // TODO(adonovan): fix: we're putting the untrusted path - // into a trusted field. What's the appropriate sanitizer? - href: "/pkg/" + path, - }) - } - } - - // RESOLUTION - qualifier := types.RelativeTo(info.Pkg) - for id, obj := range info.Uses { - // Position of the object definition. - pos := obj.Pos() - Len := len(obj.Name()) - - // Correct the position for non-renaming import specs. - // import "sync/atomic" - // ^^^^^^^^^^^ - if obj, ok := obj.(*types.PkgName); ok && id.Name == obj.Imported().Name() { - // Assume this is a non-renaming import. - // NB: not true for degenerate renamings: `import foo "foo"`. - pos++ - Len = len(obj.Imported().Path()) - } - - if obj.Pkg() == nil { - continue // don't mark up built-ins. - } - - fi, offset := a.fileAndOffset(id.NamePos) - fi.addLink(aLink{ - start: offset, - end: offset + len(id.Name), - title: types.ObjectString(obj, qualifier), - href: a.posURL(pos, Len), - }) - } - - // IMPLEMENTS & METHOD SETS - for _, obj := range info.Defs { - if obj, ok := obj.(*types.TypeName); ok { - a.namedType(obj, implements) - } - } -} - -func (a *analysis) namedType(obj *types.TypeName, implements map[*types.Named]implementsFacts) { - qualifier := types.RelativeTo(obj.Pkg()) - T := obj.Type().(*types.Named) - v := &TypeInfoJSON{ - Name: obj.Name(), - Size: sizes.Sizeof(T), - Align: sizes.Alignof(T), - Methods: []anchorJSON{}, // (JS wants non-nil) - } - - // addFact adds the fact "is implemented by T" (by) or - // "implements T" (!by) to group. - addFact := func(group *implGroupJSON, T types.Type, by bool) { - Tobj := deref(T).(*types.Named).Obj() - var byKind string - if by { - // Show underlying kind of implementing type, - // e.g. "slice", "array", "struct". - s := reflect.TypeOf(T.Underlying()).String() - byKind = strings.ToLower(strings.TrimPrefix(s, "*types.")) - } - group.Facts = append(group.Facts, implFactJSON{ - ByKind: byKind, - Other: anchorJSON{ - Href: a.posURL(Tobj.Pos(), len(Tobj.Name())), - Text: types.TypeString(T, qualifier), - }, - }) - } - - // IMPLEMENTS - if r, ok := implements[T]; ok { - if isInterface(T) { - // "T is implemented by " ... - // "T is implemented by "... - // "T implements "... - group := implGroupJSON{ - Descr: types.TypeString(T, qualifier), - } - // Show concrete types first; use two passes. - for _, sub := range r.to { - if !isInterface(sub) { - addFact(&group, sub, true) - } - } - for _, sub := range r.to { - if isInterface(sub) { - addFact(&group, sub, true) - } - } - for _, super := range r.from { - addFact(&group, super, false) - } - v.ImplGroups = append(v.ImplGroups, group) - } else { - // T is concrete. - if r.from != nil { - // "T implements "... - group := implGroupJSON{ - Descr: types.TypeString(T, qualifier), - } - for _, super := range r.from { - addFact(&group, super, false) - } - v.ImplGroups = append(v.ImplGroups, group) - } - if r.fromPtr != nil { - // "*C implements "... - group := implGroupJSON{ - Descr: "*" + types.TypeString(T, qualifier), - } - for _, psuper := range r.fromPtr { - addFact(&group, psuper, false) - } - v.ImplGroups = append(v.ImplGroups, group) - } - } - } - - // METHOD SETS - for _, sel := range typeutil.IntuitiveMethodSet(T, &a.prog.MethodSets) { - meth := sel.Obj().(*types.Func) - pos := meth.Pos() // may be 0 for error.Error - v.Methods = append(v.Methods, anchorJSON{ - Href: a.posURL(pos, len(meth.Name())), - Text: types.SelectionString(sel, qualifier), - }) - } - - // Since there can be many specs per decl, we - // can't attach the link to the keyword 'type' - // (as we do with 'func'); we use the Ident. - fi, offset := a.fileAndOffset(obj.Pos()) - fi.addLink(aLink{ - start: offset, - end: offset + len(obj.Name()), - title: fmt.Sprintf("type info for %s", obj.Name()), - onclick: fmt.Sprintf("onClickTypeInfo(%d)", fi.addData(v)), - }) - - // Add info for exported package-level types to the package info. - if obj.Exported() && isPackageLevel(obj) { - // TODO(adonovan): Path is not unique! - // It is possible to declare a non-test package called x_test. - a.result.pkgInfo(obj.Pkg().Path()).addType(v) - } -} - -// -- utilities -------------------------------------------------------- - -func isInterface(T types.Type) bool { return types.IsInterface(T) } - -// deref returns a pointer's element type; otherwise it returns typ. -func deref(typ types.Type) types.Type { - if p, ok := typ.Underlying().(*types.Pointer); ok { - return p.Elem() - } - return typ -} - -// isPackageLevel reports whether obj is a package-level object. -func isPackageLevel(obj types.Object) bool { - return obj.Pkg().Scope().Lookup(obj.Name()) == obj -} diff --git a/oracle/callees14.go b/oracle/callees14.go deleted file mode 100644 index b6d0ffeb85..0000000000 --- a/oracle/callees14.go +++ /dev/null @@ -1,260 +0,0 @@ -// 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. - -// +build !go1.5 - -package oracle - -import ( - "fmt" - "go/ast" - "go/token" - "sort" - - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/pointer" - "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/ssa/ssautil" - "golang.org/x/tools/go/types" - "golang.org/x/tools/oracle/serial" -) - -// Callees reports the possible callees of the function call site -// identified by the specified source location. -func callees(q *Query) error { - lconf := loader.Config{Build: q.Build} - - if err := setPTAScope(&lconf, q.Scope); err != nil { - return err - } - - // Load/parse/type-check the program. - lprog, err := lconf.Load() - if err != nil { - return err - } - q.Fset = lprog.Fset - - qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos - if err != nil { - return err - } - - // Determine the enclosing call for the specified position. - var e *ast.CallExpr - for _, n := range qpos.path { - if e, _ = n.(*ast.CallExpr); e != nil { - break - } - } - if e == nil { - return fmt.Errorf("there is no function call here") - } - // TODO(adonovan): issue an error if the call is "too far - // away" from the current selection, as this most likely is - // not what the user intended. - - // Reject type conversions. - if qpos.info.Types[e.Fun].IsType() { - return fmt.Errorf("this is a type conversion, not a function call") - } - - // Deal with obviously static calls before constructing SSA form. - // Some static calls may yet require SSA construction, - // e.g. f := func(){}; f(). - switch funexpr := unparen(e.Fun).(type) { - case *ast.Ident: - switch obj := qpos.info.Uses[funexpr].(type) { - case *types.Builtin: - // Reject calls to built-ins. - return fmt.Errorf("this is a call to the built-in '%s' operator", obj.Name()) - case *types.Func: - // This is a static function call - q.result = &calleesTypesResult{ - site: e, - callee: obj, - } - return nil - } - case *ast.SelectorExpr: - sel := qpos.info.Selections[funexpr] - if sel == nil { - // qualified identifier. - // May refer to top level function variable - // or to top level function. - callee := qpos.info.Uses[funexpr.Sel] - if obj, ok := callee.(*types.Func); ok { - q.result = &calleesTypesResult{ - site: e, - callee: obj, - } - return nil - } - } else if sel.Kind() == types.MethodVal { - // Inspect the receiver type of the selected method. - // If it is concrete, the call is statically dispatched. - // (Due to implicit field selections, it is not enough to look - // at sel.Recv(), the type of the actual receiver expression.) - method := sel.Obj().(*types.Func) - recvtype := method.Type().(*types.Signature).Recv().Type() - if !types.IsInterface(recvtype) { - // static method call - q.result = &calleesTypesResult{ - site: e, - callee: method, - } - return nil - } - } - } - - prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) - - ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) - if err != nil { - return err - } - - pkg := prog.Package(qpos.info.Pkg) - if pkg == nil { - return fmt.Errorf("no SSA package") - } - - // Defer SSA construction till after errors are reported. - prog.Build() - - // Ascertain calling function and call site. - callerFn := ssa.EnclosingFunction(pkg, qpos.path) - if callerFn == nil { - return fmt.Errorf("no SSA function built for this location (dead code?)") - } - - // Find the call site. - site, err := findCallSite(callerFn, e) - if err != nil { - return err - } - - funcs, err := findCallees(ptaConfig, site) - if err != nil { - return err - } - - q.result = &calleesSSAResult{ - site: site, - funcs: funcs, - } - return nil -} - -func findCallSite(fn *ssa.Function, call *ast.CallExpr) (ssa.CallInstruction, error) { - instr, _ := fn.ValueForExpr(call) - callInstr, _ := instr.(ssa.CallInstruction) - if instr == nil { - return nil, fmt.Errorf("this call site is unreachable in this analysis") - } - return callInstr, nil -} - -func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Function, error) { - // Avoid running the pointer analysis for static calls. - if callee := site.Common().StaticCallee(); callee != nil { - switch callee.String() { - case "runtime.SetFinalizer", "(reflect.Value).Call": - // The PTA treats calls to these intrinsics as dynamic. - // TODO(adonovan): avoid reliance on PTA internals. - - default: - return []*ssa.Function{callee}, nil // singleton - } - } - - // Dynamic call: use pointer analysis. - conf.BuildCallGraph = true - cg := ptrAnalysis(conf).CallGraph - cg.DeleteSyntheticNodes() - - // Find all call edges from the site. - n := cg.Nodes[site.Parent()] - if n == nil { - return nil, fmt.Errorf("this call site is unreachable in this analysis") - } - calleesMap := make(map[*ssa.Function]bool) - for _, edge := range n.Out { - if edge.Site == site { - calleesMap[edge.Callee.Func] = true - } - } - - // De-duplicate and sort. - funcs := make([]*ssa.Function, 0, len(calleesMap)) - for f := range calleesMap { - funcs = append(funcs, f) - } - sort.Sort(byFuncPos(funcs)) - return funcs, nil -} - -type calleesSSAResult struct { - site ssa.CallInstruction - funcs []*ssa.Function -} - -type calleesTypesResult struct { - site *ast.CallExpr - callee *types.Func -} - -func (r *calleesSSAResult) display(printf printfFunc) { - if len(r.funcs) == 0 { - // dynamic call on a provably nil func/interface - printf(r.site, "%s on nil value", r.site.Common().Description()) - } else { - printf(r.site, "this %s dispatches to:", r.site.Common().Description()) - for _, callee := range r.funcs { - printf(callee, "\t%s", callee) - } - } -} - -func (r *calleesSSAResult) toSerial(res *serial.Result, fset *token.FileSet) { - j := &serial.Callees{ - Pos: fset.Position(r.site.Pos()).String(), - Desc: r.site.Common().Description(), - } - for _, callee := range r.funcs { - j.Callees = append(j.Callees, &serial.CalleesItem{ - Name: callee.String(), - Pos: fset.Position(callee.Pos()).String(), - }) - } - res.Callees = j -} - -func (r *calleesTypesResult) display(printf printfFunc) { - printf(r.site, "this static function call dispatches to:") - printf(r.callee, "\t%s", r.callee.FullName()) -} - -func (r *calleesTypesResult) toSerial(res *serial.Result, fset *token.FileSet) { - j := &serial.Callees{ - Pos: fset.Position(r.site.Pos()).String(), - Desc: "static function call", - } - j.Callees = []*serial.CalleesItem{ - &serial.CalleesItem{ - Name: r.callee.FullName(), - Pos: fset.Position(r.callee.Pos()).String(), - }, - } - res.Callees = j -} - -// NB: byFuncPos is not deterministic across packages since it depends on load order. -// Use lessPos if the tests need it. -type byFuncPos []*ssa.Function - -func (a byFuncPos) Len() int { return len(a) } -func (a byFuncPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() } -func (a byFuncPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/oracle/definition14.go b/oracle/definition14.go deleted file mode 100644 index c22b1fd09b..0000000000 --- a/oracle/definition14.go +++ /dev/null @@ -1,78 +0,0 @@ -// 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. - -// +build !go1.5 - -package oracle - -import ( - "fmt" - "go/ast" - "go/token" - - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/types" - "golang.org/x/tools/oracle/serial" -) - -// definition reports the location of the definition of an identifier. -// -// TODO(adonovan): opt: for intra-file references, the parser's -// resolution might be enough; we should start with that. -// -func definition(q *Query) error { - lconf := loader.Config{Build: q.Build} - allowErrors(&lconf) - - if _, err := importQueryPackage(q.Pos, &lconf); err != nil { - return err - } - - // Load/parse/type-check the program. - lprog, err := lconf.Load() - if err != nil { - return err - } - q.Fset = lprog.Fset - - qpos, err := parseQueryPos(lprog, q.Pos, false) - if err != nil { - return err - } - - id, _ := qpos.path[0].(*ast.Ident) - if id == nil { - return fmt.Errorf("no identifier here") - } - - obj := qpos.info.ObjectOf(id) - if obj == nil { - // Happens for y in "switch y := x.(type)", - // and the package declaration, - // but I think that's all. - return fmt.Errorf("no object for identifier") - } - - q.result = &definitionResult{qpos, obj} - return nil -} - -type definitionResult struct { - qpos *queryPos - obj types.Object // object it denotes -} - -func (r *definitionResult) display(printf printfFunc) { - printf(r.obj, "defined here as %s", r.qpos.objectString(r.obj)) -} - -func (r *definitionResult) toSerial(res *serial.Result, fset *token.FileSet) { - definition := &serial.Definition{ - Desc: r.obj.String(), - } - if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos() - definition.ObjPos = fset.Position(pos).String() - } - res.Definition = definition -} diff --git a/oracle/describe14.go b/oracle/describe14.go deleted file mode 100644 index c8ccc2d844..0000000000 --- a/oracle/describe14.go +++ /dev/null @@ -1,786 +0,0 @@ -// 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. - -// +build !go1.5 - -package oracle - -import ( - "bytes" - "fmt" - "go/ast" - "go/token" - "log" - "os" - "strings" - - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/exact" - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/types" - "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/oracle/serial" -) - -// describe describes the syntax node denoted by the query position, -// including: -// - its syntactic category -// - the definition of its referent (for identifiers) [now redundant] -// - its type and method set (for an expression or type expression) -// -func describe(q *Query) error { - lconf := loader.Config{Build: q.Build} - allowErrors(&lconf) - - if _, err := importQueryPackage(q.Pos, &lconf); err != nil { - return err - } - - // Load/parse/type-check the program. - lprog, err := lconf.Load() - if err != nil { - return err - } - q.Fset = lprog.Fset - - qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos) - if err != nil { - return err - } - - if false { // debugging - fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s", - astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path)) - } - - path, action := findInterestingNode(qpos.info, qpos.path) - switch action { - case actionExpr: - q.result, err = describeValue(qpos, path) - - case actionType: - q.result, err = describeType(qpos, path) - - case actionPackage: - q.result, err = describePackage(qpos, path) - - case actionStmt: - q.result, err = describeStmt(qpos, path) - - case actionUnknown: - q.result = &describeUnknownResult{path[0]} - - default: - panic(action) // unreachable - } - return err -} - -type describeUnknownResult struct { - node ast.Node -} - -func (r *describeUnknownResult) display(printf printfFunc) { - // Nothing much to say about misc syntax. - printf(r.node, "%s", astutil.NodeDescription(r.node)) -} - -func (r *describeUnknownResult) toSerial(res *serial.Result, fset *token.FileSet) { - res.Describe = &serial.Describe{ - Desc: astutil.NodeDescription(r.node), - Pos: fset.Position(r.node.Pos()).String(), - } -} - -type action int - -const ( - actionUnknown action = iota // None of the below - actionExpr // FuncDecl, true Expr or Ident(types.{Const,Var}) - actionType // type Expr or Ident(types.TypeName). - actionStmt // Stmt or Ident(types.Label) - actionPackage // Ident(types.Package) or ImportSpec -) - -// findInterestingNode classifies the syntax node denoted by path as one of: -// - an expression, part of an expression or a reference to a constant -// or variable; -// - a type, part of a type, or a reference to a named type; -// - a statement, part of a statement, or a label referring to a statement; -// - part of a package declaration or import spec. -// - none of the above. -// and returns the most "interesting" associated node, which may be -// the same node, an ancestor or a descendent. -// -func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.Node, action) { - // TODO(adonovan): integrate with go/types/stdlib_test.go and - // apply this to every AST node we can find to make sure it - // doesn't crash. - - // TODO(adonovan): audit for ParenExpr safety, esp. since we - // traverse up and down. - - // TODO(adonovan): if the users selects the "." in - // "fmt.Fprintf()", they'll get an ambiguous selection error; - // we won't even reach here. Can we do better? - - // TODO(adonovan): describing a field within 'type T struct {...}' - // describes the (anonymous) struct type and concludes "no methods". - // We should ascend to the enclosing type decl, if any. - - for len(path) > 0 { - switch n := path[0].(type) { - case *ast.GenDecl: - if len(n.Specs) == 1 { - // Descend to sole {Import,Type,Value}Spec child. - path = append([]ast.Node{n.Specs[0]}, path...) - continue - } - return path, actionUnknown // uninteresting - - case *ast.FuncDecl: - // Descend to function name. - path = append([]ast.Node{n.Name}, path...) - continue - - case *ast.ImportSpec: - return path, actionPackage - - case *ast.ValueSpec: - if len(n.Names) == 1 { - // Descend to sole Ident child. - path = append([]ast.Node{n.Names[0]}, path...) - continue - } - return path, actionUnknown // uninteresting - - case *ast.TypeSpec: - // Descend to type name. - path = append([]ast.Node{n.Name}, path...) - continue - - case ast.Stmt: - return path, actionStmt - - case *ast.ArrayType, - *ast.StructType, - *ast.FuncType, - *ast.InterfaceType, - *ast.MapType, - *ast.ChanType: - return path, actionType - - case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause: - return path, actionUnknown // uninteresting - - case *ast.Ellipsis: - // Continue to enclosing node. - // e.g. [...]T in ArrayType - // f(x...) in CallExpr - // f(x...T) in FuncType - - case *ast.Field: - // TODO(adonovan): this needs more thought, - // since fields can be so many things. - if len(n.Names) == 1 { - // Descend to sole Ident child. - path = append([]ast.Node{n.Names[0]}, path...) - continue - } - // Zero names (e.g. anon field in struct) - // or multiple field or param names: - // continue to enclosing field list. - - case *ast.FieldList: - // Continue to enclosing node: - // {Struct,Func,Interface}Type or FuncDecl. - - case *ast.BasicLit: - if _, ok := path[1].(*ast.ImportSpec); ok { - return path[1:], actionPackage - } - return path, actionExpr - - case *ast.SelectorExpr: - // TODO(adonovan): use Selections info directly. - if pkginfo.Uses[n.Sel] == nil { - // TODO(adonovan): is this reachable? - return path, actionUnknown - } - // Descend to .Sel child. - path = append([]ast.Node{n.Sel}, path...) - continue - - case *ast.Ident: - switch pkginfo.ObjectOf(n).(type) { - case *types.PkgName: - return path, actionPackage - - case *types.Const: - return path, actionExpr - - case *types.Label: - return path, actionStmt - - case *types.TypeName: - return path, actionType - - case *types.Var: - // For x in 'struct {x T}', return struct type, for now. - if _, ok := path[1].(*ast.Field); ok { - _ = path[2].(*ast.FieldList) // assertion - if _, ok := path[3].(*ast.StructType); ok { - return path[3:], actionType - } - } - return path, actionExpr - - case *types.Func: - return path, actionExpr - - case *types.Builtin: - // For reference to built-in function, return enclosing call. - path = path[1:] // ascend to enclosing function call - continue - - case *types.Nil: - return path, actionExpr - } - - // No object. - switch path[1].(type) { - case *ast.SelectorExpr: - // Return enclosing selector expression. - return path[1:], actionExpr - - case *ast.Field: - // TODO(adonovan): test this. - // e.g. all f in: - // struct { f, g int } - // interface { f() } - // func (f T) method(f, g int) (f, g bool) - // - // switch path[3].(type) { - // case *ast.FuncDecl: - // case *ast.StructType: - // case *ast.InterfaceType: - // } - // - // return path[1:], actionExpr - // - // Unclear what to do with these. - // Struct.Fields -- field - // Interface.Methods -- field - // FuncType.{Params.Results} -- actionExpr - // FuncDecl.Recv -- actionExpr - - case *ast.File: - // 'package foo' - return path, actionPackage - - case *ast.ImportSpec: - // TODO(adonovan): fix: why no package object? go/types bug? - return path[1:], actionPackage - - default: - // e.g. blank identifier - // or y in "switch y := x.(type)" - // or code in a _test.go file that's not part of the package. - log.Printf("unknown reference %s in %T\n", n, path[1]) - return path, actionUnknown - } - - case *ast.StarExpr: - if pkginfo.Types[n].IsType() { - return path, actionType - } - return path, actionExpr - - case ast.Expr: - // All Expr but {BasicLit,Ident,StarExpr} are - // "true" expressions that evaluate to a value. - return path, actionExpr - } - - // Ascend to parent. - path = path[1:] - } - - return nil, actionUnknown // unreachable -} - -func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) { - var expr ast.Expr - var obj types.Object - switch n := path[0].(type) { - case *ast.ValueSpec: - // ambiguous ValueSpec containing multiple names - return nil, fmt.Errorf("multiple value specification") - case *ast.Ident: - obj = qpos.info.ObjectOf(n) - expr = n - case ast.Expr: - expr = n - default: - // TODO(adonovan): is this reachable? - return nil, fmt.Errorf("unexpected AST for expr: %T", n) - } - - typ := qpos.info.TypeOf(expr) - constVal := qpos.info.Types[expr].Value - - return &describeValueResult{ - qpos: qpos, - expr: expr, - typ: typ, - constVal: constVal, - obj: obj, - }, nil -} - -type describeValueResult struct { - qpos *queryPos - expr ast.Expr // query node - typ types.Type // type of expression - constVal exact.Value // value of expression, if constant - obj types.Object // var/func/const object, if expr was Ident -} - -func (r *describeValueResult) display(printf printfFunc) { - var prefix, suffix string - if r.constVal != nil { - suffix = fmt.Sprintf(" of constant value %s", constValString(r.constVal)) - } - switch obj := r.obj.(type) { - case *types.Func: - if recv := obj.Type().(*types.Signature).Recv(); recv != nil { - if _, ok := recv.Type().Underlying().(*types.Interface); ok { - prefix = "interface method " - } else { - prefix = "method " - } - } - } - - // Describe the expression. - if r.obj != nil { - if r.obj.Pos() == r.expr.Pos() { - // defining ident - printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix) - } else { - // referring ident - printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix) - if def := r.obj.Pos(); def != token.NoPos { - printf(def, "defined here") - } - } - } else { - desc := astutil.NodeDescription(r.expr) - if suffix != "" { - // constant expression - printf(r.expr, "%s%s", desc, suffix) - } else { - // non-constant expression - printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ)) - } - } -} - -func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) { - var value, objpos string - if r.constVal != nil { - value = r.constVal.String() - } - if r.obj != nil { - objpos = fset.Position(r.obj.Pos()).String() - } - - res.Describe = &serial.Describe{ - Desc: astutil.NodeDescription(r.expr), - Pos: fset.Position(r.expr.Pos()).String(), - Detail: "value", - Value: &serial.DescribeValue{ - Type: r.qpos.typeString(r.typ), - Value: value, - ObjPos: objpos, - }, - } -} - -// ---- TYPE ------------------------------------------------------------ - -func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) { - var description string - var t types.Type - switch n := path[0].(type) { - case *ast.Ident: - t = qpos.info.TypeOf(n) - switch t := t.(type) { - case *types.Basic: - description = "reference to built-in " - - case *types.Named: - isDef := t.Obj().Pos() == n.Pos() // see caveats at isDef above - if isDef { - description = "definition of " - } else { - description = "reference to " - } - } - - case ast.Expr: - t = qpos.info.TypeOf(n) - - default: - // Unreachable? - return nil, fmt.Errorf("unexpected AST for type: %T", n) - } - - description = description + "type " + qpos.typeString(t) - - // Show sizes for structs and named types (it's fairly obvious for others). - switch t.(type) { - case *types.Named, *types.Struct: - szs := types.StdSizes{8, 8} // assume amd64 - description = fmt.Sprintf("%s (size %d, align %d)", description, - szs.Sizeof(t), szs.Alignof(t)) - } - - return &describeTypeResult{ - qpos: qpos, - node: path[0], - description: description, - typ: t, - methods: accessibleMethods(t, qpos.info.Pkg), - }, nil -} - -type describeTypeResult struct { - qpos *queryPos - node ast.Node - description string - typ types.Type - methods []*types.Selection -} - -func (r *describeTypeResult) display(printf printfFunc) { - printf(r.node, "%s", r.description) - - // Show the underlying type for a reference to a named type. - if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() { - printf(nt.Obj(), "defined as %s", r.qpos.typeString(nt.Underlying())) - } - - // Print the method set, if the type kind is capable of bearing methods. - switch r.typ.(type) { - case *types.Interface, *types.Struct, *types.Named: - if len(r.methods) > 0 { - printf(r.node, "Method set:") - for _, meth := range r.methods { - // TODO(adonovan): print these relative - // to the owning package, not the - // query package. - printf(meth.Obj(), "\t%s", r.qpos.selectionString(meth)) - } - } else { - printf(r.node, "No methods.") - } - } -} - -func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) { - var namePos, nameDef string - if nt, ok := r.typ.(*types.Named); ok { - namePos = fset.Position(nt.Obj().Pos()).String() - nameDef = nt.Underlying().String() - } - res.Describe = &serial.Describe{ - Desc: r.description, - Pos: fset.Position(r.node.Pos()).String(), - Detail: "type", - Type: &serial.DescribeType{ - Type: r.qpos.typeString(r.typ), - NamePos: namePos, - NameDef: nameDef, - Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset), - }, - } -} - -// ---- PACKAGE ------------------------------------------------------------ - -func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) { - var description string - var pkg *types.Package - switch n := path[0].(type) { - case *ast.ImportSpec: - var obj types.Object - if n.Name != nil { - obj = qpos.info.Defs[n.Name] - } else { - obj = qpos.info.Implicits[n] - } - pkgname, _ := obj.(*types.PkgName) - if pkgname == nil { - return nil, fmt.Errorf("can't import package %s", n.Path.Value) - } - pkg = pkgname.Imported() - description = fmt.Sprintf("import of package %q", pkg.Path()) - - case *ast.Ident: - if _, isDef := path[1].(*ast.File); isDef { - // e.g. package id - pkg = qpos.info.Pkg - description = fmt.Sprintf("definition of package %q", pkg.Path()) - } else { - // e.g. import id "..." - // or id.F() - pkg = qpos.info.ObjectOf(n).(*types.PkgName).Imported() - description = fmt.Sprintf("reference to package %q", pkg.Path()) - } - - default: - // Unreachable? - return nil, fmt.Errorf("unexpected AST for package: %T", n) - } - - var members []*describeMember - // NB: "unsafe" has no types.Package - if pkg != nil { - // Enumerate the accessible package members - // in lexicographic order. - for _, name := range pkg.Scope().Names() { - if pkg == qpos.info.Pkg || ast.IsExported(name) { - mem := pkg.Scope().Lookup(name) - var methods []*types.Selection - if mem, ok := mem.(*types.TypeName); ok { - methods = accessibleMethods(mem.Type(), qpos.info.Pkg) - } - members = append(members, &describeMember{ - mem, - methods, - }) - - } - } - } - - return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil -} - -type describePackageResult struct { - fset *token.FileSet - node ast.Node - description string - pkg *types.Package - members []*describeMember // in lexicographic name order -} - -type describeMember struct { - obj types.Object - methods []*types.Selection // in types.MethodSet order -} - -func (r *describePackageResult) display(printf printfFunc) { - printf(r.node, "%s", r.description) - - // Compute max width of name "column". - maxname := 0 - for _, mem := range r.members { - if l := len(mem.obj.Name()); l > maxname { - maxname = l - } - } - - for _, mem := range r.members { - printf(mem.obj, "\t%s", formatMember(mem.obj, maxname)) - for _, meth := range mem.methods { - printf(meth.Obj(), "\t\t%s", types.SelectionString(meth, types.RelativeTo(r.pkg))) - } - } -} - -func formatMember(obj types.Object, maxname int) string { - qualifier := types.RelativeTo(obj.Pkg()) - var buf bytes.Buffer - fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name()) - switch obj := obj.(type) { - case *types.Const: - fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), constValString(obj.Val())) - - case *types.Func: - fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier)) - - case *types.TypeName: - // Abbreviate long aggregate type names. - var abbrev string - switch t := obj.Type().Underlying().(type) { - case *types.Interface: - if t.NumMethods() > 1 { - abbrev = "interface{...}" - } - case *types.Struct: - if t.NumFields() > 1 { - abbrev = "struct{...}" - } - } - if abbrev == "" { - fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type().Underlying(), qualifier)) - } else { - fmt.Fprintf(&buf, " %s", abbrev) - } - - case *types.Var: - fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier)) - } - return buf.String() -} - -func (r *describePackageResult) toSerial(res *serial.Result, fset *token.FileSet) { - var members []*serial.DescribeMember - for _, mem := range r.members { - typ := mem.obj.Type() - var val string - switch mem := mem.obj.(type) { - case *types.Const: - val = constValString(mem.Val()) - case *types.TypeName: - typ = typ.Underlying() - } - members = append(members, &serial.DescribeMember{ - Name: mem.obj.Name(), - Type: typ.String(), - Value: val, - Pos: fset.Position(mem.obj.Pos()).String(), - Kind: tokenOf(mem.obj), - Methods: methodsToSerial(r.pkg, mem.methods, fset), - }) - } - res.Describe = &serial.Describe{ - Desc: r.description, - Pos: fset.Position(r.node.Pos()).String(), - Detail: "package", - Package: &serial.DescribePackage{ - Path: r.pkg.Path(), - Members: members, - }, - } -} - -func tokenOf(o types.Object) string { - switch o.(type) { - case *types.Func: - return "func" - case *types.Var: - return "var" - case *types.TypeName: - return "type" - case *types.Const: - return "const" - case *types.PkgName: - return "package" - case *types.Builtin: - return "builtin" // e.g. when describing package "unsafe" - case *types.Nil: - return "nil" - case *types.Label: - return "label" - } - panic(o) -} - -// ---- STATEMENT ------------------------------------------------------------ - -func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) { - var description string - switch n := path[0].(type) { - case *ast.Ident: - if qpos.info.Defs[n] != nil { - description = "labelled statement" - } else { - description = "reference to labelled statement" - } - - default: - // Nothing much to say about statements. - description = astutil.NodeDescription(n) - } - return &describeStmtResult{qpos.fset, path[0], description}, nil -} - -type describeStmtResult struct { - fset *token.FileSet - node ast.Node - description string -} - -func (r *describeStmtResult) display(printf printfFunc) { - printf(r.node, "%s", r.description) -} - -func (r *describeStmtResult) toSerial(res *serial.Result, fset *token.FileSet) { - res.Describe = &serial.Describe{ - Desc: r.description, - Pos: fset.Position(r.node.Pos()).String(), - Detail: "unknown", - } -} - -// ------------------- Utilities ------------------- - -// pathToString returns a string containing the concrete types of the -// nodes in path. -func pathToString(path []ast.Node) string { - var buf bytes.Buffer - fmt.Fprint(&buf, "[") - for i, n := range path { - if i > 0 { - fmt.Fprint(&buf, " ") - } - fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast.")) - } - fmt.Fprint(&buf, "]") - return buf.String() -} - -func accessibleMethods(t types.Type, from *types.Package) []*types.Selection { - var methods []*types.Selection - for _, meth := range typeutil.IntuitiveMethodSet(t, nil) { - if isAccessibleFrom(meth.Obj(), from) { - methods = append(methods, meth) - } - } - return methods -} - -func isAccessibleFrom(obj types.Object, pkg *types.Package) bool { - return ast.IsExported(obj.Name()) || obj.Pkg() == pkg -} - -func methodsToSerial(this *types.Package, methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod { - qualifier := types.RelativeTo(this) - var jmethods []serial.DescribeMethod - for _, meth := range methods { - var ser serial.DescribeMethod - if meth != nil { // may contain nils when called by implements (on a method) - ser = serial.DescribeMethod{ - Name: types.SelectionString(meth, qualifier), - Pos: fset.Position(meth.Obj().Pos()).String(), - } - } - jmethods = append(jmethods, ser) - } - return jmethods -} - -// constValString emulates Go 1.6's go/constant.ExactString well enough -// to make the tests pass. This is just a stopgap until we throw away -// all the *14.go files. -func constValString(v exact.Value) string { - if v.Kind() == exact.Float { - f, _ := exact.Float64Val(v) - return fmt.Sprintf("%g", f) - } - return v.String() -} diff --git a/oracle/freevars14.go b/oracle/freevars14.go deleted file mode 100644 index 760a9e6cfb..0000000000 --- a/oracle/freevars14.go +++ /dev/null @@ -1,224 +0,0 @@ -// 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. - -// +build !go1.5 - -package oracle - -import ( - "bytes" - "go/ast" - "go/printer" - "go/token" - "sort" - - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/types" - "golang.org/x/tools/oracle/serial" -) - -// freevars displays the lexical (not package-level) free variables of -// the selection. -// -// It treats A.B.C as a separate variable from A to reveal the parts -// of an aggregate type that are actually needed. -// This aids refactoring. -// -// TODO(adonovan): optionally display the free references to -// file/package scope objects, and to objects from other packages. -// Depending on where the resulting function abstraction will go, -// these might be interesting. Perhaps group the results into three -// bands. -// -func freevars(q *Query) error { - lconf := loader.Config{Build: q.Build} - allowErrors(&lconf) - - if _, err := importQueryPackage(q.Pos, &lconf); err != nil { - return err - } - - // Load/parse/type-check the program. - lprog, err := lconf.Load() - if err != nil { - return err - } - q.Fset = lprog.Fset - - qpos, err := parseQueryPos(lprog, q.Pos, false) - if err != nil { - return err - } - - file := qpos.path[len(qpos.path)-1] // the enclosing file - fileScope := qpos.info.Scopes[file] - pkgScope := fileScope.Parent() - - // The id and sel functions return non-nil if they denote an - // object o or selection o.x.y that is referenced by the - // selection but defined neither within the selection nor at - // file scope, i.e. it is in the lexical environment. - var id func(n *ast.Ident) types.Object - var sel func(n *ast.SelectorExpr) types.Object - - sel = func(n *ast.SelectorExpr) types.Object { - switch x := unparen(n.X).(type) { - case *ast.SelectorExpr: - return sel(x) - case *ast.Ident: - return id(x) - } - return nil - } - - id = func(n *ast.Ident) types.Object { - obj := qpos.info.Uses[n] - if obj == nil { - return nil // not a reference - } - if _, ok := obj.(*types.PkgName); ok { - return nil // imported package - } - if !(file.Pos() <= obj.Pos() && obj.Pos() <= file.End()) { - return nil // not defined in this file - } - scope := obj.Parent() - if scope == nil { - return nil // e.g. interface method, struct field - } - if scope == fileScope || scope == pkgScope { - return nil // defined at file or package scope - } - if qpos.start <= obj.Pos() && obj.Pos() <= qpos.end { - return nil // defined within selection => not free - } - return obj - } - - // Maps each reference that is free in the selection - // to the object it refers to. - // The map de-duplicates repeated references. - refsMap := make(map[string]freevarsRef) - - // Visit all the identifiers in the selected ASTs. - ast.Inspect(qpos.path[0], func(n ast.Node) bool { - if n == nil { - return true // popping DFS stack - } - - // Is this node contained within the selection? - // (freevars permits inexact selections, - // like two stmts in a block.) - if qpos.start <= n.Pos() && n.End() <= qpos.end { - var obj types.Object - var prune bool - switch n := n.(type) { - case *ast.Ident: - obj = id(n) - - case *ast.SelectorExpr: - obj = sel(n) - prune = true - } - - if obj != nil { - var kind string - switch obj.(type) { - case *types.Var: - kind = "var" - case *types.Func: - kind = "func" - case *types.TypeName: - kind = "type" - case *types.Const: - kind = "const" - case *types.Label: - kind = "label" - default: - panic(obj) - } - - typ := qpos.info.TypeOf(n.(ast.Expr)) - ref := freevarsRef{kind, printNode(lprog.Fset, n), typ, obj} - refsMap[ref.ref] = ref - - if prune { - return false // don't descend - } - } - } - - return true // descend - }) - - refs := make([]freevarsRef, 0, len(refsMap)) - for _, ref := range refsMap { - refs = append(refs, ref) - } - sort.Sort(byRef(refs)) - - q.result = &freevarsResult{ - qpos: qpos, - refs: refs, - } - return nil -} - -type freevarsResult struct { - qpos *queryPos - refs []freevarsRef -} - -type freevarsRef struct { - kind string - ref string - typ types.Type - obj types.Object -} - -func (r *freevarsResult) display(printf printfFunc) { - if len(r.refs) == 0 { - printf(r.qpos, "No free identifiers.") - } else { - printf(r.qpos, "Free identifiers:") - qualifier := types.RelativeTo(r.qpos.info.Pkg) - for _, ref := range r.refs { - // Avoid printing "type T T". - var typstr string - if ref.kind != "type" { - typstr = " " + types.TypeString(ref.typ, qualifier) - } - printf(ref.obj, "%s %s%s", ref.kind, ref.ref, typstr) - } - } -} - -func (r *freevarsResult) toSerial(res *serial.Result, fset *token.FileSet) { - var refs []*serial.FreeVar - for _, ref := range r.refs { - refs = append(refs, - &serial.FreeVar{ - Pos: fset.Position(ref.obj.Pos()).String(), - Kind: ref.kind, - Ref: ref.ref, - Type: ref.typ.String(), - }) - } - res.Freevars = refs -} - -// -------- utils -------- - -type byRef []freevarsRef - -func (p byRef) Len() int { return len(p) } -func (p byRef) Less(i, j int) bool { return p[i].ref < p[j].ref } -func (p byRef) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - -// printNode returns the pretty-printed syntax of n. -func printNode(fset *token.FileSet, n ast.Node) string { - var buf bytes.Buffer - printer.Fprint(&buf, fset, n) - return buf.String() -} diff --git a/oracle/implements14.go b/oracle/implements14.go deleted file mode 100644 index 9f4c370210..0000000000 --- a/oracle/implements14.go +++ /dev/null @@ -1,354 +0,0 @@ -// 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. - -// +build !go1.5 - -package oracle - -import ( - "fmt" - "go/ast" - "go/token" - "reflect" - "sort" - "strings" - - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/types" - "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/oracle/serial" - "golang.org/x/tools/refactor/importgraph" -) - -// Implements displays the "implements" relation as it pertains to the -// selected type. -// If the selection is a method, 'implements' displays -// the corresponding methods of the types that would have been reported -// by an implements query on the receiver type. -// -func implements(q *Query) error { - lconf := loader.Config{Build: q.Build} - allowErrors(&lconf) - - qpkg, err := importQueryPackage(q.Pos, &lconf) - if err != nil { - return err - } - - // Set the packages to search. - if len(q.Scope) > 0 { - // Inspect all packages in the analysis scope, if specified. - if err := setPTAScope(&lconf, q.Scope); err != nil { - return err - } - } else { - // Otherwise inspect the forward and reverse - // transitive closure of the selected package. - // (In theory even this is incomplete.) - _, rev, _ := importgraph.Build(q.Build) - for path := range rev.Search(qpkg) { - lconf.ImportWithTests(path) - } - - // TODO(adonovan): for completeness, we should also - // type-check and inspect function bodies in all - // imported packages. This would be expensive, but we - // could optimize by skipping functions that do not - // contain type declarations. This would require - // changing the loader's TypeCheckFuncBodies hook to - // provide the []*ast.File. - } - - // Load/parse/type-check the program. - lprog, err := lconf.Load() - if err != nil { - return err - } - q.Fset = lprog.Fset - - qpos, err := parseQueryPos(lprog, q.Pos, false) - if err != nil { - return err - } - - // Find the selected type. - path, action := findInterestingNode(qpos.info, qpos.path) - - var method *types.Func - var T types.Type // selected type (receiver if method != nil) - - switch action { - case actionExpr: - // method? - if id, ok := path[0].(*ast.Ident); ok { - if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok { - recv := obj.Type().(*types.Signature).Recv() - if recv == nil { - return fmt.Errorf("this function is not a method") - } - method = obj - T = recv.Type() - } - } - case actionType: - T = qpos.info.TypeOf(path[0].(ast.Expr)) - } - if T == nil { - return fmt.Errorf("no type or method here") - } - - // Find all named types, even local types (which can have - // methods via promotion) and the built-in "error". - var allNamed []types.Type - for _, info := range lprog.AllPackages { - for _, obj := range info.Defs { - if obj, ok := obj.(*types.TypeName); ok { - allNamed = append(allNamed, obj.Type()) - } - } - } - allNamed = append(allNamed, types.Universe.Lookup("error").Type()) - - var msets typeutil.MethodSetCache - - // Test each named type. - var to, from, fromPtr []types.Type - for _, U := range allNamed { - if isInterface(T) { - if msets.MethodSet(T).Len() == 0 { - continue // empty interface - } - if isInterface(U) { - if msets.MethodSet(U).Len() == 0 { - continue // empty interface - } - - // T interface, U interface - if !types.Identical(T, U) { - if types.AssignableTo(U, T) { - to = append(to, U) - } - if types.AssignableTo(T, U) { - from = append(from, U) - } - } - } else { - // T interface, U concrete - if types.AssignableTo(U, T) { - to = append(to, U) - } else if pU := types.NewPointer(U); types.AssignableTo(pU, T) { - to = append(to, pU) - } - } - } else if isInterface(U) { - if msets.MethodSet(U).Len() == 0 { - continue // empty interface - } - - // T concrete, U interface - if types.AssignableTo(T, U) { - from = append(from, U) - } else if pT := types.NewPointer(T); types.AssignableTo(pT, U) { - fromPtr = append(fromPtr, U) - } - } - } - - var pos interface{} = qpos - if nt, ok := deref(T).(*types.Named); ok { - pos = nt.Obj() - } - - // Sort types (arbitrarily) to ensure test determinism. - sort.Sort(typesByString(to)) - sort.Sort(typesByString(from)) - sort.Sort(typesByString(fromPtr)) - - var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils - if method != nil { - for _, t := range to { - toMethod = append(toMethod, - types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) - } - for _, t := range from { - fromMethod = append(fromMethod, - types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) - } - for _, t := range fromPtr { - fromPtrMethod = append(fromPtrMethod, - types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) - } - } - - q.result = &implementsResult{ - qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod, - } - return nil -} - -type implementsResult struct { - qpos *queryPos - - t types.Type // queried type (not necessarily named) - pos interface{} // pos of t (*types.Name or *QueryPos) - to []types.Type // named or ptr-to-named types assignable to interface T - from []types.Type // named interfaces assignable from T - fromPtr []types.Type // named interfaces assignable only from *T - - // if a method was queried: - method *types.Func // queried method - toMethod []*types.Selection // method of type to[i], if any - fromMethod []*types.Selection // method of type from[i], if any - fromPtrMethod []*types.Selection // method of type fromPtrMethod[i], if any -} - -func (r *implementsResult) display(printf printfFunc) { - relation := "is implemented by" - - meth := func(sel *types.Selection) { - if sel != nil { - printf(sel.Obj(), "\t%s method (%s).%s", - relation, r.qpos.typeString(sel.Recv()), sel.Obj().Name()) - } - } - - if isInterface(r.t) { - if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset - printf(r.pos, "empty interface type %s", r.qpos.typeString(r.t)) - return - } - - if r.method == nil { - printf(r.pos, "interface type %s", r.qpos.typeString(r.t)) - } else { - printf(r.method, "abstract method %s", r.qpos.objectString(r.method)) - } - - // Show concrete types (or methods) first; use two passes. - for i, sub := range r.to { - if !isInterface(sub) { - if r.method == nil { - printf(deref(sub).(*types.Named).Obj(), "\t%s %s type %s", - relation, typeKind(sub), r.qpos.typeString(sub)) - } else { - meth(r.toMethod[i]) - } - } - } - for i, sub := range r.to { - if isInterface(sub) { - if r.method == nil { - printf(sub.(*types.Named).Obj(), "\t%s %s type %s", - relation, typeKind(sub), r.qpos.typeString(sub)) - } else { - meth(r.toMethod[i]) - } - } - } - - relation = "implements" - for i, super := range r.from { - if r.method == nil { - printf(super.(*types.Named).Obj(), "\t%s %s", - relation, r.qpos.typeString(super)) - } else { - meth(r.fromMethod[i]) - } - } - } else { - relation = "implements" - - if r.from != nil { - if r.method == nil { - printf(r.pos, "%s type %s", - typeKind(r.t), r.qpos.typeString(r.t)) - } else { - printf(r.method, "concrete method %s", - r.qpos.objectString(r.method)) - } - for i, super := range r.from { - if r.method == nil { - printf(super.(*types.Named).Obj(), "\t%s %s", - relation, r.qpos.typeString(super)) - } else { - meth(r.fromMethod[i]) - } - } - } - if r.fromPtr != nil { - if r.method == nil { - printf(r.pos, "pointer type *%s", r.qpos.typeString(r.t)) - } else { - // TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f. - printf(r.method, "concrete method %s", - r.qpos.objectString(r.method)) - } - - for i, psuper := range r.fromPtr { - if r.method == nil { - printf(psuper.(*types.Named).Obj(), "\t%s %s", - relation, r.qpos.typeString(psuper)) - } else { - meth(r.fromPtrMethod[i]) - } - } - } else if r.from == nil { - printf(r.pos, "%s type %s implements only interface{}", - typeKind(r.t), r.qpos.typeString(r.t)) - } - } -} - -func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) { - res.Implements = &serial.Implements{ - T: makeImplementsType(r.t, fset), - AssignableTo: makeImplementsTypes(r.to, fset), - AssignableFrom: makeImplementsTypes(r.from, fset), - AssignableFromPtr: makeImplementsTypes(r.fromPtr, fset), - AssignableToMethod: methodsToSerial(r.qpos.info.Pkg, r.toMethod, fset), - AssignableFromMethod: methodsToSerial(r.qpos.info.Pkg, r.fromMethod, fset), - AssignableFromPtrMethod: methodsToSerial(r.qpos.info.Pkg, r.fromPtrMethod, fset), - } - if r.method != nil { - res.Implements.Method = &serial.DescribeMethod{ - Name: r.qpos.objectString(r.method), - Pos: fset.Position(r.method.Pos()).String(), - } - } -} - -func makeImplementsTypes(tt []types.Type, fset *token.FileSet) []serial.ImplementsType { - var r []serial.ImplementsType - for _, t := range tt { - r = append(r, makeImplementsType(t, fset)) - } - return r -} - -func makeImplementsType(T types.Type, fset *token.FileSet) serial.ImplementsType { - var pos token.Pos - if nt, ok := deref(T).(*types.Named); ok { // implementsResult.t may be non-named - pos = nt.Obj().Pos() - } - return serial.ImplementsType{ - Name: T.String(), - Pos: fset.Position(pos).String(), - Kind: typeKind(T), - } -} - -// typeKind returns a string describing the underlying kind of type, -// e.g. "slice", "array", "struct". -func typeKind(T types.Type) string { - s := reflect.TypeOf(T.Underlying()).String() - return strings.ToLower(strings.TrimPrefix(s, "*types.")) -} - -func isInterface(T types.Type) bool { return types.IsInterface(T) } - -type typesByString []types.Type - -func (p typesByString) Len() int { return len(p) } -func (p typesByString) Less(i, j int) bool { return p[i].String() < p[j].String() } -func (p typesByString) Swap(i, j int) { p[i], p[j] = p[j], p[i] } diff --git a/oracle/oracle14.go b/oracle/oracle14.go deleted file mode 100644 index ed8a166d0b..0000000000 --- a/oracle/oracle14.go +++ /dev/null @@ -1,367 +0,0 @@ -// Copyright 2014 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. - -// +build !go1.5 - -// Package oracle contains the implementation of the oracle tool whose -// command-line is provided by golang.org/x/tools/cmd/oracle. -// -// http://golang.org/s/oracle-design -// http://golang.org/s/oracle-user-manual -// -package oracle // import "golang.org/x/tools/oracle" - -// This file defines oracle.Query, the entry point for the oracle tool. -// The actual executable is defined in cmd/oracle. - -// TODO(adonovan): new queries -// - show all statements that may update the selected lvalue -// (local, global, field, etc). -// - show all places where an object of type T is created -// (&T{}, var t T, new(T), new(struct{array [3]T}), etc. - -import ( - "fmt" - "go/ast" - "go/build" - "go/parser" - "go/token" - "io" - "path/filepath" - - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/pointer" - "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/types" - "golang.org/x/tools/oracle/serial" -) - -type printfFunc func(pos interface{}, format string, args ...interface{}) - -// queryResult is the interface of each query-specific result type. -type queryResult interface { - toSerial(res *serial.Result, fset *token.FileSet) - display(printf printfFunc) -} - -// A QueryPos represents the position provided as input to a query: -// a textual extent in the program's source code, the AST node it -// corresponds to, and the package to which it belongs. -// Instances are created by parseQueryPos. -type queryPos struct { - fset *token.FileSet - start, end token.Pos // source extent of query - path []ast.Node // AST path from query node to root of ast.File - exact bool // 2nd result of PathEnclosingInterval - info *loader.PackageInfo // type info for the queried package (nil for fastQueryPos) -} - -// TypeString prints type T relative to the query position. -func (qpos *queryPos) typeString(T types.Type) string { - return types.TypeString(T, types.RelativeTo(qpos.info.Pkg)) -} - -// ObjectString prints object obj relative to the query position. -func (qpos *queryPos) objectString(obj types.Object) string { - return types.ObjectString(obj, types.RelativeTo(qpos.info.Pkg)) -} - -// SelectionString prints selection sel relative to the query position. -func (qpos *queryPos) selectionString(sel *types.Selection) string { - return types.SelectionString(sel, types.RelativeTo(qpos.info.Pkg)) -} - -// A Query specifies a single oracle query. -type Query struct { - Mode string // query mode ("callers", etc) - Pos string // query position - Build *build.Context // package loading configuration - - // pointer analysis options - Scope []string // main packages in (*loader.Config).FromArgs syntax - PTALog io.Writer // (optional) pointer-analysis log file - Reflection bool // model reflection soundly (currently slow). - - // Populated during Run() - Fset *token.FileSet - result queryResult -} - -// Serial returns an instance of serial.Result, which implements the -// {xml,json}.Marshaler interfaces so that query results can be -// serialized as JSON or XML. -// -func (q *Query) Serial() *serial.Result { - resj := &serial.Result{Mode: q.Mode} - q.result.toSerial(resj, q.Fset) - return resj -} - -// WriteTo writes the oracle query result res to out in a compiler diagnostic format. -func (q *Query) WriteTo(out io.Writer) { - printf := func(pos interface{}, format string, args ...interface{}) { - fprintf(out, q.Fset, pos, format, args...) - } - q.result.display(printf) -} - -// Run runs an oracle query and populates its Fset and Result. -func Run(q *Query) error { - switch q.Mode { - case "callees": - return callees(q) - case "callers": - return callers(q) - case "callstack": - return callstack(q) - case "peers": - return peers(q) - case "pointsto": - return pointsto(q) - case "whicherrs": - return whicherrs(q) - case "definition": - return definition(q) - case "describe": - return describe(q) - case "freevars": - return freevars(q) - case "implements": - return implements(q) - case "referrers": - return referrers(q) - case "what": - return what(q) - default: - return fmt.Errorf("invalid mode: %q", q.Mode) - } -} - -func setPTAScope(lconf *loader.Config, scope []string) error { - if len(scope) == 0 { - return fmt.Errorf("no packages specified for pointer analysis scope") - } - - // Determine initial packages for PTA. - args, err := lconf.FromArgs(scope, true) - if err != nil { - return err - } - if len(args) > 0 { - return fmt.Errorf("surplus arguments: %q", args) - } - return nil -} - -// Create a pointer.Config whose scope is the initial packages of lprog -// and their dependencies. -func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer, reflection bool) (*pointer.Config, error) { - // TODO(adonovan): the body of this function is essentially - // duplicated in all go/pointer clients. Refactor. - - // For each initial package (specified on the command line), - // if it has a main function, analyze that, - // otherwise analyze its tests, if any. - var testPkgs, mains []*ssa.Package - for _, info := range lprog.InitialPackages() { - initialPkg := prog.Package(info.Pkg) - - // Add package to the pointer analysis scope. - if initialPkg.Func("main") != nil { - mains = append(mains, initialPkg) - } else { - testPkgs = append(testPkgs, initialPkg) - } - } - if testPkgs != nil { - if p := prog.CreateTestMainPackage(testPkgs...); p != nil { - mains = append(mains, p) - } - } - if mains == nil { - return nil, fmt.Errorf("analysis scope has no main and no tests") - } - return &pointer.Config{ - Log: ptaLog, - Reflection: reflection, - Mains: mains, - }, nil -} - -// importQueryPackage finds the package P containing the -// query position and tells conf to import it. -// It returns the package's path. -func importQueryPackage(pos string, conf *loader.Config) (string, error) { - fqpos, err := fastQueryPos(pos) - if err != nil { - return "", err // bad query - } - filename := fqpos.fset.File(fqpos.start).Name() - - // This will not work for ad-hoc packages - // such as $GOROOT/src/net/http/triv.go. - // TODO(adonovan): ensure we report a clear error. - _, importPath, err := guessImportPath(filename, conf.Build) - if err != nil { - return "", err // can't find GOPATH dir - } - if importPath == "" { - return "", fmt.Errorf("can't guess import path from %s", filename) - } - - // Check that it's possible to load the queried package. - // (e.g. oracle tests contain different 'package' decls in same dir.) - // Keep consistent with logic in loader/util.go! - cfg2 := *conf.Build - cfg2.CgoEnabled = false - bp, err := cfg2.Import(importPath, "", 0) - if err != nil { - return "", err // no files for package - } - - switch pkgContainsFile(bp, filename) { - case 'T': - conf.ImportWithTests(importPath) - case 'X': - conf.ImportWithTests(importPath) - importPath += "_test" // for TypeCheckFuncBodies - case 'G': - conf.Import(importPath) - default: - return "", fmt.Errorf("package %q doesn't contain file %s", - importPath, filename) - } - - conf.TypeCheckFuncBodies = func(p string) bool { return p == importPath } - - return importPath, nil -} - -// pkgContainsFile reports whether file was among the packages Go -// files, Test files, eXternal test files, or not found. -func pkgContainsFile(bp *build.Package, filename string) byte { - for i, files := range [][]string{bp.GoFiles, bp.TestGoFiles, bp.XTestGoFiles} { - for _, file := range files { - if sameFile(filepath.Join(bp.Dir, file), filename) { - return "GTX"[i] - } - } - } - return 0 // not found -} - -// ParseQueryPos parses the source query position pos and returns the -// AST node of the loaded program lprog that it identifies. -// If needExact, it must identify a single AST subtree; -// this is appropriate for queries that allow fairly arbitrary syntax, -// e.g. "describe". -// -func parseQueryPos(lprog *loader.Program, posFlag string, needExact bool) (*queryPos, error) { - filename, startOffset, endOffset, err := parsePosFlag(posFlag) - if err != nil { - return nil, err - } - start, end, err := findQueryPos(lprog.Fset, filename, startOffset, endOffset) - if err != nil { - return nil, err - } - info, path, exact := lprog.PathEnclosingInterval(start, end) - if path == nil { - return nil, fmt.Errorf("no syntax here") - } - if needExact && !exact { - return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0])) - } - return &queryPos{lprog.Fset, start, end, path, exact, info}, nil -} - -// ---------- Utilities ---------- - -// allowErrors causes type errors to be silently ignored. -// (Not suitable if SSA construction follows.) -func allowErrors(lconf *loader.Config) { - ctxt := *lconf.Build // copy - ctxt.CgoEnabled = false - lconf.Build = &ctxt - lconf.AllowErrors = true - // AllErrors makes the parser always return an AST instead of - // bailing out after 10 errors and returning an empty ast.File. - lconf.ParserMode = parser.AllErrors - lconf.TypeChecker.Error = func(err error) {} -} - -// ptrAnalysis runs the pointer analysis and returns its result. -func ptrAnalysis(conf *pointer.Config) *pointer.Result { - result, err := pointer.Analyze(conf) - if err != nil { - panic(err) // pointer analysis internal error - } - return result -} - -func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) } - -// deref returns a pointer's element type; otherwise it returns typ. -func deref(typ types.Type) types.Type { - if p, ok := typ.Underlying().(*types.Pointer); ok { - return p.Elem() - } - return typ -} - -// fprintf prints to w a message of the form "location: message\n" -// where location is derived from pos. -// -// pos must be one of: -// - a token.Pos, denoting a position -// - an ast.Node, denoting an interval -// - anything with a Pos() method: -// ssa.Member, ssa.Value, ssa.Instruction, types.Object, pointer.Label, etc. -// - a QueryPos, denoting the extent of the user's query. -// - nil, meaning no position at all. -// -// The output format is is compatible with the 'gnu' -// compilation-error-regexp in Emacs' compilation mode. -// TODO(adonovan): support other editors. -// -func fprintf(w io.Writer, fset *token.FileSet, pos interface{}, format string, args ...interface{}) { - var start, end token.Pos - switch pos := pos.(type) { - case ast.Node: - start = pos.Pos() - end = pos.End() - case token.Pos: - start = pos - end = start - case interface { - Pos() token.Pos - }: - start = pos.Pos() - end = start - case *queryPos: - start = pos.start - end = pos.end - case nil: - // no-op - default: - panic(fmt.Sprintf("invalid pos: %T", pos)) - } - - if sp := fset.Position(start); start == end { - // (prints "-: " for token.NoPos) - fmt.Fprintf(w, "%s: ", sp) - } else { - ep := fset.Position(end) - // The -1 below is a concession to Emacs's broken use of - // inclusive (not half-open) intervals. - // Other editors may not want it. - // TODO(adonovan): add an -editor=vim|emacs|acme|auto - // flag; auto uses EMACS=t / VIM=... / etc env vars. - fmt.Fprintf(w, "%s:%d.%d-%d.%d: ", - sp.Filename, sp.Line, sp.Column, ep.Line, ep.Column-1) - } - fmt.Fprintf(w, format, args...) - io.WriteString(w, "\n") -} diff --git a/oracle/peers14.go b/oracle/peers14.go deleted file mode 100644 index 9b97cbf2d7..0000000000 --- a/oracle/peers14.go +++ /dev/null @@ -1,254 +0,0 @@ -// 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. - -// +build !go1.5 - -package oracle - -import ( - "fmt" - "go/ast" - "go/token" - "sort" - - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/ssa/ssautil" - "golang.org/x/tools/go/types" - "golang.org/x/tools/oracle/serial" -) - -// peers enumerates, for a given channel send (or receive) operation, -// the set of possible receives (or sends) that correspond to it. -// -// TODO(adonovan): support reflect.{Select,Recv,Send,Close}. -// TODO(adonovan): permit the user to query based on a MakeChan (not send/recv), -// or the implicit receive in "for v := range ch". -func peers(q *Query) error { - lconf := loader.Config{Build: q.Build} - - if err := setPTAScope(&lconf, q.Scope); err != nil { - return err - } - - // Load/parse/type-check the program. - lprog, err := lconf.Load() - if err != nil { - return err - } - q.Fset = lprog.Fset - - qpos, err := parseQueryPos(lprog, q.Pos, false) - if err != nil { - return err - } - - prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) - - ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) - if err != nil { - return err - } - - opPos := findOp(qpos) - if opPos == token.NoPos { - return fmt.Errorf("there is no channel operation here") - } - - // Defer SSA construction till after errors are reported. - prog.Build() - - var queryOp chanOp // the originating send or receive operation - var ops []chanOp // all sends/receives of opposite direction - - // Look at all channel operations in the whole ssa.Program. - // Build a list of those of same type as the query. - allFuncs := ssautil.AllFunctions(prog) - for fn := range allFuncs { - for _, b := range fn.Blocks { - for _, instr := range b.Instrs { - for _, op := range chanOps(instr) { - ops = append(ops, op) - if op.pos == opPos { - queryOp = op // we found the query op - } - } - } - } - } - if queryOp.ch == nil { - return fmt.Errorf("ssa.Instruction for send/receive not found") - } - - // Discard operations of wrong channel element type. - // Build set of channel ssa.Values as query to pointer analysis. - // We compare channels by element types, not channel types, to - // ignore both directionality and type names. - queryType := queryOp.ch.Type() - queryElemType := queryType.Underlying().(*types.Chan).Elem() - ptaConfig.AddQuery(queryOp.ch) - i := 0 - for _, op := range ops { - if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) { - ptaConfig.AddQuery(op.ch) - ops[i] = op - i++ - } - } - ops = ops[:i] - - // Run the pointer analysis. - ptares := ptrAnalysis(ptaConfig) - - // Find the points-to set. - queryChanPtr := ptares.Queries[queryOp.ch] - - // Ascertain which make(chan) labels the query's channel can alias. - var makes []token.Pos - for _, label := range queryChanPtr.PointsTo().Labels() { - makes = append(makes, label.Pos()) - } - sort.Sort(byPos(makes)) - - // Ascertain which channel operations can alias the same make(chan) labels. - var sends, receives, closes []token.Pos - for _, op := range ops { - if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) { - switch op.dir { - case types.SendOnly: - sends = append(sends, op.pos) - case types.RecvOnly: - receives = append(receives, op.pos) - case types.SendRecv: - closes = append(closes, op.pos) - } - } - } - sort.Sort(byPos(sends)) - sort.Sort(byPos(receives)) - sort.Sort(byPos(closes)) - - q.result = &peersResult{ - queryPos: opPos, - queryType: queryType, - makes: makes, - sends: sends, - receives: receives, - closes: closes, - } - return nil -} - -// findOp returns the position of the enclosing send/receive/close op. -// For send and receive operations, this is the position of the <- token; -// for close operations, it's the Lparen of the function call. -// -// TODO(adonovan): handle implicit receive operations from 'for...range chan' statements. -func findOp(qpos *queryPos) token.Pos { - for _, n := range qpos.path { - switch n := n.(type) { - case *ast.UnaryExpr: - if n.Op == token.ARROW { - return n.OpPos - } - case *ast.SendStmt: - return n.Arrow - case *ast.CallExpr: - // close function call can only exist as a direct identifier - if close, ok := unparen(n.Fun).(*ast.Ident); ok { - if b, ok := qpos.info.Info.Uses[close].(*types.Builtin); ok && b.Name() == "close" { - return n.Lparen - } - } - } - } - return token.NoPos -} - -// chanOp abstracts an ssa.Send, ssa.Unop(ARROW), or a SelectState. -type chanOp struct { - ch ssa.Value - dir types.ChanDir // SendOnly=send, RecvOnly=recv, SendRecv=close - pos token.Pos -} - -// chanOps returns a slice of all the channel operations in the instruction. -func chanOps(instr ssa.Instruction) []chanOp { - // TODO(adonovan): handle calls to reflect.{Select,Recv,Send,Close} too. - var ops []chanOp - switch instr := instr.(type) { - case *ssa.UnOp: - if instr.Op == token.ARROW { - ops = append(ops, chanOp{instr.X, types.RecvOnly, instr.Pos()}) - } - case *ssa.Send: - ops = append(ops, chanOp{instr.Chan, types.SendOnly, instr.Pos()}) - case *ssa.Select: - for _, st := range instr.States { - ops = append(ops, chanOp{st.Chan, st.Dir, st.Pos}) - } - case ssa.CallInstruction: - cc := instr.Common() - if b, ok := cc.Value.(*ssa.Builtin); ok && b.Name() == "close" { - ops = append(ops, chanOp{cc.Args[0], types.SendRecv, cc.Pos()}) - } - } - return ops -} - -type peersResult struct { - queryPos token.Pos // of queried channel op - queryType types.Type // type of queried channel - makes, sends, receives, closes []token.Pos // positions of aliased makechan/send/receive/close instrs -} - -func (r *peersResult) display(printf printfFunc) { - if len(r.makes) == 0 { - printf(r.queryPos, "This channel can't point to anything.") - return - } - printf(r.queryPos, "This channel of type %s may be:", r.queryType) - for _, alloc := range r.makes { - printf(alloc, "\tallocated here") - } - for _, send := range r.sends { - printf(send, "\tsent to, here") - } - for _, receive := range r.receives { - printf(receive, "\treceived from, here") - } - for _, clos := range r.closes { - printf(clos, "\tclosed, here") - } -} - -func (r *peersResult) toSerial(res *serial.Result, fset *token.FileSet) { - peers := &serial.Peers{ - Pos: fset.Position(r.queryPos).String(), - Type: r.queryType.String(), - } - for _, alloc := range r.makes { - peers.Allocs = append(peers.Allocs, fset.Position(alloc).String()) - } - for _, send := range r.sends { - peers.Sends = append(peers.Sends, fset.Position(send).String()) - } - for _, receive := range r.receives { - peers.Receives = append(peers.Receives, fset.Position(receive).String()) - } - for _, clos := range r.closes { - peers.Closes = append(peers.Closes, fset.Position(clos).String()) - } - res.Peers = peers -} - -// -------- utils -------- - -// NB: byPos is not deterministic across packages since it depends on load order. -// Use lessPos if the tests need it. -type byPos []token.Pos - -func (p byPos) Len() int { return len(p) } -func (p byPos) Less(i, j int) bool { return p[i] < p[j] } -func (p byPos) Swap(i, j int) { p[i], p[j] = p[j], p[i] } diff --git a/oracle/pointsto14.go b/oracle/pointsto14.go deleted file mode 100644 index 1e406199e8..0000000000 --- a/oracle/pointsto14.go +++ /dev/null @@ -1,293 +0,0 @@ -// 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. - -// +build !go1.5 - -package oracle - -import ( - "fmt" - "go/ast" - "go/token" - "sort" - - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/pointer" - "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/ssa/ssautil" - "golang.org/x/tools/go/types" - "golang.org/x/tools/oracle/serial" -) - -// pointsto runs the pointer analysis on the selected expression, -// and reports its points-to set (for a pointer-like expression) -// or its dynamic types (for an interface, reflect.Value, or -// reflect.Type expression) and their points-to sets. -// -// All printed sets are sorted to ensure determinism. -// -func pointsto(q *Query) error { - lconf := loader.Config{Build: q.Build} - - if err := setPTAScope(&lconf, q.Scope); err != nil { - return err - } - - // Load/parse/type-check the program. - lprog, err := lconf.Load() - if err != nil { - return err - } - q.Fset = lprog.Fset - - qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos - if err != nil { - return err - } - - prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) - - ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) - if err != nil { - return err - } - - path, action := findInterestingNode(qpos.info, qpos.path) - if action != actionExpr { - return fmt.Errorf("pointer analysis wants an expression; got %s", - astutil.NodeDescription(qpos.path[0])) - } - - var expr ast.Expr - var obj types.Object - switch n := path[0].(type) { - case *ast.ValueSpec: - // ambiguous ValueSpec containing multiple names - return fmt.Errorf("multiple value specification") - case *ast.Ident: - obj = qpos.info.ObjectOf(n) - expr = n - case ast.Expr: - expr = n - default: - // TODO(adonovan): is this reachable? - return fmt.Errorf("unexpected AST for expr: %T", n) - } - - // Reject non-pointerlike types (includes all constants---except nil). - // TODO(adonovan): reject nil too. - typ := qpos.info.TypeOf(expr) - if !pointer.CanPoint(typ) { - return fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ) - } - - // Determine the ssa.Value for the expression. - var value ssa.Value - var isAddr bool - if obj != nil { - // def/ref of func/var object - value, isAddr, err = ssaValueForIdent(prog, qpos.info, obj, path) - } else { - value, isAddr, err = ssaValueForExpr(prog, qpos.info, path) - } - if err != nil { - return err // e.g. trivially dead code - } - - // Defer SSA construction till after errors are reported. - prog.Build() - - // Run the pointer analysis. - ptrs, err := runPTA(ptaConfig, value, isAddr) - if err != nil { - return err // e.g. analytically unreachable - } - - q.result = &pointstoResult{ - qpos: qpos, - typ: typ, - ptrs: ptrs, - } - return nil -} - -// ssaValueForIdent returns the ssa.Value for the ast.Ident whose path -// to the root of the AST is path. isAddr reports whether the -// ssa.Value is the address denoted by the ast.Ident, not its value. -// -func ssaValueForIdent(prog *ssa.Program, qinfo *loader.PackageInfo, obj types.Object, path []ast.Node) (value ssa.Value, isAddr bool, err error) { - switch obj := obj.(type) { - case *types.Var: - pkg := prog.Package(qinfo.Pkg) - pkg.Build() - if v, addr := prog.VarValue(obj, pkg, path); v != nil { - return v, addr, nil - } - return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name()) - - case *types.Func: - fn := prog.FuncValue(obj) - if fn == nil { - return nil, false, fmt.Errorf("%s is an interface method", obj) - } - // TODO(adonovan): there's no point running PTA on a *Func ident. - // Eliminate this feature. - return fn, false, nil - } - panic(obj) -} - -// ssaValueForExpr returns the ssa.Value of the non-ast.Ident -// expression whose path to the root of the AST is path. -// -func ssaValueForExpr(prog *ssa.Program, qinfo *loader.PackageInfo, path []ast.Node) (value ssa.Value, isAddr bool, err error) { - pkg := prog.Package(qinfo.Pkg) - pkg.SetDebugMode(true) - pkg.Build() - - fn := ssa.EnclosingFunction(pkg, path) - if fn == nil { - return nil, false, fmt.Errorf("no SSA function built for this location (dead code?)") - } - - if v, addr := fn.ValueForExpr(path[0].(ast.Expr)); v != nil { - return v, addr, nil - } - - return nil, false, fmt.Errorf("can't locate SSA Value for expression in %s", fn) -} - -// runPTA runs the pointer analysis of the selected SSA value or address. -func runPTA(conf *pointer.Config, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) { - T := v.Type() - if isAddr { - conf.AddIndirectQuery(v) - T = deref(T) - } else { - conf.AddQuery(v) - } - ptares := ptrAnalysis(conf) - - var ptr pointer.Pointer - if isAddr { - ptr = ptares.IndirectQueries[v] - } else { - ptr = ptares.Queries[v] - } - if ptr == (pointer.Pointer{}) { - return nil, fmt.Errorf("pointer analysis did not find expression (dead code?)") - } - pts := ptr.PointsTo() - - if pointer.CanHaveDynamicTypes(T) { - // Show concrete types for interface/reflect.Value expression. - if concs := pts.DynamicTypes(); concs.Len() > 0 { - concs.Iterate(func(conc types.Type, pta interface{}) { - labels := pta.(pointer.PointsToSet).Labels() - sort.Sort(byPosAndString(labels)) // to ensure determinism - ptrs = append(ptrs, pointerResult{conc, labels}) - }) - } - } else { - // Show labels for other expressions. - labels := pts.Labels() - sort.Sort(byPosAndString(labels)) // to ensure determinism - ptrs = append(ptrs, pointerResult{T, labels}) - } - sort.Sort(byTypeString(ptrs)) // to ensure determinism - return ptrs, nil -} - -type pointerResult struct { - typ types.Type // type of the pointer (always concrete) - labels []*pointer.Label // set of labels -} - -type pointstoResult struct { - qpos *queryPos - typ types.Type // type of expression - ptrs []pointerResult // pointer info (typ is concrete => len==1) -} - -func (r *pointstoResult) display(printf printfFunc) { - if pointer.CanHaveDynamicTypes(r.typ) { - // Show concrete types for interface, reflect.Type or - // reflect.Value expression. - - if len(r.ptrs) > 0 { - printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.typeString(r.typ)) - for _, ptr := range r.ptrs { - var obj types.Object - if nt, ok := deref(ptr.typ).(*types.Named); ok { - obj = nt.Obj() - } - if len(ptr.labels) > 0 { - printf(obj, "\t%s, may point to:", r.qpos.typeString(ptr.typ)) - printLabels(printf, ptr.labels, "\t\t") - } else { - printf(obj, "\t%s", r.qpos.typeString(ptr.typ)) - } - } - } else { - printf(r.qpos, "this %s cannot contain any dynamic types.", r.typ) - } - } else { - // Show labels for other expressions. - if ptr := r.ptrs[0]; len(ptr.labels) > 0 { - printf(r.qpos, "this %s may point to these objects:", - r.qpos.typeString(r.typ)) - printLabels(printf, ptr.labels, "\t") - } else { - printf(r.qpos, "this %s may not point to anything.", - r.qpos.typeString(r.typ)) - } - } -} - -func (r *pointstoResult) toSerial(res *serial.Result, fset *token.FileSet) { - var pts []serial.PointsTo - for _, ptr := range r.ptrs { - var namePos string - if nt, ok := deref(ptr.typ).(*types.Named); ok { - namePos = fset.Position(nt.Obj().Pos()).String() - } - var labels []serial.PointsToLabel - for _, l := range ptr.labels { - labels = append(labels, serial.PointsToLabel{ - Pos: fset.Position(l.Pos()).String(), - Desc: l.String(), - }) - } - pts = append(pts, serial.PointsTo{ - Type: r.qpos.typeString(ptr.typ), - NamePos: namePos, - Labels: labels, - }) - } - res.PointsTo = pts -} - -type byTypeString []pointerResult - -func (a byTypeString) Len() int { return len(a) } -func (a byTypeString) Less(i, j int) bool { return a[i].typ.String() < a[j].typ.String() } -func (a byTypeString) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type byPosAndString []*pointer.Label - -func (a byPosAndString) Len() int { return len(a) } -func (a byPosAndString) Less(i, j int) bool { - cmp := a[i].Pos() - a[j].Pos() - return cmp < 0 || (cmp == 0 && a[i].String() < a[j].String()) -} -func (a byPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -func printLabels(printf printfFunc, labels []*pointer.Label, prefix string) { - // TODO(adonovan): due to context-sensitivity, many of these - // labels may differ only by context, which isn't apparent. - for _, label := range labels { - printf(label, "%s%s", prefix, label) - } -} diff --git a/oracle/referrers14.go b/oracle/referrers14.go deleted file mode 100644 index 17adf023d5..0000000000 --- a/oracle/referrers14.go +++ /dev/null @@ -1,243 +0,0 @@ -// 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. - -// +build !go1.5 - -package oracle - -import ( - "bytes" - "fmt" - "go/ast" - "go/token" - "io/ioutil" - "sort" - - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/types" - "golang.org/x/tools/oracle/serial" - "golang.org/x/tools/refactor/importgraph" -) - -// Referrers reports all identifiers that resolve to the same object -// as the queried identifier, within any package in the analysis scope. -func referrers(q *Query) error { - lconf := loader.Config{Build: q.Build} - allowErrors(&lconf) - - if _, err := importQueryPackage(q.Pos, &lconf); err != nil { - return err - } - - var id *ast.Ident - var obj types.Object - var lprog *loader.Program - var pass2 bool - var qpos *queryPos - for { - // Load/parse/type-check the program. - var err error - lprog, err = lconf.Load() - if err != nil { - return err - } - q.Fset = lprog.Fset - - qpos, err = parseQueryPos(lprog, q.Pos, false) - if err != nil { - return err - } - - id, _ = qpos.path[0].(*ast.Ident) - if id == nil { - return fmt.Errorf("no identifier here") - } - - obj = qpos.info.ObjectOf(id) - if obj == nil { - // Happens for y in "switch y := x.(type)", - // the package declaration, - // and unresolved identifiers. - if _, ok := qpos.path[1].(*ast.File); ok { // package decl? - pkg := qpos.info.Pkg - obj = types.NewPkgName(id.Pos(), pkg, pkg.Name(), pkg) - } else { - return fmt.Errorf("no object for identifier: %T", qpos.path[1]) - } - } - - if pass2 { - break - } - - // If the identifier is exported, we must load all packages that - // depend transitively upon the package that defines it. - // Treat PkgNames as exported, even though they're lowercase. - if _, isPkg := obj.(*types.PkgName); !(isPkg || obj.Exported()) { - break // not exported - } - - // Scan the workspace and build the import graph. - // Ignore broken packages. - _, rev, _ := importgraph.Build(q.Build) - - // Re-load the larger program. - // Create a new file set so that ... - // External test packages are never imported, - // so they will never appear in the graph. - // (We must reset the Config here, not just reset the Fset field.) - lconf = loader.Config{ - Fset: token.NewFileSet(), - Build: q.Build, - } - allowErrors(&lconf) - for path := range rev.Search(obj.Pkg().Path()) { - lconf.ImportWithTests(path) - } - pass2 = true - } - - // Iterate over all go/types' Uses facts for the entire program. - var refs []*ast.Ident - for _, info := range lprog.AllPackages { - for id2, obj2 := range info.Uses { - if sameObj(obj, obj2) { - refs = append(refs, id2) - } - } - } - sort.Sort(byNamePos{q.Fset, refs}) - - q.result = &referrersResult{ - qpos: qpos, - query: id, - obj: obj, - refs: refs, - } - return nil -} - -// same reports whether x and y are identical, or both are PkgNames -// that import the same Package. -// -func sameObj(x, y types.Object) bool { - if x == y { - return true - } - if x, ok := x.(*types.PkgName); ok { - if y, ok := y.(*types.PkgName); ok { - return x.Imported() == y.Imported() - } - } - return false -} - -// -------- utils -------- - -// An deterministic ordering for token.Pos that doesn't -// depend on the order in which packages were loaded. -func lessPos(fset *token.FileSet, x, y token.Pos) bool { - fx := fset.File(x) - fy := fset.File(y) - if fx != fy { - return fx.Name() < fy.Name() - } - return x < y -} - -type byNamePos struct { - fset *token.FileSet - ids []*ast.Ident -} - -func (p byNamePos) Len() int { return len(p.ids) } -func (p byNamePos) Swap(i, j int) { p.ids[i], p.ids[j] = p.ids[j], p.ids[i] } -func (p byNamePos) Less(i, j int) bool { - return lessPos(p.fset, p.ids[i].NamePos, p.ids[j].NamePos) -} - -type referrersResult struct { - qpos *queryPos - query *ast.Ident // identifier of query - obj types.Object // object it denotes - refs []*ast.Ident // set of all other references to it -} - -func (r *referrersResult) display(printf printfFunc) { - printf(r.obj, "%d references to %s", len(r.refs), r.qpos.objectString(r.obj)) - - // Show referring lines, like grep. - type fileinfo struct { - refs []*ast.Ident - linenums []int // line number of refs[i] - data chan interface{} // file contents or error - } - var fileinfos []*fileinfo - fileinfosByName := make(map[string]*fileinfo) - - // First pass: start the file reads concurrently. - sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency - for _, ref := range r.refs { - posn := r.qpos.fset.Position(ref.Pos()) - fi := fileinfosByName[posn.Filename] - if fi == nil { - fi = &fileinfo{data: make(chan interface{})} - fileinfosByName[posn.Filename] = fi - fileinfos = append(fileinfos, fi) - - // First request for this file: - // start asynchronous read. - go func() { - sema <- struct{}{} // acquire token - content, err := ioutil.ReadFile(posn.Filename) - <-sema // release token - if err != nil { - fi.data <- err - } else { - fi.data <- content - } - }() - } - fi.refs = append(fi.refs, ref) - fi.linenums = append(fi.linenums, posn.Line) - } - - // Second pass: print refs in original order. - // One line may have several refs at different columns. - for _, fi := range fileinfos { - v := <-fi.data // wait for I/O completion - - // Print one item for all refs in a file that could not - // be loaded (perhaps due to //line directives). - if err, ok := v.(error); ok { - var suffix string - if more := len(fi.refs) - 1; more > 0 { - suffix = fmt.Sprintf(" (+ %d more refs in this file)", more) - } - printf(fi.refs[0], "%v%s", err, suffix) - continue - } - - lines := bytes.Split(v.([]byte), []byte("\n")) - for i, ref := range fi.refs { - printf(ref, "%s", lines[fi.linenums[i]-1]) - } - } -} - -// TODO(adonovan): encode extent, not just Pos info, in Serial form. - -func (r *referrersResult) toSerial(res *serial.Result, fset *token.FileSet) { - referrers := &serial.Referrers{ - Pos: fset.Position(r.query.Pos()).String(), - Desc: r.obj.String(), - } - if pos := r.obj.Pos(); pos != token.NoPos { // Package objects have no Pos() - referrers.ObjPos = fset.Position(pos).String() - } - for _, ref := range r.refs { - referrers.Refs = append(referrers.Refs, fset.Position(ref.NamePos).String()) - } - res.Referrers = referrers -} diff --git a/oracle/whicherrs14.go b/oracle/whicherrs14.go deleted file mode 100644 index 25449f3183..0000000000 --- a/oracle/whicherrs14.go +++ /dev/null @@ -1,328 +0,0 @@ -// Copyright 2014 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. - -// +build !go1.5 - -package oracle - -import ( - "fmt" - "go/ast" - "go/token" - "sort" - - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/ssa/ssautil" - "golang.org/x/tools/go/types" - "golang.org/x/tools/oracle/serial" -) - -var builtinErrorType = types.Universe.Lookup("error").Type() - -// whicherrs takes an position to an error and tries to find all types, constants -// and global value which a given error can point to and which can be checked from the -// scope where the error lives. -// In short, it returns a list of things that can be checked against in order to handle -// an error properly. -// -// TODO(dmorsing): figure out if fields in errors like *os.PathError.Err -// can be queried recursively somehow. -func whicherrs(q *Query) error { - lconf := loader.Config{Build: q.Build} - - if err := setPTAScope(&lconf, q.Scope); err != nil { - return err - } - - // Load/parse/type-check the program. - lprog, err := lconf.Load() - if err != nil { - return err - } - q.Fset = lprog.Fset - - qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos - if err != nil { - return err - } - - prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) - - ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) - if err != nil { - return err - } - - path, action := findInterestingNode(qpos.info, qpos.path) - if action != actionExpr { - return fmt.Errorf("whicherrs wants an expression; got %s", - astutil.NodeDescription(qpos.path[0])) - } - var expr ast.Expr - var obj types.Object - switch n := path[0].(type) { - case *ast.ValueSpec: - // ambiguous ValueSpec containing multiple names - return fmt.Errorf("multiple value specification") - case *ast.Ident: - obj = qpos.info.ObjectOf(n) - expr = n - case ast.Expr: - expr = n - default: - return fmt.Errorf("unexpected AST for expr: %T", n) - } - - typ := qpos.info.TypeOf(expr) - if !types.Identical(typ, builtinErrorType) { - return fmt.Errorf("selection is not an expression of type 'error'") - } - // Determine the ssa.Value for the expression. - var value ssa.Value - if obj != nil { - // def/ref of func/var object - value, _, err = ssaValueForIdent(prog, qpos.info, obj, path) - } else { - value, _, err = ssaValueForExpr(prog, qpos.info, path) - } - if err != nil { - return err // e.g. trivially dead code - } - - // Defer SSA construction till after errors are reported. - prog.Build() - - globals := findVisibleErrs(prog, qpos) - constants := findVisibleConsts(prog, qpos) - - res := &whicherrsResult{ - qpos: qpos, - errpos: expr.Pos(), - } - - // TODO(adonovan): the following code is heavily duplicated - // w.r.t. "pointsto". Refactor? - - // Find the instruction which initialized the - // global error. If more than one instruction has stored to the global - // remove the global from the set of values that we want to query. - allFuncs := ssautil.AllFunctions(prog) - for fn := range allFuncs { - for _, b := range fn.Blocks { - for _, instr := range b.Instrs { - store, ok := instr.(*ssa.Store) - if !ok { - continue - } - gval, ok := store.Addr.(*ssa.Global) - if !ok { - continue - } - gbl, ok := globals[gval] - if !ok { - continue - } - // we already found a store to this global - // The normal error define is just one store in the init - // so we just remove this global from the set we want to query - if gbl != nil { - delete(globals, gval) - } - globals[gval] = store.Val - } - } - } - - ptaConfig.AddQuery(value) - for _, v := range globals { - ptaConfig.AddQuery(v) - } - - ptares := ptrAnalysis(ptaConfig) - valueptr := ptares.Queries[value] - for g, v := range globals { - ptr, ok := ptares.Queries[v] - if !ok { - continue - } - if !ptr.MayAlias(valueptr) { - continue - } - res.globals = append(res.globals, g) - } - pts := valueptr.PointsTo() - dedup := make(map[*ssa.NamedConst]bool) - for _, label := range pts.Labels() { - // These values are either MakeInterfaces or reflect - // generated interfaces. For the purposes of this - // analysis, we don't care about reflect generated ones - makeiface, ok := label.Value().(*ssa.MakeInterface) - if !ok { - continue - } - constval, ok := makeiface.X.(*ssa.Const) - if !ok { - continue - } - c := constants[*constval] - if c != nil && !dedup[c] { - dedup[c] = true - res.consts = append(res.consts, c) - } - } - concs := pts.DynamicTypes() - concs.Iterate(func(conc types.Type, _ interface{}) { - // go/types is a bit annoying here. - // We want to find all the types that we can - // typeswitch or assert to. This means finding out - // if the type pointed to can be seen by us. - // - // For the purposes of this analysis, the type is always - // either a Named type or a pointer to one. - // There are cases where error can be implemented - // by unnamed types, but in that case, we can't assert to - // it, so we don't care about it for this analysis. - var name *types.TypeName - switch t := conc.(type) { - case *types.Pointer: - named, ok := t.Elem().(*types.Named) - if !ok { - return - } - name = named.Obj() - case *types.Named: - name = t.Obj() - default: - return - } - if !isAccessibleFrom(name, qpos.info.Pkg) { - return - } - res.types = append(res.types, &errorType{conc, name}) - }) - sort.Sort(membersByPosAndString(res.globals)) - sort.Sort(membersByPosAndString(res.consts)) - sort.Sort(sorterrorType(res.types)) - - q.result = res - return nil -} - -// findVisibleErrs returns a mapping from each package-level variable of type "error" to nil. -func findVisibleErrs(prog *ssa.Program, qpos *queryPos) map[*ssa.Global]ssa.Value { - globals := make(map[*ssa.Global]ssa.Value) - for _, pkg := range prog.AllPackages() { - for _, mem := range pkg.Members { - gbl, ok := mem.(*ssa.Global) - if !ok { - continue - } - gbltype := gbl.Type() - // globals are always pointers - if !types.Identical(deref(gbltype), builtinErrorType) { - continue - } - if !isAccessibleFrom(gbl.Object(), qpos.info.Pkg) { - continue - } - globals[gbl] = nil - } - } - return globals -} - -// findVisibleConsts returns a mapping from each package-level constant assignable to type "error", to nil. -func findVisibleConsts(prog *ssa.Program, qpos *queryPos) map[ssa.Const]*ssa.NamedConst { - constants := make(map[ssa.Const]*ssa.NamedConst) - for _, pkg := range prog.AllPackages() { - for _, mem := range pkg.Members { - obj, ok := mem.(*ssa.NamedConst) - if !ok { - continue - } - consttype := obj.Type() - if !types.AssignableTo(consttype, builtinErrorType) { - continue - } - if !isAccessibleFrom(obj.Object(), qpos.info.Pkg) { - continue - } - constants[*obj.Value] = obj - } - } - - return constants -} - -type membersByPosAndString []ssa.Member - -func (a membersByPosAndString) Len() int { return len(a) } -func (a membersByPosAndString) Less(i, j int) bool { - cmp := a[i].Pos() - a[j].Pos() - return cmp < 0 || cmp == 0 && a[i].String() < a[j].String() -} -func (a membersByPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type sorterrorType []*errorType - -func (a sorterrorType) Len() int { return len(a) } -func (a sorterrorType) Less(i, j int) bool { - cmp := a[i].obj.Pos() - a[j].obj.Pos() - return cmp < 0 || cmp == 0 && a[i].typ.String() < a[j].typ.String() -} -func (a sorterrorType) Swap(i, j int) { a[i], a[j] = a[j], a[i] } - -type errorType struct { - typ types.Type // concrete type N or *N that implements error - obj *types.TypeName // the named type N -} - -type whicherrsResult struct { - qpos *queryPos - errpos token.Pos - globals []ssa.Member - consts []ssa.Member - types []*errorType -} - -func (r *whicherrsResult) display(printf printfFunc) { - if len(r.globals) > 0 { - printf(r.qpos, "this error may point to these globals:") - for _, g := range r.globals { - printf(g.Pos(), "\t%s", g.RelString(r.qpos.info.Pkg)) - } - } - if len(r.consts) > 0 { - printf(r.qpos, "this error may contain these constants:") - for _, c := range r.consts { - printf(c.Pos(), "\t%s", c.RelString(r.qpos.info.Pkg)) - } - } - if len(r.types) > 0 { - printf(r.qpos, "this error may contain these dynamic types:") - for _, t := range r.types { - printf(t.obj.Pos(), "\t%s", r.qpos.typeString(t.typ)) - } - } -} - -func (r *whicherrsResult) toSerial(res *serial.Result, fset *token.FileSet) { - we := &serial.WhichErrs{} - we.ErrPos = fset.Position(r.errpos).String() - for _, g := range r.globals { - we.Globals = append(we.Globals, fset.Position(g.Pos()).String()) - } - for _, c := range r.consts { - we.Constants = append(we.Constants, fset.Position(c.Pos()).String()) - } - for _, t := range r.types { - var et serial.WhichErrsType - et.Type = r.qpos.typeString(t.typ) - et.Position = fset.Position(t.obj.Pos()).String() - we.Types = append(we.Types, et) - } - res.WhichErrs = we -} diff --git a/refactor/rename/check14.go b/refactor/rename/check14.go deleted file mode 100644 index 3f1f7abf22..0000000000 --- a/refactor/rename/check14.go +++ /dev/null @@ -1,860 +0,0 @@ -// Copyright 2014 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. - -// +build !go1.5 - -package rename - -// This file defines the safety checks for each kind of renaming. - -import ( - "fmt" - "go/ast" - "go/token" - - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/types" - "golang.org/x/tools/refactor/satisfy" -) - -// errorf reports an error (e.g. conflict) and prevents file modification. -func (r *renamer) errorf(pos token.Pos, format string, args ...interface{}) { - r.hadConflicts = true - reportError(r.iprog.Fset.Position(pos), fmt.Sprintf(format, args...)) -} - -// check performs safety checks of the renaming of the 'from' object to r.to. -func (r *renamer) check(from types.Object) { - if r.objsToUpdate[from] { - return - } - r.objsToUpdate[from] = true - - // NB: order of conditions is important. - if from_, ok := from.(*types.PkgName); ok { - r.checkInFileBlock(from_) - } else if from_, ok := from.(*types.Label); ok { - r.checkLabel(from_) - } else if isPackageLevel(from) { - r.checkInPackageBlock(from) - } else if v, ok := from.(*types.Var); ok && v.IsField() { - r.checkStructField(v) - } else if f, ok := from.(*types.Func); ok && recv(f) != nil { - r.checkMethod(f) - } else if isLocal(from) { - r.checkInLocalScope(from) - } else { - r.errorf(from.Pos(), "unexpected %s object %q (please report a bug)\n", - objectKind(from), from) - } -} - -// checkInFileBlock performs safety checks for renames of objects in the file block, -// i.e. imported package names. -func (r *renamer) checkInFileBlock(from *types.PkgName) { - // Check import name is not "init". - if r.to == "init" { - r.errorf(from.Pos(), "%q is not a valid imported package name", r.to) - } - - // Check for conflicts between file and package block. - if prev := from.Pkg().Scope().Lookup(r.to); prev != nil { - r.errorf(from.Pos(), "renaming this %s %q to %q would conflict", - objectKind(from), from.Name(), r.to) - r.errorf(prev.Pos(), "\twith this package member %s", - objectKind(prev)) - return // since checkInPackageBlock would report redundant errors - } - - // Check for conflicts in lexical scope. - r.checkInLexicalScope(from, r.packages[from.Pkg()]) - - // Finally, modify ImportSpec syntax to add or remove the Name as needed. - info, path, _ := r.iprog.PathEnclosingInterval(from.Pos(), from.Pos()) - if from.Imported().Name() == r.to { - // ImportSpec.Name not needed - path[1].(*ast.ImportSpec).Name = nil - } else { - // ImportSpec.Name needed - if spec := path[1].(*ast.ImportSpec); spec.Name == nil { - spec.Name = &ast.Ident{NamePos: spec.Path.Pos(), Name: r.to} - info.Defs[spec.Name] = from - } - } -} - -// checkInPackageBlock performs safety checks for renames of -// func/var/const/type objects in the package block. -func (r *renamer) checkInPackageBlock(from types.Object) { - // Check that there are no references to the name from another - // package if the renaming would make it unexported. - if ast.IsExported(from.Name()) && !ast.IsExported(r.to) { - for pkg, info := range r.packages { - if pkg == from.Pkg() { - continue - } - if id := someUse(info, from); id != nil && - !r.checkExport(id, pkg, from) { - break - } - } - } - - info := r.packages[from.Pkg()] - - // Check that in the package block, "init" is a function, and never referenced. - if r.to == "init" { - kind := objectKind(from) - if kind == "func" { - // Reject if intra-package references to it exist. - for id, obj := range info.Uses { - if obj == from { - r.errorf(from.Pos(), - "renaming this func %q to %q would make it a package initializer", - from.Name(), r.to) - r.errorf(id.Pos(), "\tbut references to it exist") - break - } - } - } else { - r.errorf(from.Pos(), "you cannot have a %s at package level named %q", - kind, r.to) - } - } - - // Check for conflicts between package block and all file blocks. - for _, f := range info.Files { - fileScope := info.Info.Scopes[f] - b, prev := fileScope.LookupParent(r.to, token.NoPos) - if b == fileScope { - r.errorf(from.Pos(), "renaming this %s %q to %q would conflict", - objectKind(from), from.Name(), r.to) - r.errorf(prev.Pos(), "\twith this %s", - objectKind(prev)) - return // since checkInPackageBlock would report redundant errors - } - } - - // Check for conflicts in lexical scope. - if from.Exported() { - for _, info := range r.packages { - r.checkInLexicalScope(from, info) - } - } else { - r.checkInLexicalScope(from, info) - } -} - -func (r *renamer) checkInLocalScope(from types.Object) { - info := r.packages[from.Pkg()] - - // Is this object an implicit local var for a type switch? - // Each case has its own var, whose position is the decl of y, - // but Ident in that decl does not appear in the Uses map. - // - // switch y := x.(type) { // Defs[Ident(y)] is undefined - // case int: print(y) // Implicits[CaseClause(int)] = Var(y_int) - // case string: print(y) // Implicits[CaseClause(string)] = Var(y_string) - // } - // - var isCaseVar bool - for syntax, obj := range info.Implicits { - if _, ok := syntax.(*ast.CaseClause); ok && obj.Pos() == from.Pos() { - isCaseVar = true - r.check(obj) - } - } - - r.checkInLexicalScope(from, info) - - // Finally, if this was a type switch, change the variable y. - if isCaseVar { - _, path, _ := r.iprog.PathEnclosingInterval(from.Pos(), from.Pos()) - path[0].(*ast.Ident).Name = r.to // path is [Ident AssignStmt TypeSwitchStmt...] - } -} - -// checkInLexicalScope performs safety checks that a renaming does not -// change the lexical reference structure of the specified package. -// -// For objects in lexical scope, there are three kinds of conflicts: -// same-, sub-, and super-block conflicts. We will illustrate all three -// using this example: -// -// var x int -// var z int -// -// func f(y int) { -// print(x) -// print(y) -// } -// -// Renaming x to z encounters a SAME-BLOCK CONFLICT, because an object -// with the new name already exists, defined in the same lexical block -// as the old object. -// -// Renaming x to y encounters a SUB-BLOCK CONFLICT, because there exists -// a reference to x from within (what would become) a hole in its scope. -// The definition of y in an (inner) sub-block would cast a shadow in -// the scope of the renamed variable. -// -// Renaming y to x encounters a SUPER-BLOCK CONFLICT. This is the -// converse situation: there is an existing definition of the new name -// (x) in an (enclosing) super-block, and the renaming would create a -// hole in its scope, within which there exist references to it. The -// new name casts a shadow in scope of the existing definition of x in -// the super-block. -// -// Removing the old name (and all references to it) is always safe, and -// requires no checks. -// -func (r *renamer) checkInLexicalScope(from types.Object, info *loader.PackageInfo) { - b := from.Parent() // the block defining the 'from' object - if b != nil { - toBlock, to := b.LookupParent(r.to, from.Parent().End()) - if toBlock == b { - // same-block conflict - r.errorf(from.Pos(), "renaming this %s %q to %q", - objectKind(from), from.Name(), r.to) - r.errorf(to.Pos(), "\tconflicts with %s in same block", - objectKind(to)) - return - } else if toBlock != nil { - // Check for super-block conflict. - // The name r.to is defined in a superblock. - // Is that name referenced from within this block? - forEachLexicalRef(info, to, func(id *ast.Ident, block *types.Scope) bool { - _, obj := lexicalLookup(block, from.Name(), id.Pos()) - if obj == from { - // super-block conflict - r.errorf(from.Pos(), "renaming this %s %q to %q", - objectKind(from), from.Name(), r.to) - r.errorf(id.Pos(), "\twould shadow this reference") - r.errorf(to.Pos(), "\tto the %s declared here", - objectKind(to)) - return false // stop - } - return true - }) - } - } - - // Check for sub-block conflict. - // Is there an intervening definition of r.to between - // the block defining 'from' and some reference to it? - forEachLexicalRef(info, from, func(id *ast.Ident, block *types.Scope) bool { - // Find the block that defines the found reference. - // It may be an ancestor. - fromBlock, _ := lexicalLookup(block, from.Name(), id.Pos()) - - // See what r.to would resolve to in the same scope. - toBlock, to := lexicalLookup(block, r.to, id.Pos()) - if to != nil { - // sub-block conflict - if deeper(toBlock, fromBlock) { - r.errorf(from.Pos(), "renaming this %s %q to %q", - objectKind(from), from.Name(), r.to) - r.errorf(id.Pos(), "\twould cause this reference to become shadowed") - r.errorf(to.Pos(), "\tby this intervening %s definition", - objectKind(to)) - return false // stop - } - } - return true - }) - - // Renaming a type that is used as an embedded field - // requires renaming the field too. e.g. - // type T int // if we rename this to U.. - // var s struct {T} - // print(s.T) // ...this must change too - if _, ok := from.(*types.TypeName); ok { - for id, obj := range info.Uses { - if obj == from { - if field := info.Defs[id]; field != nil { - r.check(field) - } - } - } - } -} - -// lexicalLookup is like (*types.Scope).LookupParent but respects the -// environment visible at pos. It assumes the relative position -// information is correct with each file. -func lexicalLookup(block *types.Scope, name string, pos token.Pos) (*types.Scope, types.Object) { - for b := block; b != nil; b = b.Parent() { - obj := b.Lookup(name) - // The scope of a package-level object is the entire package, - // so ignore pos in that case. - // No analogous clause is needed for file-level objects - // since no reference can appear before an import decl. - if obj != nil && (b == obj.Pkg().Scope() || obj.Pos() < pos) { - return b, obj - } - } - return nil, nil -} - -// deeper reports whether block x is lexically deeper than y. -func deeper(x, y *types.Scope) bool { - if x == y || x == nil { - return false - } else if y == nil { - return true - } else { - return deeper(x.Parent(), y.Parent()) - } -} - -// forEachLexicalRef calls fn(id, block) for each identifier id in package -// info that is a reference to obj in lexical scope. block is the -// lexical block enclosing the reference. If fn returns false the -// iteration is terminated and findLexicalRefs returns false. -func forEachLexicalRef(info *loader.PackageInfo, obj types.Object, fn func(id *ast.Ident, block *types.Scope) bool) bool { - ok := true - var stack []ast.Node - - var visit func(n ast.Node) bool - visit = func(n ast.Node) bool { - if n == nil { - stack = stack[:len(stack)-1] // pop - return false - } - if !ok { - return false // bail out - } - - stack = append(stack, n) // push - switch n := n.(type) { - case *ast.Ident: - if info.Uses[n] == obj { - block := enclosingBlock(&info.Info, stack) - if !fn(n, block) { - ok = false - } - } - return visit(nil) // pop stack - - case *ast.SelectorExpr: - // don't visit n.Sel - ast.Inspect(n.X, visit) - return visit(nil) // pop stack, don't descend - - case *ast.CompositeLit: - // Handle recursion ourselves for struct literals - // so we don't visit field identifiers. - tv := info.Types[n] - if _, ok := deref(tv.Type).Underlying().(*types.Struct); ok { - if n.Type != nil { - ast.Inspect(n.Type, visit) - } - for _, elt := range n.Elts { - if kv, ok := elt.(*ast.KeyValueExpr); ok { - ast.Inspect(kv.Value, visit) - } else { - ast.Inspect(elt, visit) - } - } - return visit(nil) // pop stack, don't descend - } - } - return true - } - - for _, f := range info.Files { - ast.Inspect(f, visit) - if len(stack) != 0 { - panic(stack) - } - if !ok { - break - } - } - return ok -} - -// enclosingBlock returns the innermost block enclosing the specified -// AST node, specified in the form of a path from the root of the file, -// [file...n]. -func enclosingBlock(info *types.Info, stack []ast.Node) *types.Scope { - for i := range stack { - n := stack[len(stack)-1-i] - // For some reason, go/types always associates a - // function's scope with its FuncType. - // TODO(adonovan): feature or a bug? - switch f := n.(type) { - case *ast.FuncDecl: - n = f.Type - case *ast.FuncLit: - n = f.Type - } - if b := info.Scopes[n]; b != nil { - return b - } - } - panic("no Scope for *ast.File") -} - -func (r *renamer) checkLabel(label *types.Label) { - // Check there are no identical labels in the function's label block. - // (Label blocks don't nest, so this is easy.) - if prev := label.Parent().Lookup(r.to); prev != nil { - r.errorf(label.Pos(), "renaming this label %q to %q", label.Name(), prev.Name()) - r.errorf(prev.Pos(), "\twould conflict with this one") - } -} - -// checkStructField checks that the field renaming will not cause -// conflicts at its declaration, or ambiguity or changes to any selection. -func (r *renamer) checkStructField(from *types.Var) { - // Check that the struct declaration is free of field conflicts, - // and field/method conflicts. - - // go/types offers no easy way to get from a field (or interface - // method) to its declaring struct (or interface), so we must - // ascend the AST. - info, path, _ := r.iprog.PathEnclosingInterval(from.Pos(), from.Pos()) - // path matches this pattern: - // [Ident SelectorExpr? StarExpr? Field FieldList StructType ParenExpr* ... File] - - // Ascend to FieldList. - var i int - for { - if _, ok := path[i].(*ast.FieldList); ok { - break - } - i++ - } - i++ - tStruct := path[i].(*ast.StructType) - i++ - // Ascend past parens (unlikely). - for { - _, ok := path[i].(*ast.ParenExpr) - if !ok { - break - } - i++ - } - if spec, ok := path[i].(*ast.TypeSpec); ok { - // This struct is also a named type. - // We must check for direct (non-promoted) field/field - // and method/field conflicts. - named := info.Defs[spec.Name].Type() - prev, indices, _ := types.LookupFieldOrMethod(named, true, info.Pkg, r.to) - if len(indices) == 1 { - r.errorf(from.Pos(), "renaming this field %q to %q", - from.Name(), r.to) - r.errorf(prev.Pos(), "\twould conflict with this %s", - objectKind(prev)) - return // skip checkSelections to avoid redundant errors - } - } else { - // This struct is not a named type. - // We need only check for direct (non-promoted) field/field conflicts. - T := info.Types[tStruct].Type.Underlying().(*types.Struct) - for i := 0; i < T.NumFields(); i++ { - if prev := T.Field(i); prev.Name() == r.to { - r.errorf(from.Pos(), "renaming this field %q to %q", - from.Name(), r.to) - r.errorf(prev.Pos(), "\twould conflict with this field") - return // skip checkSelections to avoid redundant errors - } - } - } - - // Renaming an anonymous field requires renaming the type too. e.g. - // print(s.T) // if we rename T to U, - // type T int // this and - // var s struct {T} // this must change too. - if from.Anonymous() { - if named, ok := from.Type().(*types.Named); ok { - r.check(named.Obj()) - } else if named, ok := deref(from.Type()).(*types.Named); ok { - r.check(named.Obj()) - } - } - - // Check integrity of existing (field and method) selections. - r.checkSelections(from) -} - -// checkSelection checks that all uses and selections that resolve to -// the specified object would continue to do so after the renaming. -func (r *renamer) checkSelections(from types.Object) { - for pkg, info := range r.packages { - if id := someUse(info, from); id != nil { - if !r.checkExport(id, pkg, from) { - return - } - } - - for syntax, sel := range info.Selections { - // There may be extant selections of only the old - // name or only the new name, so we must check both. - // (If neither, the renaming is sound.) - // - // In both cases, we wish to compare the lengths - // of the implicit field path (Selection.Index) - // to see if the renaming would change it. - // - // If a selection that resolves to 'from', when renamed, - // would yield a path of the same or shorter length, - // this indicates ambiguity or a changed referent, - // analogous to same- or sub-block lexical conflict. - // - // If a selection using the name 'to' would - // yield a path of the same or shorter length, - // this indicates ambiguity or shadowing, - // analogous to same- or super-block lexical conflict. - - // TODO(adonovan): fix: derive from Types[syntax.X].Mode - // TODO(adonovan): test with pointer, value, addressable value. - isAddressable := true - - if sel.Obj() == from { - if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), isAddressable, from.Pkg(), r.to); obj != nil { - // Renaming this existing selection of - // 'from' may block access to an existing - // type member named 'to'. - delta := len(indices) - len(sel.Index()) - if delta > 0 { - continue // no ambiguity - } - r.selectionConflict(from, delta, syntax, obj) - return - } - - } else if sel.Obj().Name() == r.to { - if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), isAddressable, from.Pkg(), from.Name()); obj == from { - // Renaming 'from' may cause this existing - // selection of the name 'to' to change - // its meaning. - delta := len(indices) - len(sel.Index()) - if delta > 0 { - continue // no ambiguity - } - r.selectionConflict(from, -delta, syntax, sel.Obj()) - return - } - } - } - } -} - -func (r *renamer) selectionConflict(from types.Object, delta int, syntax *ast.SelectorExpr, obj types.Object) { - r.errorf(from.Pos(), "renaming this %s %q to %q", - objectKind(from), from.Name(), r.to) - - switch { - case delta < 0: - // analogous to sub-block conflict - r.errorf(syntax.Sel.Pos(), - "\twould change the referent of this selection") - r.errorf(obj.Pos(), "\tof this %s", objectKind(obj)) - case delta == 0: - // analogous to same-block conflict - r.errorf(syntax.Sel.Pos(), - "\twould make this reference ambiguous") - r.errorf(obj.Pos(), "\twith this %s", objectKind(obj)) - case delta > 0: - // analogous to super-block conflict - r.errorf(syntax.Sel.Pos(), - "\twould shadow this selection") - r.errorf(obj.Pos(), "\tof the %s declared here", - objectKind(obj)) - } -} - -// checkMethod performs safety checks for renaming a method. -// There are three hazards: -// - declaration conflicts -// - selection ambiguity/changes -// - entailed renamings of assignable concrete/interface types. -// We reject renamings initiated at concrete methods if it would -// change the assignability relation. For renamings of abstract -// methods, we rename all methods transitively coupled to it via -// assignability. -func (r *renamer) checkMethod(from *types.Func) { - // e.g. error.Error - if from.Pkg() == nil { - r.errorf(from.Pos(), "you cannot rename built-in method %s", from) - return - } - - // ASSIGNABILITY: We reject renamings of concrete methods that - // would break a 'satisfy' constraint; but renamings of abstract - // methods are allowed to proceed, and we rename affected - // concrete and abstract methods as necessary. It is the - // initial method that determines the policy. - - // Check for conflict at point of declaration. - // Check to ensure preservation of assignability requirements. - R := recv(from).Type() - if isInterface(R) { - // Abstract method - - // declaration - prev, _, _ := types.LookupFieldOrMethod(R, false, from.Pkg(), r.to) - if prev != nil { - r.errorf(from.Pos(), "renaming this interface method %q to %q", - from.Name(), r.to) - r.errorf(prev.Pos(), "\twould conflict with this method") - return - } - - // Check all interfaces that embed this one for - // declaration conflicts too. - for _, info := range r.packages { - // Start with named interface types (better errors) - for _, obj := range info.Defs { - if obj, ok := obj.(*types.TypeName); ok && isInterface(obj.Type()) { - f, _, _ := types.LookupFieldOrMethod( - obj.Type(), false, from.Pkg(), from.Name()) - if f == nil { - continue - } - t, _, _ := types.LookupFieldOrMethod( - obj.Type(), false, from.Pkg(), r.to) - if t == nil { - continue - } - r.errorf(from.Pos(), "renaming this interface method %q to %q", - from.Name(), r.to) - r.errorf(t.Pos(), "\twould conflict with this method") - r.errorf(obj.Pos(), "\tin named interface type %q", obj.Name()) - } - } - - // Now look at all literal interface types (includes named ones again). - for e, tv := range info.Types { - if e, ok := e.(*ast.InterfaceType); ok { - _ = e - _ = tv.Type.(*types.Interface) - // TODO(adonovan): implement same check as above. - } - } - } - - // assignability - // - // Find the set of concrete or abstract methods directly - // coupled to abstract method 'from' by some - // satisfy.Constraint, and rename them too. - for key := range r.satisfy() { - // key = (lhs, rhs) where lhs is always an interface. - - lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name()) - if lsel == nil { - continue - } - rmethods := r.msets.MethodSet(key.RHS) - rsel := rmethods.Lookup(from.Pkg(), from.Name()) - if rsel == nil { - continue - } - - // If both sides have a method of this name, - // and one of them is m, the other must be coupled. - var coupled *types.Func - switch from { - case lsel.Obj(): - coupled = rsel.Obj().(*types.Func) - case rsel.Obj(): - coupled = lsel.Obj().(*types.Func) - default: - continue - } - - // We must treat concrete-to-interface - // constraints like an implicit selection C.f of - // each interface method I.f, and check that the - // renaming leaves the selection unchanged and - // unambiguous. - // - // Fun fact: the implicit selection of C.f - // type I interface{f()} - // type C struct{I} - // func (C) g() - // var _ I = C{} // here - // yields abstract method I.f. This can make error - // messages less than obvious. - // - if !isInterface(key.RHS) { - // The logic below was derived from checkSelections. - - rtosel := rmethods.Lookup(from.Pkg(), r.to) - if rtosel != nil { - rto := rtosel.Obj().(*types.Func) - delta := len(rsel.Index()) - len(rtosel.Index()) - if delta < 0 { - continue // no ambiguity - } - - // TODO(adonovan): record the constraint's position. - keyPos := token.NoPos - - r.errorf(from.Pos(), "renaming this method %q to %q", - from.Name(), r.to) - if delta == 0 { - // analogous to same-block conflict - r.errorf(keyPos, "\twould make the %s method of %s invoked via interface %s ambiguous", - r.to, key.RHS, key.LHS) - r.errorf(rto.Pos(), "\twith (%s).%s", - recv(rto).Type(), r.to) - } else { - // analogous to super-block conflict - r.errorf(keyPos, "\twould change the %s method of %s invoked via interface %s", - r.to, key.RHS, key.LHS) - r.errorf(coupled.Pos(), "\tfrom (%s).%s", - recv(coupled).Type(), r.to) - r.errorf(rto.Pos(), "\tto (%s).%s", - recv(rto).Type(), r.to) - } - return // one error is enough - } - } - - if !r.changeMethods { - // This should be unreachable. - r.errorf(from.Pos(), "internal error: during renaming of abstract method %s", from) - r.errorf(coupled.Pos(), "\tchangedMethods=false, coupled method=%s", coupled) - r.errorf(from.Pos(), "\tPlease file a bug report") - return - } - - // Rename the coupled method to preserve assignability. - r.check(coupled) - } - } else { - // Concrete method - - // declaration - prev, indices, _ := types.LookupFieldOrMethod(R, true, from.Pkg(), r.to) - if prev != nil && len(indices) == 1 { - r.errorf(from.Pos(), "renaming this method %q to %q", - from.Name(), r.to) - r.errorf(prev.Pos(), "\twould conflict with this %s", - objectKind(prev)) - return - } - - // assignability - // - // Find the set of abstract methods coupled to concrete - // method 'from' by some satisfy.Constraint, and rename - // them too. - // - // Coupling may be indirect, e.g. I.f <-> C.f via type D. - // - // type I interface {f()} - // type C int - // type (C) f() - // type D struct{C} - // var _ I = D{} - // - for key := range r.satisfy() { - // key = (lhs, rhs) where lhs is always an interface. - if isInterface(key.RHS) { - continue - } - rsel := r.msets.MethodSet(key.RHS).Lookup(from.Pkg(), from.Name()) - if rsel == nil || rsel.Obj() != from { - continue // rhs does not have the method - } - lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name()) - if lsel == nil { - continue - } - imeth := lsel.Obj().(*types.Func) - - // imeth is the abstract method (e.g. I.f) - // and key.RHS is the concrete coupling type (e.g. D). - if !r.changeMethods { - r.errorf(from.Pos(), "renaming this method %q to %q", - from.Name(), r.to) - var pos token.Pos - var iface string - - I := recv(imeth).Type() - if named, ok := I.(*types.Named); ok { - pos = named.Obj().Pos() - iface = "interface " + named.Obj().Name() - } else { - pos = from.Pos() - iface = I.String() - } - r.errorf(pos, "\twould make %s no longer assignable to %s", - key.RHS, iface) - r.errorf(imeth.Pos(), "\t(rename %s.%s if you intend to change both types)", - I, from.Name()) - return // one error is enough - } - - // Rename the coupled interface method to preserve assignability. - r.check(imeth) - } - } - - // Check integrity of existing (field and method) selections. - // We skip this if there were errors above, to avoid redundant errors. - r.checkSelections(from) -} - -func (r *renamer) checkExport(id *ast.Ident, pkg *types.Package, from types.Object) bool { - // Reject cross-package references if r.to is unexported. - // (Such references may be qualified identifiers or field/method - // selections.) - if !ast.IsExported(r.to) && pkg != from.Pkg() { - r.errorf(from.Pos(), - "renaming this %s %q to %q would make it unexported", - objectKind(from), from.Name(), r.to) - r.errorf(id.Pos(), "\tbreaking references from packages such as %q", - pkg.Path()) - return false - } - return true -} - -// satisfy returns the set of interface satisfaction constraints. -func (r *renamer) satisfy() map[satisfy.Constraint]bool { - if r.satisfyConstraints == nil { - // Compute on demand: it's expensive. - var f satisfy.Finder - for _, info := range r.packages { - f.Find(&info.Info, info.Files) - } - r.satisfyConstraints = f.Result - } - return r.satisfyConstraints -} - -// -- helpers ---------------------------------------------------------- - -// recv returns the method's receiver. -func recv(meth *types.Func) *types.Var { - return meth.Type().(*types.Signature).Recv() -} - -// someUse returns an arbitrary use of obj within info. -func someUse(info *loader.PackageInfo, obj types.Object) *ast.Ident { - for id, o := range info.Uses { - if o == obj { - return id - } - } - return nil -} - -// -- Plundered from golang.org/x/tools/go/ssa ----------------- - -func isInterface(T types.Type) bool { return types.IsInterface(T) } - -func deref(typ types.Type) types.Type { - if p, _ := typ.(*types.Pointer); p != nil { - return p.Elem() - } - return typ -} diff --git a/refactor/rename/rename14.go b/refactor/rename/rename14.go deleted file mode 100644 index e4ccc061ab..0000000000 --- a/refactor/rename/rename14.go +++ /dev/null @@ -1,510 +0,0 @@ -// Copyright 2014 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. - -// +build !go1.5 - -// Package rename contains the implementation of the 'gorename' command -// whose main function is in golang.org/x/tools/cmd/gorename. -// See the Usage constant for the command documentation. -package rename // import "golang.org/x/tools/refactor/rename" - -import ( - "bytes" - "errors" - "fmt" - "go/ast" - "go/build" - "go/format" - "go/parser" - "go/token" - "io" - "io/ioutil" - "log" - "os" - "os/exec" - "path" - "sort" - "strconv" - "strings" - - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/types" - "golang.org/x/tools/go/types/typeutil" - "golang.org/x/tools/refactor/importgraph" - "golang.org/x/tools/refactor/satisfy" -) - -const Usage = `gorename: precise type-safe renaming of identifiers in Go source code. - -Usage: - - gorename (-from | -offset :#) -to [-force] - -You must specify the object (named entity) to rename using the -offset -or -from flag. Exactly one must be specified. - -Flags: - --offset specifies the filename and byte offset of an identifier to rename. - This form is intended for use by text editors. - --from specifies the object to rename using a query notation; - This form is intended for interactive use at the command line. - A legal -from query has one of the following forms: - - "encoding/json".Decoder.Decode method of package-level named type - (*"encoding/json".Decoder).Decode ditto, alternative syntax - "encoding/json".Decoder.buf field of package-level named struct type - "encoding/json".HTMLEscape package member (const, func, var, type) - "encoding/json".Decoder.Decode::x local object x within a method - "encoding/json".HTMLEscape::x local object x within a function - "encoding/json"::x object x anywhere within a package - json.go::x object x within file json.go - - Double-quotes must be escaped when writing a shell command. - Quotes may be omitted for single-segment import paths such as "fmt". - - For methods, the parens and '*' on the receiver type are both - optional. - - It is an error if one of the ::x queries matches multiple - objects. - --to the new name. - --force causes the renaming to proceed even if conflicts were reported. - The resulting program may be ill-formed, or experience a change - in behaviour. - - WARNING: this flag may even cause the renaming tool to crash. - (In due course this bug will be fixed by moving certain - analyses into the type-checker.) - --d display diffs instead of rewriting files - --v enables verbose logging. - -gorename automatically computes the set of packages that might be -affected. For a local renaming, this is just the package specified by --from or -offset, but for a potentially exported name, gorename scans -the workspace ($GOROOT and $GOPATH). - -gorename rejects renamings of concrete methods that would change the -assignability relation between types and interfaces. If the interface -change was intentional, initiate the renaming at the interface method. - -gorename rejects any renaming that would create a conflict at the point -of declaration, or a reference conflict (ambiguity or shadowing), or -anything else that could cause the resulting program not to compile. - - -Examples: - -$ gorename -offset file.go:#123 -to foo - - Rename the object whose identifier is at byte offset 123 within file file.go. - -$ gorename -from '"bytes".Buffer.Len' -to Size - - Rename the "Len" method of the *bytes.Buffer type to "Size". - ----- TODO ---- - -Correctness: -- handle dot imports correctly -- document limitations (reflection, 'implements' algorithm). -- sketch a proof of exhaustiveness. - -Features: -- support running on packages specified as *.go files on the command line -- support running on programs containing errors (loader.Config.AllowErrors) -- allow users to specify a scope other than "global" (to avoid being - stuck by neglected packages in $GOPATH that don't build). -- support renaming the package clause (no object) -- support renaming an import path (no ident or object) - (requires filesystem + SCM updates). -- detect and reject edits to autogenerated files (cgo, protobufs) - and optionally $GOROOT packages. -- report all conflicts, or at least all qualitatively distinct ones. - Sometimes we stop to avoid redundancy, but - it may give a disproportionate sense of safety in -force mode. -- support renaming all instances of a pattern, e.g. - all receiver vars of a given type, - all local variables of a given type, - all PkgNames for a given package. -- emit JSON output for other editors and tools. -` - -var ( - // Force enables patching of the source files even if conflicts were reported. - // The resulting program may be ill-formed. - // It may even cause gorename to crash. TODO(adonovan): fix that. - Force bool - - // Diff causes the tool to display diffs instead of rewriting files. - Diff bool - - // DiffCmd specifies the diff command used by the -d feature. - // (The command must accept a -u flag and two filename arguments.) - DiffCmd = "diff" - - // ConflictError is returned by Main when it aborts the renaming due to conflicts. - // (It is distinguished because the interesting errors are the conflicts themselves.) - ConflictError = errors.New("renaming aborted due to conflicts") - - // Verbose enables extra logging. - Verbose bool -) - -var stdout io.Writer = os.Stdout - -type renamer struct { - iprog *loader.Program - objsToUpdate map[types.Object]bool - hadConflicts bool - to string - satisfyConstraints map[satisfy.Constraint]bool - packages map[*types.Package]*loader.PackageInfo // subset of iprog.AllPackages to inspect - msets typeutil.MethodSetCache - changeMethods bool -} - -var reportError = func(posn token.Position, message string) { - fmt.Fprintf(os.Stderr, "%s: %s\n", posn, message) -} - -// importName renames imports of the package with the given path in -// the given package. If fromName is not empty, only imports as -// fromName will be renamed. If the renaming would lead to a conflict, -// the file is left unchanged. -func importName(iprog *loader.Program, info *loader.PackageInfo, fromPath, fromName, to string) error { - for _, f := range info.Files { - var from types.Object - for _, imp := range f.Imports { - importPath, _ := strconv.Unquote(imp.Path.Value) - importName := path.Base(importPath) - if imp.Name != nil { - importName = imp.Name.Name - } - if importPath == fromPath && (fromName == "" || importName == fromName) { - from = info.Implicits[imp] - break - } - } - if from == nil { - continue - } - r := renamer{ - iprog: iprog, - objsToUpdate: make(map[types.Object]bool), - to: to, - packages: map[*types.Package]*loader.PackageInfo{info.Pkg: info}, - } - r.check(from) - if r.hadConflicts { - continue // ignore errors; leave the existing name - } - if err := r.update(); err != nil { - return err - } - } - return nil -} - -func Main(ctxt *build.Context, offsetFlag, fromFlag, to string) error { - // -- Parse the -from or -offset specifier ---------------------------- - - if (offsetFlag == "") == (fromFlag == "") { - return fmt.Errorf("exactly one of the -from and -offset flags must be specified") - } - - if !isValidIdentifier(to) { - return fmt.Errorf("-to %q: not a valid identifier", to) - } - - if Diff { - defer func(saved func(string, []byte) error) { writeFile = saved }(writeFile) - writeFile = diff - } - - var spec *spec - var err error - if fromFlag != "" { - spec, err = parseFromFlag(ctxt, fromFlag) - } else { - spec, err = parseOffsetFlag(ctxt, offsetFlag) - } - if err != nil { - return err - } - - if spec.fromName == to { - return fmt.Errorf("the old and new names are the same: %s", to) - } - - // -- Load the program consisting of the initial package ------------- - - iprog, err := loadProgram(ctxt, map[string]bool{spec.pkg: true}) - if err != nil { - return err - } - - fromObjects, err := findFromObjects(iprog, spec) - if err != nil { - return err - } - - // -- Load a larger program, for global renamings --------------------- - - if requiresGlobalRename(fromObjects, to) { - // For a local refactoring, we needn't load more - // packages, but if the renaming affects the package's - // API, we we must load all packages that depend on the - // package defining the object, plus their tests. - - if Verbose { - log.Print("Potentially global renaming; scanning workspace...") - } - - // Scan the workspace and build the import graph. - _, rev, errors := importgraph.Build(ctxt) - if len(errors) > 0 { - // With a large GOPATH tree, errors are inevitable. - // Report them but proceed. - fmt.Fprintf(os.Stderr, "While scanning Go workspace:\n") - for path, err := range errors { - fmt.Fprintf(os.Stderr, "Package %q: %s.\n", path, err) - } - } - - // Enumerate the set of potentially affected packages. - affectedPackages := make(map[string]bool) - for _, obj := range fromObjects { - // External test packages are never imported, - // so they will never appear in the graph. - for path := range rev.Search(obj.Pkg().Path()) { - affectedPackages[path] = true - } - } - - // TODO(adonovan): allow the user to specify the scope, - // or -ignore patterns? Computing the scope when we - // don't (yet) support inputs containing errors can make - // the tool rather brittle. - - // Re-load the larger program. - iprog, err = loadProgram(ctxt, affectedPackages) - if err != nil { - return err - } - - fromObjects, err = findFromObjects(iprog, spec) - if err != nil { - return err - } - } - - // -- Do the renaming ------------------------------------------------- - - r := renamer{ - iprog: iprog, - objsToUpdate: make(map[types.Object]bool), - to: to, - packages: make(map[*types.Package]*loader.PackageInfo), - } - - // A renaming initiated at an interface method indicates the - // intention to rename abstract and concrete methods as needed - // to preserve assignability. - for _, obj := range fromObjects { - if obj, ok := obj.(*types.Func); ok { - recv := obj.Type().(*types.Signature).Recv() - if recv != nil && isInterface(recv.Type().Underlying()) { - r.changeMethods = true - break - } - } - } - - // Only the initially imported packages (iprog.Imported) and - // their external tests (iprog.Created) should be inspected or - // modified, as only they have type-checked functions bodies. - // The rest are just dependencies, needed only for package-level - // type information. - for _, info := range iprog.Imported { - r.packages[info.Pkg] = info - } - for _, info := range iprog.Created { // (tests) - r.packages[info.Pkg] = info - } - - for _, from := range fromObjects { - r.check(from) - } - if r.hadConflicts && !Force { - return ConflictError - } - return r.update() -} - -// loadProgram loads the specified set of packages (plus their tests) -// and all their dependencies, from source, through the specified build -// context. Only packages in pkgs will have their functions bodies typechecked. -func loadProgram(ctxt *build.Context, pkgs map[string]bool) (*loader.Program, error) { - conf := loader.Config{ - Build: ctxt, - ParserMode: parser.ParseComments, - - // TODO(adonovan): enable this. Requires making a lot of code more robust! - AllowErrors: false, - } - - // Optimization: don't type-check the bodies of functions in our - // dependencies, since we only need exported package members. - conf.TypeCheckFuncBodies = func(p string) bool { - return pkgs[p] || pkgs[strings.TrimSuffix(p, "_test")] - } - - if Verbose { - var list []string - for pkg := range pkgs { - list = append(list, pkg) - } - sort.Strings(list) - for _, pkg := range list { - log.Printf("Loading package: %s", pkg) - } - } - - for pkg := range pkgs { - conf.ImportWithTests(pkg) - } - return conf.Load() -} - -// requiresGlobalRename reports whether this renaming could potentially -// affect other packages in the Go workspace. -func requiresGlobalRename(fromObjects []types.Object, to string) bool { - var tfm bool - for _, from := range fromObjects { - if from.Exported() { - return true - } - switch objectKind(from) { - case "type", "field", "method": - tfm = true - } - } - if ast.IsExported(to) && tfm { - // A global renaming may be necessary even if we're - // exporting a previous unexported name, since if it's - // the name of a type, field or method, this could - // change selections in other packages. - // (We include "type" in this list because a type - // used as an embedded struct field entails a field - // renaming.) - return true - } - return false -} - -// update updates the input files. -func (r *renamer) update() error { - // We use token.File, not filename, since a file may appear to - // belong to multiple packages and be parsed more than once. - // token.File captures this distinction; filename does not. - var nidents int - var filesToUpdate = make(map[*token.File]bool) - for _, info := range r.packages { - // Mutate the ASTs and note the filenames. - for id, obj := range info.Defs { - if r.objsToUpdate[obj] { - nidents++ - id.Name = r.to - filesToUpdate[r.iprog.Fset.File(id.Pos())] = true - } - } - for id, obj := range info.Uses { - if r.objsToUpdate[obj] { - nidents++ - id.Name = r.to - filesToUpdate[r.iprog.Fset.File(id.Pos())] = true - } - } - } - - // TODO(adonovan): don't rewrite cgo + generated files. - var nerrs, npkgs int - for _, info := range r.packages { - first := true - for _, f := range info.Files { - tokenFile := r.iprog.Fset.File(f.Pos()) - if filesToUpdate[tokenFile] { - if first { - npkgs++ - first = false - if Verbose { - log.Printf("Updating package %s", info.Pkg.Path()) - } - } - - filename := tokenFile.Name() - var buf bytes.Buffer - if err := format.Node(&buf, r.iprog.Fset, f); err != nil { - log.Printf("failed to pretty-print syntax tree: %v", err) - nerrs++ - continue - } - if err := writeFile(filename, buf.Bytes()); err != nil { - log.Print(err) - nerrs++ - } - } - } - } - if !Diff { - fmt.Printf("Renamed %d occurrence%s in %d file%s in %d package%s.\n", - nidents, plural(nidents), - len(filesToUpdate), plural(len(filesToUpdate)), - npkgs, plural(npkgs)) - } - if nerrs > 0 { - return fmt.Errorf("failed to rewrite %d file%s", nerrs, plural(nerrs)) - } - return nil -} - -func plural(n int) string { - if n != 1 { - return "s" - } - return "" -} - -// writeFile is a seam for testing and for the -d flag. -var writeFile = reallyWriteFile - -func reallyWriteFile(filename string, content []byte) error { - return ioutil.WriteFile(filename, content, 0644) -} - -func diff(filename string, content []byte) error { - renamed := fmt.Sprintf("%s.%d.renamed", filename, os.Getpid()) - if err := ioutil.WriteFile(renamed, content, 0644); err != nil { - return err - } - defer os.Remove(renamed) - - diff, err := exec.Command(DiffCmd, "-u", filename, renamed).CombinedOutput() - if len(diff) > 0 { - // diff exits with a non-zero status when the files don't match. - // Ignore that failure as long as we get output. - stdout.Write(diff) - return nil - } - if err != nil { - return fmt.Errorf("computing diff: %v", err) - } - return nil -} diff --git a/refactor/rename/spec14.go b/refactor/rename/spec14.go deleted file mode 100644 index 7634ae8fb0..0000000000 --- a/refactor/rename/spec14.go +++ /dev/null @@ -1,568 +0,0 @@ -// Copyright 2014 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. - -// +build !go1.5 - -package rename - -// This file contains logic related to specifying a renaming: parsing of -// the flags as a form of query, and finding the object(s) it denotes. -// See Usage for flag details. - -import ( - "bytes" - "fmt" - "go/ast" - "go/build" - "go/parser" - "go/token" - "log" - "os" - "path/filepath" - "strconv" - "strings" - - "golang.org/x/tools/go/buildutil" - "golang.org/x/tools/go/loader" - "golang.org/x/tools/go/types" -) - -// A spec specifies an entity to rename. -// -// It is populated from an -offset flag or -from query; -// see Usage for the allowed -from query forms. -// -type spec struct { - // pkg is the package containing the position - // specified by the -from or -offset flag. - // If filename == "", our search for the 'from' entity - // is restricted to this package. - pkg string - - // The original name of the entity being renamed. - // If the query had a ::from component, this is that; - // otherwise it's the last segment, e.g. - // (encoding/json.Decoder).from - // encoding/json.from - fromName string - - // -- The remaining fields are private to this file. All are optional. -- - - // The query's ::x suffix, if any. - searchFor string - - // e.g. "Decoder" in "(encoding/json.Decoder).fieldOrMethod" - // or "encoding/json.Decoder - pkgMember string - - // e.g. fieldOrMethod in "(encoding/json.Decoder).fieldOrMethod" - typeMember string - - // Restricts the query to this file. - // Implied by -from="file.go::x" and -offset flags. - filename string - - // Byte offset of the 'from' identifier within the file named 'filename'. - // -offset mode only. - offset int -} - -// parseFromFlag interprets the "-from" flag value as a renaming specification. -// See Usage in rename.go for valid formats. -func parseFromFlag(ctxt *build.Context, fromFlag string) (*spec, error) { - var spec spec - var main string // sans "::x" suffix - switch parts := strings.Split(fromFlag, "::"); len(parts) { - case 1: - main = parts[0] - case 2: - main = parts[0] - spec.searchFor = parts[1] - if parts[1] == "" { - // error - } - default: - return nil, fmt.Errorf("-from %q: invalid identifier specification (see -help for formats)", fromFlag) - } - - if strings.HasSuffix(main, ".go") { - // main is "filename.go" - if spec.searchFor == "" { - return nil, fmt.Errorf("-from: filename %q must have a ::name suffix", main) - } - spec.filename = main - if !buildutil.FileExists(ctxt, spec.filename) { - return nil, fmt.Errorf("no such file: %s", spec.filename) - } - - bp, err := buildutil.ContainingPackage(ctxt, wd, spec.filename) - if err != nil { - return nil, err - } - spec.pkg = bp.ImportPath - - } else { - // main is one of: - // "importpath" - // "importpath".member - // (*"importpath".type).fieldormethod (parens and star optional) - if err := parseObjectSpec(&spec, main); err != nil { - return nil, err - } - } - - if spec.searchFor != "" { - spec.fromName = spec.searchFor - } - - cwd, err := os.Getwd() - if err != nil { - return nil, err - } - - // Sanitize the package. - bp, err := ctxt.Import(spec.pkg, cwd, build.FindOnly) - if err != nil { - return nil, fmt.Errorf("can't find package %q", spec.pkg) - } - spec.pkg = bp.ImportPath - - if !isValidIdentifier(spec.fromName) { - return nil, fmt.Errorf("-from: invalid identifier %q", spec.fromName) - } - - if Verbose { - log.Printf("-from spec: %+v", spec) - } - - return &spec, nil -} - -// parseObjectSpec parses main as one of the non-filename forms of -// object specification. -func parseObjectSpec(spec *spec, main string) error { - // Parse main as a Go expression, albeit a strange one. - e, _ := parser.ParseExpr(main) - - if pkg := parseImportPath(e); pkg != "" { - // e.g. bytes or "encoding/json": a package - spec.pkg = pkg - if spec.searchFor == "" { - return fmt.Errorf("-from %q: package import path %q must have a ::name suffix", - main, main) - } - return nil - } - - if e, ok := e.(*ast.SelectorExpr); ok { - x := unparen(e.X) - - // Strip off star constructor, if any. - if star, ok := x.(*ast.StarExpr); ok { - x = star.X - } - - if pkg := parseImportPath(x); pkg != "" { - // package member e.g. "encoding/json".HTMLEscape - spec.pkg = pkg // e.g. "encoding/json" - spec.pkgMember = e.Sel.Name // e.g. "HTMLEscape" - spec.fromName = e.Sel.Name - return nil - } - - if x, ok := x.(*ast.SelectorExpr); ok { - // field/method of type e.g. ("encoding/json".Decoder).Decode - y := unparen(x.X) - if pkg := parseImportPath(y); pkg != "" { - spec.pkg = pkg // e.g. "encoding/json" - spec.pkgMember = x.Sel.Name // e.g. "Decoder" - spec.typeMember = e.Sel.Name // e.g. "Decode" - spec.fromName = e.Sel.Name - return nil - } - } - } - - return fmt.Errorf("-from %q: invalid expression", main) -} - -// parseImportPath returns the import path of the package denoted by e. -// Any import path may be represented as a string literal; -// single-segment import paths (e.g. "bytes") may also be represented as -// ast.Ident. parseImportPath returns "" for all other expressions. -func parseImportPath(e ast.Expr) string { - switch e := e.(type) { - case *ast.Ident: - return e.Name // e.g. bytes - - case *ast.BasicLit: - if e.Kind == token.STRING { - pkgname, _ := strconv.Unquote(e.Value) - return pkgname // e.g. "encoding/json" - } - } - return "" -} - -// parseOffsetFlag interprets the "-offset" flag value as a renaming specification. -func parseOffsetFlag(ctxt *build.Context, offsetFlag string) (*spec, error) { - var spec spec - // Validate -offset, e.g. file.go:#123 - parts := strings.Split(offsetFlag, ":#") - if len(parts) != 2 { - return nil, fmt.Errorf("-offset %q: invalid offset specification", offsetFlag) - } - - spec.filename = parts[0] - if !buildutil.FileExists(ctxt, spec.filename) { - return nil, fmt.Errorf("no such file: %s", spec.filename) - } - - bp, err := buildutil.ContainingPackage(ctxt, wd, spec.filename) - if err != nil { - return nil, err - } - spec.pkg = bp.ImportPath - - for _, r := range parts[1] { - if !isDigit(r) { - return nil, fmt.Errorf("-offset %q: non-numeric offset", offsetFlag) - } - } - spec.offset, err = strconv.Atoi(parts[1]) - if err != nil { - return nil, fmt.Errorf("-offset %q: non-numeric offset", offsetFlag) - } - - // Parse the file and check there's an identifier at that offset. - fset := token.NewFileSet() - f, err := buildutil.ParseFile(fset, ctxt, nil, wd, spec.filename, parser.ParseComments) - if err != nil { - return nil, fmt.Errorf("-offset %q: cannot parse file: %s", offsetFlag, err) - } - - id := identAtOffset(fset, f, spec.offset) - if id == nil { - return nil, fmt.Errorf("-offset %q: no identifier at this position", offsetFlag) - } - - spec.fromName = id.Name - - return &spec, nil -} - -var wd = func() string { - wd, err := os.Getwd() - if err != nil { - panic("cannot get working directory: " + err.Error()) - } - return wd -}() - -// For source trees built with 'go build', the -from or -offset -// spec identifies exactly one initial 'from' object to rename , -// but certain proprietary build systems allow a single file to -// appear in multiple packages (e.g. the test package contains a -// copy of its library), so there may be multiple objects for -// the same source entity. - -func findFromObjects(iprog *loader.Program, spec *spec) ([]types.Object, error) { - if spec.filename != "" { - return findFromObjectsInFile(iprog, spec) - } - - // Search for objects defined in specified package. - - // TODO(adonovan): the iprog.ImportMap has an entry {"main": ...} - // for main packages, even though that's not an import path. - // Seems like a bug. - // - // pkg := iprog.ImportMap[spec.pkg] - // if pkg == nil { - // return fmt.Errorf("cannot find package %s", spec.pkg) // can't happen? - // } - // info := iprog.AllPackages[pkg] - - // Workaround: lookup by value. - var info *loader.PackageInfo - var pkg *types.Package - for pkg, info = range iprog.AllPackages { - if pkg.Path() == spec.pkg { - break - } - } - if info == nil { - return nil, fmt.Errorf("package %q was not loaded", spec.pkg) - } - - objects, err := findObjects(info, spec) - if err != nil { - return nil, err - } - if len(objects) > 1 { - // ambiguous "*" scope query - return nil, ambiguityError(iprog.Fset, objects) - } - return objects, nil -} - -func findFromObjectsInFile(iprog *loader.Program, spec *spec) ([]types.Object, error) { - var fromObjects []types.Object - for _, info := range iprog.AllPackages { - // restrict to specified filename - // NB: under certain proprietary build systems, a given - // filename may appear in multiple packages. - for _, f := range info.Files { - thisFile := iprog.Fset.File(f.Pos()) - if !sameFile(thisFile.Name(), spec.filename) { - continue - } - // This package contains the query file. - - if spec.offset != 0 { - // Search for a specific ident by file/offset. - id := identAtOffset(iprog.Fset, f, spec.offset) - if id == nil { - // can't happen? - return nil, fmt.Errorf("identifier not found") - } - obj := info.Uses[id] - if obj == nil { - obj = info.Defs[id] - if obj == nil { - // Ident without Object. - - // Package clause? - pos := thisFile.Pos(spec.offset) - _, path, _ := iprog.PathEnclosingInterval(pos, pos) - if len(path) == 2 { // [Ident File] - // TODO(adonovan): support this case. - return nil, fmt.Errorf("cannot rename %q: renaming package clauses is not yet supported", - path[1].(*ast.File).Name.Name) - } - - // Implicit y in "switch y := x.(type) {"? - if obj := typeSwitchVar(&info.Info, path); obj != nil { - return []types.Object{obj}, nil - } - - // Probably a type error. - return nil, fmt.Errorf("cannot find object for %q", id.Name) - } - } - if obj.Pkg() == nil { - return nil, fmt.Errorf("cannot rename predeclared identifiers (%s)", obj) - - } - - fromObjects = append(fromObjects, obj) - } else { - // do a package-wide query - objects, err := findObjects(info, spec) - if err != nil { - return nil, err - } - - // filter results: only objects defined in thisFile - var filtered []types.Object - for _, obj := range objects { - if iprog.Fset.File(obj.Pos()) == thisFile { - filtered = append(filtered, obj) - } - } - if len(filtered) == 0 { - return nil, fmt.Errorf("no object %q declared in file %s", - spec.fromName, spec.filename) - } else if len(filtered) > 1 { - return nil, ambiguityError(iprog.Fset, filtered) - } - fromObjects = append(fromObjects, filtered[0]) - } - break - } - } - if len(fromObjects) == 0 { - // can't happen? - return nil, fmt.Errorf("file %s was not part of the loaded program", spec.filename) - } - return fromObjects, nil -} - -func typeSwitchVar(info *types.Info, path []ast.Node) types.Object { - if len(path) > 3 { - // [Ident AssignStmt TypeSwitchStmt...] - if sw, ok := path[2].(*ast.TypeSwitchStmt); ok { - // choose the first case. - if len(sw.Body.List) > 0 { - obj := info.Implicits[sw.Body.List[0].(*ast.CaseClause)] - if obj != nil { - return obj - } - } - } - } - return nil -} - -// On success, findObjects returns the list of objects named -// spec.fromName matching the spec. On success, the result has exactly -// one element unless spec.searchFor!="", in which case it has at least one -// element. -// -func findObjects(info *loader.PackageInfo, spec *spec) ([]types.Object, error) { - if spec.pkgMember == "" { - if spec.searchFor == "" { - panic(spec) - } - objects := searchDefs(&info.Info, spec.searchFor) - if objects == nil { - return nil, fmt.Errorf("no object %q declared in package %q", - spec.searchFor, info.Pkg.Path()) - } - return objects, nil - } - - pkgMember := info.Pkg.Scope().Lookup(spec.pkgMember) - if pkgMember == nil { - return nil, fmt.Errorf("package %q has no member %q", - info.Pkg.Path(), spec.pkgMember) - } - - var searchFunc *types.Func - if spec.typeMember == "" { - // package member - if spec.searchFor == "" { - return []types.Object{pkgMember}, nil - } - - // Search within pkgMember, which must be a function. - searchFunc, _ = pkgMember.(*types.Func) - if searchFunc == nil { - return nil, fmt.Errorf("cannot search for %q within %s %q", - spec.searchFor, objectKind(pkgMember), pkgMember) - } - } else { - // field/method of type - // e.g. (encoding/json.Decoder).Decode - // or ::x within it. - - tName, _ := pkgMember.(*types.TypeName) - if tName == nil { - return nil, fmt.Errorf("%s.%s is a %s, not a type", - info.Pkg.Path(), pkgMember.Name(), objectKind(pkgMember)) - } - - // search within named type. - obj, _, _ := types.LookupFieldOrMethod(tName.Type(), true, info.Pkg, spec.typeMember) - if obj == nil { - return nil, fmt.Errorf("cannot find field or method %q of %s %s.%s", - spec.typeMember, typeKind(tName.Type()), info.Pkg.Path(), tName.Name()) - } - - if spec.searchFor == "" { - // If it is an embedded field, return the type of the field. - if v, ok := obj.(*types.Var); ok && v.Anonymous() { - switch t := v.Type().(type) { - case *types.Pointer: - return []types.Object{t.Elem().(*types.Named).Obj()}, nil - case *types.Named: - return []types.Object{t.Obj()}, nil - } - } - return []types.Object{obj}, nil - } - - searchFunc, _ = obj.(*types.Func) - if searchFunc == nil { - return nil, fmt.Errorf("cannot search for local name %q within %s (%s.%s).%s; need a function", - spec.searchFor, objectKind(obj), info.Pkg.Path(), tName.Name(), - obj.Name()) - } - if isInterface(tName.Type()) { - return nil, fmt.Errorf("cannot search for local name %q within abstract method (%s.%s).%s", - spec.searchFor, info.Pkg.Path(), tName.Name(), searchFunc.Name()) - } - } - - // -- search within function or method -- - - decl := funcDecl(info, searchFunc) - if decl == nil { - return nil, fmt.Errorf("cannot find syntax for %s", searchFunc) // can't happen? - } - - var objects []types.Object - for _, obj := range searchDefs(&info.Info, spec.searchFor) { - // We use positions, not scopes, to determine whether - // the obj is within searchFunc. This is clumsy, but the - // alternative, using the types.Scope tree, doesn't - // account for non-lexical objects like fields and - // interface methods. - if decl.Pos() <= obj.Pos() && obj.Pos() < decl.End() && obj != searchFunc { - objects = append(objects, obj) - } - } - if objects == nil { - return nil, fmt.Errorf("no local definition of %q within %s", - spec.searchFor, searchFunc) - } - return objects, nil -} - -func funcDecl(info *loader.PackageInfo, fn *types.Func) *ast.FuncDecl { - for _, f := range info.Files { - for _, d := range f.Decls { - if d, ok := d.(*ast.FuncDecl); ok && info.Defs[d.Name] == fn { - return d - } - } - } - return nil -} - -func searchDefs(info *types.Info, name string) []types.Object { - var objects []types.Object - for id, obj := range info.Defs { - if obj == nil { - // e.g. blank ident. - // TODO(adonovan): but also implicit y in - // switch y := x.(type) - // Needs some thought. - continue - } - if id.Name == name { - objects = append(objects, obj) - } - } - return objects -} - -func identAtOffset(fset *token.FileSet, f *ast.File, offset int) *ast.Ident { - var found *ast.Ident - ast.Inspect(f, func(n ast.Node) bool { - if id, ok := n.(*ast.Ident); ok { - idpos := fset.Position(id.Pos()).Offset - if idpos <= offset && offset < idpos+len(id.Name) { - found = id - } - } - return found == nil // keep traversing only until found - }) - return found -} - -// ambiguityError returns an error describing an ambiguous "*" scope query. -func ambiguityError(fset *token.FileSet, objects []types.Object) error { - var buf bytes.Buffer - for i, obj := range objects { - if i > 0 { - buf.WriteString(", ") - } - posn := fset.Position(obj.Pos()) - fmt.Fprintf(&buf, "%s at %s:%d", - objectKind(obj), filepath.Base(posn.Filename), posn.Column) - } - return fmt.Errorf("ambiguous specifier %s matches %s", - objects[0].Name(), buf.String()) -} diff --git a/refactor/rename/util14.go b/refactor/rename/util14.go deleted file mode 100644 index 126d3eec13..0000000000 --- a/refactor/rename/util14.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2014 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. - -// +build !go1.5 - -package rename - -import ( - "go/ast" - "os" - "path/filepath" - "reflect" - "runtime" - "strings" - "unicode" - - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/types" -) - -func objectKind(obj types.Object) string { - switch obj := obj.(type) { - case *types.PkgName: - return "imported package name" - case *types.TypeName: - return "type" - case *types.Var: - if obj.IsField() { - return "field" - } - case *types.Func: - if obj.Type().(*types.Signature).Recv() != nil { - return "method" - } - } - // label, func, var, const - return strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types.")) -} - -func typeKind(T types.Type) string { - return strings.ToLower(strings.TrimPrefix(reflect.TypeOf(T.Underlying()).String(), "*types.")) -} - -// NB: for renamings, blank is not considered valid. -func isValidIdentifier(id string) bool { - if id == "" || id == "_" { - return false - } - for i, r := range id { - if !isLetter(r) && (i == 0 || !isDigit(r)) { - return false - } - } - return true -} - -// isLocal reports whether obj is local to some function. -// Precondition: not a struct field or interface method. -func isLocal(obj types.Object) bool { - // [... 5=stmt 4=func 3=file 2=pkg 1=universe] - var depth int - for scope := obj.Parent(); scope != nil; scope = scope.Parent() { - depth++ - } - return depth >= 4 -} - -func isPackageLevel(obj types.Object) bool { - return obj.Pkg().Scope().Lookup(obj.Name()) == obj -} - -// -- Plundered from go/scanner: --------------------------------------- - -func isLetter(ch rune) bool { - return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch) -} - -func isDigit(ch rune) bool { - return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) -} - -// -- Plundered from golang.org/x/tools/oracle ----------------- - -// sameFile returns true if x and y have the same basename and denote -// the same file. -// -func sameFile(x, y string) bool { - if runtime.GOOS == "windows" { - x = filepath.ToSlash(x) - y = filepath.ToSlash(y) - } - if x == y { - return true - } - if filepath.Base(x) == filepath.Base(y) { // (optimisation) - if xi, err := os.Stat(x); err == nil { - if yi, err := os.Stat(y); err == nil { - return os.SameFile(xi, yi) - } - } - } - return false -} - -func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) } diff --git a/refactor/satisfy/find14.go b/refactor/satisfy/find14.go deleted file mode 100644 index 3b8d1e096a..0000000000 --- a/refactor/satisfy/find14.go +++ /dev/null @@ -1,707 +0,0 @@ -// Copyright 2014 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. - -// +build !go1.5 - -// Package satisfy inspects the type-checked ASTs of Go packages and -// reports the set of discovered type constraints of the form (lhs, rhs -// Type) where lhs is a non-trivial interface, rhs satisfies this -// interface, and this fact is necessary for the package to be -// well-typed. -// -// THIS PACKAGE IS EXPERIMENTAL AND MAY CHANGE AT ANY TIME. -// -// It is provided only for the gorename tool. Ideally this -// functionality will become part of the type-checker in due course, -// since it is computing it anyway, and it is robust for ill-typed -// inputs, which this package is not. -// -package satisfy // import "golang.org/x/tools/refactor/satisfy" - -// NOTES: -// -// We don't care about numeric conversions, so we don't descend into -// types or constant expressions. This is unsound because -// constant expressions can contain arbitrary statements, e.g. -// const x = len([1]func(){func() { -// ... -// }}) -// -// TODO(adonovan): make this robust against ill-typed input. -// Or move it into the type-checker. -// -// Assignability conversions are possible in the following places: -// - in assignments y = x, y := x, var y = x. -// - from call argument types to formal parameter types -// - in append and delete calls -// - from return operands to result parameter types -// - in composite literal T{k:v}, from k and v to T's field/element/key type -// - in map[key] from key to the map's key type -// - in comparisons x==y and switch x { case y: }. -// - in explicit conversions T(x) -// - in sends ch <- x, from x to the channel element type -// - in type assertions x.(T) and switch x.(type) { case T: } -// -// The results of this pass provide information equivalent to the -// ssa.MakeInterface and ssa.ChangeInterface instructions. - -import ( - "fmt" - "go/ast" - "go/token" - - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/types" - "golang.org/x/tools/go/types/typeutil" -) - -// A Constraint records the fact that the RHS type does and must -// satisify the LHS type, which is an interface. -// The names are suggestive of an assignment statement LHS = RHS. -type Constraint struct { - LHS, RHS types.Type -} - -// A Finder inspects the type-checked ASTs of Go packages and -// accumulates the set of type constraints (x, y) such that x is -// assignable to y, y is an interface, and both x and y have methods. -// -// In other words, it returns the subset of the "implements" relation -// that is checked during compilation of a package. Refactoring tools -// will need to preserve at least this part of the relation to ensure -// continued compilation. -// -type Finder struct { - Result map[Constraint]bool - msetcache typeutil.MethodSetCache - - // per-Find state - info *types.Info - sig *types.Signature -} - -// Find inspects a single package, populating Result with its pairs of -// constrained types. -// -// The result is non-canonical and thus may contain duplicates (but this -// tends to preserves names of interface types better). -// -// The package must be free of type errors, and -// info.{Defs,Uses,Selections,Types} must have been populated by the -// type-checker. -// -func (f *Finder) Find(info *types.Info, files []*ast.File) { - if f.Result == nil { - f.Result = make(map[Constraint]bool) - } - - f.info = info - for _, file := range files { - for _, d := range file.Decls { - switch d := d.(type) { - case *ast.GenDecl: - if d.Tok == token.VAR { // ignore consts - for _, spec := range d.Specs { - f.valueSpec(spec.(*ast.ValueSpec)) - } - } - - case *ast.FuncDecl: - if d.Body != nil { - f.sig = f.info.Defs[d.Name].Type().(*types.Signature) - f.stmt(d.Body) - f.sig = nil - } - } - } - } - f.info = nil -} - -var ( - tInvalid = types.Typ[types.Invalid] - tUntypedBool = types.Typ[types.UntypedBool] - tUntypedNil = types.Typ[types.UntypedNil] -) - -// exprN visits an expression in a multi-value context. -func (f *Finder) exprN(e ast.Expr) types.Type { - typ := f.info.Types[e].Type.(*types.Tuple) - switch e := e.(type) { - case *ast.ParenExpr: - return f.exprN(e.X) - - case *ast.CallExpr: - // x, err := f(args) - sig := f.expr(e.Fun).Underlying().(*types.Signature) - f.call(sig, e.Args) - - case *ast.IndexExpr: - // y, ok := x[i] - x := f.expr(e.X) - f.assign(f.expr(e.Index), x.Underlying().(*types.Map).Key()) - - case *ast.TypeAssertExpr: - // y, ok := x.(T) - f.typeAssert(f.expr(e.X), typ.At(0).Type()) - - case *ast.UnaryExpr: // must be receive <- - // y, ok := <-x - f.expr(e.X) - - default: - panic(e) - } - return typ -} - -func (f *Finder) call(sig *types.Signature, args []ast.Expr) { - if len(args) == 0 { - return - } - - // Ellipsis call? e.g. f(x, y, z...) - if _, ok := args[len(args)-1].(*ast.Ellipsis); ok { - for i, arg := range args { - // The final arg is a slice, and so is the final param. - f.assign(sig.Params().At(i).Type(), f.expr(arg)) - } - return - } - - var argtypes []types.Type - - // Gather the effective actual parameter types. - if tuple, ok := f.info.Types[args[0]].Type.(*types.Tuple); ok { - // f(g()) call where g has multiple results? - f.expr(args[0]) - // unpack the tuple - for i := 0; i < tuple.Len(); i++ { - argtypes = append(argtypes, tuple.At(i).Type()) - } - } else { - for _, arg := range args { - argtypes = append(argtypes, f.expr(arg)) - } - } - - // Assign the actuals to the formals. - if !sig.Variadic() { - for i, argtype := range argtypes { - f.assign(sig.Params().At(i).Type(), argtype) - } - } else { - // The first n-1 parameters are assigned normally. - nnormals := sig.Params().Len() - 1 - for i, argtype := range argtypes[:nnormals] { - f.assign(sig.Params().At(i).Type(), argtype) - } - // Remaining args are assigned to elements of varargs slice. - tElem := sig.Params().At(nnormals).Type().(*types.Slice).Elem() - for i := nnormals; i < len(argtypes); i++ { - f.assign(tElem, argtypes[i]) - } - } -} - -func (f *Finder) builtin(obj *types.Builtin, sig *types.Signature, args []ast.Expr, T types.Type) types.Type { - switch obj.Name() { - case "make", "new": - // skip the type operand - for _, arg := range args[1:] { - f.expr(arg) - } - - case "append": - s := f.expr(args[0]) - if _, ok := args[len(args)-1].(*ast.Ellipsis); ok && len(args) == 2 { - // append(x, y...) including append([]byte, "foo"...) - f.expr(args[1]) - } else { - // append(x, y, z) - tElem := s.Underlying().(*types.Slice).Elem() - for _, arg := range args[1:] { - f.assign(tElem, f.expr(arg)) - } - } - - case "delete": - m := f.expr(args[0]) - k := f.expr(args[1]) - f.assign(m.Underlying().(*types.Map).Key(), k) - - default: - // ordinary call - f.call(sig, args) - } - - return T -} - -func (f *Finder) extract(tuple types.Type, i int) types.Type { - if tuple, ok := tuple.(*types.Tuple); ok && i < tuple.Len() { - return tuple.At(i).Type() - } - return tInvalid -} - -func (f *Finder) valueSpec(spec *ast.ValueSpec) { - var T types.Type - if spec.Type != nil { - T = f.info.Types[spec.Type].Type - } - switch len(spec.Values) { - case len(spec.Names): // e.g. var x, y = f(), g() - for _, value := range spec.Values { - v := f.expr(value) - if T != nil { - f.assign(T, v) - } - } - - case 1: // e.g. var x, y = f() - tuple := f.exprN(spec.Values[0]) - for i := range spec.Names { - if T != nil { - f.assign(T, f.extract(tuple, i)) - } - } - } -} - -// assign records pairs of distinct types that are related by -// assignability, where the left-hand side is an interface and both -// sides have methods. -// -// It should be called for all assignability checks, type assertions, -// explicit conversions and comparisons between two types, unless the -// types are uninteresting (e.g. lhs is a concrete type, or the empty -// interface; rhs has no methods). -// -func (f *Finder) assign(lhs, rhs types.Type) { - if types.Identical(lhs, rhs) { - return - } - if !isInterface(lhs) { - return - } - - if f.msetcache.MethodSet(lhs).Len() == 0 { - return - } - if f.msetcache.MethodSet(rhs).Len() == 0 { - return - } - // record the pair - f.Result[Constraint{lhs, rhs}] = true -} - -// typeAssert must be called for each type assertion x.(T) where x has -// interface type I. -func (f *Finder) typeAssert(I, T types.Type) { - // Type assertions are slightly subtle, because they are allowed - // to be "impossible", e.g. - // - // var x interface{f()} - // _ = x.(interface{f()int}) // legal - // - // (In hindsight, the language spec should probably not have - // allowed this, but it's too late to fix now.) - // - // This means that a type assert from I to T isn't exactly a - // constraint that T is assignable to I, but for a refactoring - // tool it is a conditional constraint that, if T is assignable - // to I before a refactoring, it should remain so after. - - if types.AssignableTo(T, I) { - f.assign(I, T) - } -} - -// compare must be called for each comparison x==y. -func (f *Finder) compare(x, y types.Type) { - if types.AssignableTo(x, y) { - f.assign(y, x) - } else if types.AssignableTo(y, x) { - f.assign(x, y) - } -} - -// expr visits a true expression (not a type or defining ident) -// and returns its type. -func (f *Finder) expr(e ast.Expr) types.Type { - tv := f.info.Types[e] - if tv.Value != nil { - return tv.Type // prune the descent for constants - } - - // tv.Type may be nil for an ast.Ident. - - switch e := e.(type) { - case *ast.BadExpr, *ast.BasicLit: - // no-op - - case *ast.Ident: - // (referring idents only) - if obj, ok := f.info.Uses[e]; ok { - return obj.Type() - } - if e.Name == "_" { // e.g. "for _ = range x" - return tInvalid - } - panic("undefined ident: " + e.Name) - - case *ast.Ellipsis: - if e.Elt != nil { - f.expr(e.Elt) - } - - case *ast.FuncLit: - saved := f.sig - f.sig = tv.Type.(*types.Signature) - f.stmt(e.Body) - f.sig = saved - - case *ast.CompositeLit: - switch T := deref(tv.Type).Underlying().(type) { - case *types.Struct: - for i, elem := range e.Elts { - if kv, ok := elem.(*ast.KeyValueExpr); ok { - f.assign(f.info.Uses[kv.Key.(*ast.Ident)].Type(), f.expr(kv.Value)) - } else { - f.assign(T.Field(i).Type(), f.expr(elem)) - } - } - - case *types.Map: - for _, elem := range e.Elts { - elem := elem.(*ast.KeyValueExpr) - f.assign(T.Key(), f.expr(elem.Key)) - f.assign(T.Elem(), f.expr(elem.Value)) - } - - case *types.Array, *types.Slice: - tElem := T.(interface { - Elem() types.Type - }).Elem() - for _, elem := range e.Elts { - if kv, ok := elem.(*ast.KeyValueExpr); ok { - // ignore the key - f.assign(tElem, f.expr(kv.Value)) - } else { - f.assign(tElem, f.expr(elem)) - } - } - - default: - panic("unexpected composite literal type: " + tv.Type.String()) - } - - case *ast.ParenExpr: - f.expr(e.X) - - case *ast.SelectorExpr: - if _, ok := f.info.Selections[e]; ok { - f.expr(e.X) // selection - } else { - return f.info.Uses[e.Sel].Type() // qualified identifier - } - - case *ast.IndexExpr: - x := f.expr(e.X) - i := f.expr(e.Index) - if ux, ok := x.Underlying().(*types.Map); ok { - f.assign(ux.Key(), i) - } - - case *ast.SliceExpr: - f.expr(e.X) - if e.Low != nil { - f.expr(e.Low) - } - if e.High != nil { - f.expr(e.High) - } - if e.Max != nil { - f.expr(e.Max) - } - - case *ast.TypeAssertExpr: - x := f.expr(e.X) - f.typeAssert(x, f.info.Types[e.Type].Type) - - case *ast.CallExpr: - if tvFun := f.info.Types[e.Fun]; tvFun.IsType() { - // conversion - arg0 := f.expr(e.Args[0]) - f.assign(tvFun.Type, arg0) - } else { - // function call - if id, ok := unparen(e.Fun).(*ast.Ident); ok { - if obj, ok := f.info.Uses[id].(*types.Builtin); ok { - sig := f.info.Types[id].Type.(*types.Signature) - return f.builtin(obj, sig, e.Args, tv.Type) - } - } - // ordinary call - f.call(f.expr(e.Fun).Underlying().(*types.Signature), e.Args) - } - - case *ast.StarExpr: - f.expr(e.X) - - case *ast.UnaryExpr: - f.expr(e.X) - - case *ast.BinaryExpr: - x := f.expr(e.X) - y := f.expr(e.Y) - if e.Op == token.EQL || e.Op == token.NEQ { - f.compare(x, y) - } - - case *ast.KeyValueExpr: - f.expr(e.Key) - f.expr(e.Value) - - case *ast.ArrayType, - *ast.StructType, - *ast.FuncType, - *ast.InterfaceType, - *ast.MapType, - *ast.ChanType: - panic(e) - } - - if tv.Type == nil { - panic(fmt.Sprintf("no type for %T", e)) - } - - return tv.Type -} - -func (f *Finder) stmt(s ast.Stmt) { - switch s := s.(type) { - case *ast.BadStmt, - *ast.EmptyStmt, - *ast.BranchStmt: - // no-op - - case *ast.DeclStmt: - d := s.Decl.(*ast.GenDecl) - if d.Tok == token.VAR { // ignore consts - for _, spec := range d.Specs { - f.valueSpec(spec.(*ast.ValueSpec)) - } - } - - case *ast.LabeledStmt: - f.stmt(s.Stmt) - - case *ast.ExprStmt: - f.expr(s.X) - - case *ast.SendStmt: - ch := f.expr(s.Chan) - val := f.expr(s.Value) - f.assign(ch.Underlying().(*types.Chan).Elem(), val) - - case *ast.IncDecStmt: - f.expr(s.X) - - case *ast.AssignStmt: - switch s.Tok { - case token.ASSIGN, token.DEFINE: - // y := x or y = x - var rhsTuple types.Type - if len(s.Lhs) != len(s.Rhs) { - rhsTuple = f.exprN(s.Rhs[0]) - } - for i := range s.Lhs { - var lhs, rhs types.Type - if rhsTuple == nil { - rhs = f.expr(s.Rhs[i]) // 1:1 assignment - } else { - rhs = f.extract(rhsTuple, i) // n:1 assignment - } - - if id, ok := s.Lhs[i].(*ast.Ident); ok { - if id.Name != "_" { - if obj, ok := f.info.Defs[id]; ok { - lhs = obj.Type() // definition - } - } - } - if lhs == nil { - lhs = f.expr(s.Lhs[i]) // assignment - } - f.assign(lhs, rhs) - } - - default: - // y op= x - f.expr(s.Lhs[0]) - f.expr(s.Rhs[0]) - } - - case *ast.GoStmt: - f.expr(s.Call) - - case *ast.DeferStmt: - f.expr(s.Call) - - case *ast.ReturnStmt: - formals := f.sig.Results() - switch len(s.Results) { - case formals.Len(): // 1:1 - for i, result := range s.Results { - f.assign(formals.At(i).Type(), f.expr(result)) - } - - case 1: // n:1 - tuple := f.exprN(s.Results[0]) - for i := 0; i < formals.Len(); i++ { - f.assign(formals.At(i).Type(), f.extract(tuple, i)) - } - } - - case *ast.SelectStmt: - f.stmt(s.Body) - - case *ast.BlockStmt: - for _, s := range s.List { - f.stmt(s) - } - - case *ast.IfStmt: - if s.Init != nil { - f.stmt(s.Init) - } - f.expr(s.Cond) - f.stmt(s.Body) - if s.Else != nil { - f.stmt(s.Else) - } - - case *ast.SwitchStmt: - if s.Init != nil { - f.stmt(s.Init) - } - var tag types.Type = tUntypedBool - if s.Tag != nil { - tag = f.expr(s.Tag) - } - for _, cc := range s.Body.List { - cc := cc.(*ast.CaseClause) - for _, cond := range cc.List { - f.compare(tag, f.info.Types[cond].Type) - } - for _, s := range cc.Body { - f.stmt(s) - } - } - - case *ast.TypeSwitchStmt: - if s.Init != nil { - f.stmt(s.Init) - } - var I types.Type - switch ass := s.Assign.(type) { - case *ast.ExprStmt: // x.(type) - I = f.expr(unparen(ass.X).(*ast.TypeAssertExpr).X) - case *ast.AssignStmt: // y := x.(type) - I = f.expr(unparen(ass.Rhs[0]).(*ast.TypeAssertExpr).X) - } - for _, cc := range s.Body.List { - cc := cc.(*ast.CaseClause) - for _, cond := range cc.List { - tCase := f.info.Types[cond].Type - if tCase != tUntypedNil { - f.typeAssert(I, tCase) - } - } - for _, s := range cc.Body { - f.stmt(s) - } - } - - case *ast.CommClause: - if s.Comm != nil { - f.stmt(s.Comm) - } - for _, s := range s.Body { - f.stmt(s) - } - - case *ast.ForStmt: - if s.Init != nil { - f.stmt(s.Init) - } - if s.Cond != nil { - f.expr(s.Cond) - } - if s.Post != nil { - f.stmt(s.Post) - } - f.stmt(s.Body) - - case *ast.RangeStmt: - x := f.expr(s.X) - // No conversions are involved when Tok==DEFINE. - if s.Tok == token.ASSIGN { - if s.Key != nil { - k := f.expr(s.Key) - var xelem types.Type - // keys of array, *array, slice, string aren't interesting - switch ux := x.Underlying().(type) { - case *types.Chan: - xelem = ux.Elem() - case *types.Map: - xelem = ux.Key() - } - if xelem != nil { - f.assign(xelem, k) - } - } - if s.Value != nil { - val := f.expr(s.Value) - var xelem types.Type - // values of strings aren't interesting - switch ux := x.Underlying().(type) { - case *types.Array: - xelem = ux.Elem() - case *types.Chan: - xelem = ux.Elem() - case *types.Map: - xelem = ux.Elem() - case *types.Pointer: // *array - xelem = deref(ux).(*types.Array).Elem() - case *types.Slice: - xelem = ux.Elem() - } - if xelem != nil { - f.assign(xelem, val) - } - } - } - f.stmt(s.Body) - - default: - panic(s) - } -} - -// -- Plundered from golang.org/x/tools/go/ssa ----------------- - -// deref returns a pointer's element type; otherwise it returns typ. -func deref(typ types.Type) types.Type { - if p, ok := typ.Underlying().(*types.Pointer); ok { - return p.Elem() - } - return typ -} - -func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) } - -func isInterface(T types.Type) bool { return types.IsInterface(T) }