2013-12-13 08:04:55 -07:00
|
|
|
// 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"
|
|
|
|
|
2014-11-09 14:50:40 -07:00
|
|
|
"golang.org/x/tools/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"
|
2013-12-13 08:04:55 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
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 11:10:26 -06:00
|
|
|
// Reject non-pointerlike types (includes all constants---except nil).
|
|
|
|
// TODO(adonovan): reject nil too.
|
2013-12-13 08:04:55 -07:00
|
|
|
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.
|
|
|
|
//
|
2014-01-16 07:33:58 -07:00
|
|
|
func ssaValueForIdent(prog *ssa.Program, qinfo *loader.PackageInfo, obj types.Object, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
|
2013-12-13 08:04:55 -07:00
|
|
|
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:
|
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 11:10:26 -06:00
|
|
|
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
|
2013-12-13 08:04:55 -07:00
|
|
|
}
|
|
|
|
panic(obj)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ssaValueForExpr returns the ssa.Value of the non-ast.Ident
|
|
|
|
// expression whose path to the root of the AST is path.
|
|
|
|
//
|
2014-01-16 07:33:58 -07:00
|
|
|
func ssaValueForExpr(prog *ssa.Program, qinfo *loader.PackageInfo, path []ast.Node) (value ssa.Value, isAddr bool, err error) {
|
2013-12-13 08:04:55 -07:00
|
|
|
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)
|
|
|
|
|
2014-07-22 16:29:56 -06:00
|
|
|
T := v.Type()
|
2013-12-13 08:04:55 -07:00
|
|
|
if isAddr {
|
|
|
|
o.ptaConfig.AddIndirectQuery(v)
|
2014-07-22 16:29:56 -06:00
|
|
|
T = deref(T)
|
2013-12-13 08:04:55 -07:00
|
|
|
} else {
|
|
|
|
o.ptaConfig.AddQuery(v)
|
|
|
|
}
|
|
|
|
ptares := ptrAnalysis(o)
|
|
|
|
|
2014-02-20 09:35:09 -07:00
|
|
|
var ptr pointer.Pointer
|
2013-12-13 08:04:55 -07:00
|
|
|
if isAddr {
|
2014-02-20 09:35:09 -07:00
|
|
|
ptr = ptares.IndirectQueries[v]
|
2013-12-13 08:04:55 -07:00
|
|
|
} else {
|
2014-02-20 09:35:09 -07:00
|
|
|
ptr = ptares.Queries[v]
|
2013-12-13 08:04:55 -07:00
|
|
|
}
|
2014-02-20 09:35:09 -07:00
|
|
|
if ptr == (pointer.Pointer{}) {
|
2013-12-13 08:04:55 -07:00
|
|
|
return nil, fmt.Errorf("pointer analysis did not find expression (dead code?)")
|
|
|
|
}
|
2014-02-20 09:35:09 -07:00
|
|
|
pts := ptr.PointsTo()
|
2013-12-13 08:04:55 -07:00
|
|
|
|
2014-07-22 16:29:56 -06:00
|
|
|
if pointer.CanHaveDynamicTypes(T) {
|
2013-12-13 08:04:55 -07:00
|
|
|
// Show concrete types for interface/reflect.Value expression.
|
|
|
|
if concs := pts.DynamicTypes(); concs.Len() > 0 {
|
|
|
|
concs.Iterate(func(conc types.Type, pta interface{}) {
|
2014-02-20 09:35:09 -07:00
|
|
|
labels := pta.(pointer.PointsToSet).Labels()
|
2013-12-13 08:04:55 -07:00
|
|
|
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
|
2014-07-22 16:29:56 -06:00
|
|
|
ptrs = append(ptrs, pointerResult{T, labels})
|
2013-12-13 08:04:55 -07:00
|
|
|
}
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|