зеркало из https://github.com/golang/tools.git
x/tools: remove remaining files tagged for Go 1.4
(accidentally omitted from https://go-review.googlesource.com/20810) Change-Id: Ib6ee4b2e43b6f32c5c0c031910087cc39d5c5d14 Reviewed-on: https://go-review.googlesource.com/21862 Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Родитель
d601baae9c
Коммит
51487f711d
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 <a> 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, `<a title='%s'`, html.EscapeString(a.title))
|
||||
if a.onclick != "" {
|
||||
fmt.Fprintf(w, ` onclick='%s'`, html.EscapeString(a.onclick))
|
||||
}
|
||||
if a.href != "" {
|
||||
// TODO(adonovan): I think that in principle, a.href must first be
|
||||
// url.QueryEscape'd, but if I do that, a leading slash becomes "%2F",
|
||||
// which causes the browser to treat the path as relative, not absolute.
|
||||
// WTF?
|
||||
fmt.Fprintf(w, ` href='%s'`, html.EscapeString(a.href))
|
||||
}
|
||||
fmt.Fprintf(w, ">")
|
||||
} else {
|
||||
fmt.Fprintf(w, "</a>")
|
||||
}
|
||||
}
|
||||
|
||||
// An <a class='error'> 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) {
|
||||
// <span> causes havoc, not sure why, so use <a>.
|
||||
if start {
|
||||
fmt.Fprintf(w, `<a class='error' title='%s'>`, html.EscapeString(e.msg))
|
||||
} else {
|
||||
fmt.Fprintf(w, "</a>")
|
||||
}
|
||||
}
|
||||
|
||||
// -- 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
|
||||
}
|
|
@ -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 <root>
|
||||
}
|
||||
|
||||
// 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: "<external>"})
|
||||
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) }
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 <conc>" ...
|
||||
// "T is implemented by <iface>"...
|
||||
// "T implements <iface>"...
|
||||
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 <iface>"...
|
||||
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 <iface>"...
|
||||
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
|
||||
}
|
|
@ -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] }
|
|
@ -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
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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] }
|
|
@ -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")
|
||||
}
|
|
@ -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] }
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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 <spec> | -offset <file>:#<byte-offset>) -to <name> [-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
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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) }
|
|
@ -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) }
|
Загрузка…
Ссылка в новой задаче