1
0
mirror of https://github.com/golang/go synced 2024-11-19 02:54:42 -07:00
go/oracle/pointsto.go
Alan Donovan fec252214b go.tools/ssa: create thunks for method expressions T.f.
Until now, the same Function was used to represent a method
(T)func() and the "method expression" function func(T) formed
from it. So the SSA code for this:

    var buf bytes.Buffer
    f := Buffer.Bytes
    f(buf)
    buf.Bytes()

would involve an implicit cast (ChangeType) on line 2.
However, compilers based on go/ssa may want to use different
calling conventions for them, like gccgo does (see issue
7839).  This change decouples them by using an anonymous
function called a "thunk", rather like this:

    f := func(r *bytes.Buffer) []byte { return r.Bytes() }

Thunks are similar to method wrappers; both are created by
makeWrapper.

"Interface method wrappers" were a special case of thunks for
direct calls (no indirection/fields) of interface methods.
They are now subsumed by thunks and have been deleted.  Now
that only the needed thunks are built, we don't need to
populate the concrete method sets of interface types at all,
so (*Program).Method and LookupMethod return nil for them.
This results in a slight reduction in function count (>1%) and
instruction count (<<1%).

Details:

go/ssa:
- API: ChangeType no longer supports func/method conversions.
- API: (*Program).FuncValue now returns nil for abstract
  (interface) methods.
- API: (*Function).RelString simplified.
  "$bound" is now a suffix not a prefix, and the receiver
  type is rendered package-relative.
- API: Function.Object is now defined for all wrappers too.
- API: (*Program).Method and LookupMethod return nil for
  abstract methods.
- emitConv no longer permits (non-identical)
  Signature->Signature conversions.  Added assertion.
- add and use isInterface helper
- sanity: we check packages after Build, not Create, otherwise
  cross-package refs might fail.

go/pointer:
- update tests for new function strings.
- pointer_test: don't add non-pointerlike probes to analysis.
  (The error was checked, but too late, causing a panic.)
- fixed a minor bug: if a test probe print(x) was the sole
  reference to x, no nodes were generated for x.
- (reflect.Type).MethodByName: updated due to ssa API changes.
  Also, fixed incorrect testdata/funcreflect.go expectation
  for MethodByName on interfaces.

oracle:
- fix for new FuncValue semantics.
- a "pointsto" query on an I.f thunk now returns an error.

Fixes golang/go#7839

LGTM=gri
R=gri
CC=golang-codereviews, pcc
https://golang.org/cl/93780044
2014-06-11 13:10:26 -04:00

263 lines
7.6 KiB
Go

// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package oracle
import (
"fmt"
"go/ast"
"go/token"
"sort"
"code.google.com/p/go.tools/astutil"
"code.google.com/p/go.tools/go/loader"
"code.google.com/p/go.tools/go/pointer"
"code.google.com/p/go.tools/go/ssa"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.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(o *Oracle, qpos *QueryPos) (queryResult, error) {
path, action := findInterestingNode(qpos.info, qpos.path)
if action != actionExpr {
return nil, 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 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)
}
// Reject non-pointerlike types (includes all constants---except nil).
// TODO(adonovan): reject nil too.
typ := qpos.info.TypeOf(expr)
if !pointer.CanPoint(typ) {
return nil, 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
var err error
if obj != nil {
// def/ref of func/var object
value, isAddr, err = ssaValueForIdent(o.prog, qpos.info, obj, path)
} else {
value, isAddr, err = ssaValueForExpr(o.prog, qpos.info, path)
}
if err != nil {
return nil, err // e.g. trivially dead code
}
// Run the pointer analysis.
ptrs, err := runPTA(o, value, isAddr)
if err != nil {
return nil, err // e.g. analytically unreachable
}
return &pointstoResult{
qpos: qpos,
typ: typ,
ptrs: ptrs,
}, 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(o *Oracle, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) {
buildSSA(o)
if isAddr {
o.ptaConfig.AddIndirectQuery(v)
} else {
o.ptaConfig.AddQuery(v)
}
ptares := ptrAnalysis(o)
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(v.Type()) {
// 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{v.Type(), 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)
}
}