go.tools/pointer: make sole callsite available to intrinsics in non-shared contours.

This information can be used to specialize such calls, e.g.
- report location of unsound calls (done for reflect.NewAt)
- exploit argument information (done for constant 'dir' parameter to reflect.ChanOf)

+ tests.

R=crawshaw
CC=golang-dev
https://golang.org/cl/14517046
This commit is contained in:
Alan Donovan 2013-10-09 12:41:55 -04:00
parent cd908f1108
commit 2299ac6bf3
6 changed files with 83 additions and 67 deletions

View File

@ -34,9 +34,10 @@ func (g *cgraph) Root() call.GraphNode {
// cgnode implements call.GraphNode.
type cgnode struct {
fn *ssa.Function
obj nodeid // start of this contour's object block
sites []*callsite // ordered list of callsites within this function
fn *ssa.Function
obj nodeid // start of this contour's object block
sites []*callsite // ordered list of callsites within this function
callersite *callsite // where called from, if known; nil for shared contours
}
func (n *cgnode) Func() *ssa.Function {

View File

@ -115,18 +115,21 @@ func (a *analysis) endObject(obj nodeid, cgn *cgnode, data interface{}) *object
return o
}
// makeFunctionObject creates and returns a new function object for
// fn, and returns the id of its first node. It also enqueues fn for
// subsequent constraint generation.
// makeFunctionObject creates and returns a new function object
// (contour) for fn, and returns the id of its first node. It also
// enqueues fn for subsequent constraint generation.
//
func (a *analysis) makeFunctionObject(fn *ssa.Function) nodeid {
// For a context-sensitive contour, callersite identifies the sole
// callsite; for shared contours, caller is nil.
//
func (a *analysis) makeFunctionObject(fn *ssa.Function, callersite *callsite) nodeid {
if a.log != nil {
fmt.Fprintf(a.log, "\t---- makeFunctionObject %s\n", fn)
}
// obj is the function object (identity, params, results).
obj := a.nextNode()
cgn := a.makeCGNode(fn, obj)
cgn := a.makeCGNode(fn, obj, callersite)
sig := fn.Signature
a.addOneNode(sig, "func.cgnode", nil) // (scalar with Signature type)
if recv := sig.Recv(); recv != nil {
@ -568,18 +571,15 @@ func (a *analysis) shouldUseContext(fn *ssa.Function) bool {
return true
}
// genStaticCall generates constraints for a statically dispatched
// function call. It returns a node whose pts() will be the set of
// possible call targets (in this case, a singleton).
//
func (a *analysis) genStaticCall(call *ssa.CallCommon, result nodeid) nodeid {
// genStaticCall generates constraints for a statically dispatched function call.
func (a *analysis) genStaticCall(caller *cgnode, site *callsite, call *ssa.CallCommon, result nodeid) {
// Ascertain the context (contour/CGNode) for a particular call.
var obj nodeid
fn := call.StaticCallee()
if a.shouldUseContext(fn) {
obj = a.makeFunctionObject(fn) // new contour for this call
obj = a.makeFunctionObject(fn, site) // new contour
} else {
obj = a.objectNode(nil, fn) // ordinary (shared) contour
obj = a.objectNode(nil, fn) // shared contour
}
sig := call.Signature()
@ -609,13 +609,12 @@ func (a *analysis) genStaticCall(call *ssa.CallCommon, result nodeid) nodeid {
a.copy(result, a.funcResults(obj), a.sizeof(sig.Results()))
}
return targets
// pts(targets) will be the (singleton) set of possible call targets.
site.targets = targets
}
// genDynamicCall generates constraints for a dynamic function call.
// It returns a node whose pts() will be the set of possible call targets.
//
func (a *analysis) genDynamicCall(caller *cgnode, call *ssa.CallCommon, result nodeid) nodeid {
func (a *analysis) genDynamicCall(caller *cgnode, site *callsite, call *ssa.CallCommon, result nodeid) {
fn := a.valueNode(call.Value)
sig := call.Signature()
@ -632,22 +631,24 @@ func (a *analysis) genDynamicCall(caller *cgnode, call *ssa.CallCommon, result n
if result != 0 {
a.genLoad(caller, result, call.Value, offset, a.sizeof(sig.Results()))
}
return fn
// pts(targets) will be the (singleton) set of possible call targets.
site.targets = fn
}
// genInvoke generates constraints for a dynamic method invocation.
// It returns a node whose pts() will be the set of possible call targets.
//
func (a *analysis) genInvoke(call *ssa.CallCommon, result nodeid) nodeid {
func (a *analysis) genInvoke(caller *cgnode, site *callsite, call *ssa.CallCommon, result nodeid) {
if call.Value.Type() == a.reflectType {
return a.genInvokeReflectType(call, result)
a.genInvokeReflectType(caller, site, call, result)
return
}
sig := call.Signature()
// Allocate a contiguous targets/params/results block for this call.
block := a.nextNode()
targets := a.addOneNode(sig, "invoke.targets", nil)
// pts(targets) will be the set of possible call targets
site.targets = a.addOneNode(sig, "invoke.targets", nil)
p := a.addNodes(sig.Params(), "invoke.params")
r := a.addNodes(sig.Results(), "invoke.results")
@ -666,8 +667,6 @@ func (a *analysis) genInvoke(call *ssa.CallCommon, result nodeid) nodeid {
// edges from the caller's P/R block to the callee's
// P/R block for each discovered call target.
a.addConstraint(&invokeConstraint{call.Method, a.valueNode(call.Value), block})
return targets
}
// genInvokeReflectType is a specialization of genInvoke where the
@ -684,10 +683,7 @@ func (a *analysis) genInvoke(call *ssa.CallCommon, result nodeid) nodeid {
// as this:
// rt.(*reflect.rtype).F()
//
// It returns a node whose pts() will be the (singleton) set of
// possible call targets.
//
func (a *analysis) genInvokeReflectType(call *ssa.CallCommon, result nodeid) nodeid {
func (a *analysis) genInvokeReflectType(caller *cgnode, site *callsite, call *ssa.CallCommon, result nodeid) {
// Unpack receiver into rtype
rtype := a.addOneNode(a.reflectRtypePtr, "rtype.recv", nil)
recv := a.valueNode(call.Value)
@ -697,7 +693,10 @@ func (a *analysis) genInvokeReflectType(call *ssa.CallCommon, result nodeid) nod
meth := a.reflectRtypePtr.MethodSet().Lookup(call.Method.Pkg(), call.Method.Name())
fn := a.prog.Method(meth)
obj := a.makeFunctionObject(fn) // new contour for this call
obj := a.makeFunctionObject(fn, site) // new contour for this call
// pts(targets) will be the (singleton) set of possible call targets.
site.targets = obj
// From now on, it's essentially a static call, but little is
// gained by factoring together the code for both cases.
@ -723,8 +722,6 @@ func (a *analysis) genInvokeReflectType(call *ssa.CallCommon, result nodeid) nod
if result != 0 {
a.copy(result, a.funcResults(obj), a.sizeof(sig.Results()))
}
return obj
}
// genCall generates contraints for call instruction instr.
@ -742,22 +739,19 @@ func (a *analysis) genCall(caller *cgnode, instr ssa.CallInstruction) {
result = a.valueNode(v)
}
// The node whose pts(·) will contain all targets of the call.
var targets nodeid
site := &callsite{instr: instr}
switch {
case call.StaticCallee() != nil:
targets = a.genStaticCall(call, result)
a.genStaticCall(caller, site, call, result)
case call.IsInvoke():
targets = a.genInvoke(call, result)
a.genInvoke(caller, site, call, result)
default:
targets = a.genDynamicCall(caller, call, result)
a.genDynamicCall(caller, site, call, result)
}
site := &callsite{
targets: targets,
instr: instr,
}
caller.sites = append(caller.sites, site)
if a.log != nil {
fmt.Fprintf(a.log, "\t%s to targets %s from %s\n", site, site.targets, caller)
}
@ -791,7 +785,7 @@ func (a *analysis) objectNode(cgn *cgnode, v ssa.Value) nodeid {
a.endObject(obj, nil, v)
case *ssa.Function:
obj = a.makeFunctionObject(v)
obj = a.makeFunctionObject(v, nil)
case *ssa.Const:
if t, ok := v.Type().Underlying().(*types.Slice); ok && !v.IsNil() {
@ -1075,8 +1069,8 @@ func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) {
}
}
func (a *analysis) makeCGNode(fn *ssa.Function, obj nodeid) *cgnode {
cgn := &cgnode{fn: fn, obj: obj}
func (a *analysis) makeCGNode(fn *ssa.Function, obj nodeid, callersite *callsite) *cgnode {
cgn := &cgnode{fn: fn, obj: obj, callersite: callersite}
a.cgnodes = append(a.cgnodes, cgn)
return cgn
}
@ -1090,7 +1084,7 @@ func (a *analysis) genRootCalls() *cgnode {
r.Prog = a.prog // hack.
r.Enclosing = r // hack, so Function.String() doesn't crash
r.String() // (asserts that it doesn't crash)
root := a.makeCGNode(r, 0)
root := a.makeCGNode(r, 0, nil)
// For each main package, call main.init(), main.main().
for _, mainPkg := range a.config.Mains {

View File

@ -21,6 +21,8 @@ import (
// Instances of 'intrinsic' generate analysis constraints for calls to
// intrinsic functions.
// Implementations may exploit information from the calling site
// via cgn.callersite; for shared contours this is nil.
type intrinsic func(a *analysis, cgn *cgnode)
// Initialized in explicit init() to defeat (spurious) initialization

View File

@ -22,8 +22,11 @@ package pointer
import (
"fmt"
"go/ast"
"reflect"
"code.google.com/p/go.tools/go/exact"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/ssa"
)
// -------------------- (reflect.Value) --------------------
@ -362,11 +365,12 @@ func ext۰reflect۰Copy(a *analysis, cgn *cgnode) {}
// ---------- func ChanOf(ChanDir, Type) Type ----------
// result = ChanOf(_, t)
// result = ChanOf(dir, t)
type reflectChanOfConstraint struct {
cgn *cgnode
t nodeid // (ptr)
result nodeid
dirs []ast.ChanDir
}
func (c *reflectChanOfConstraint) String() string {
@ -381,9 +385,8 @@ func (c *reflectChanOfConstraint) solve(a *analysis, _ *node, delta nodeset) {
changed := false
for tObj := range delta {
T := a.rtypeTaggedValue(tObj)
// TODO(adonovan): use only the channel direction
// provided at the callsite, if constant.
for _, dir := range []ast.ChanDir{1, 2, 3} {
for _, dir := range c.dirs {
if a.addLabel(c.result, a.makeRtype(types.NewChan(dir, T))) {
changed = true
}
@ -394,12 +397,34 @@ func (c *reflectChanOfConstraint) solve(a *analysis, _ *node, delta nodeset) {
}
}
// dirMap maps reflect.ChanDir to the set of channel types generated by ChanOf.
var dirMap = [...][]ast.ChanDir{
0: {ast.RECV, ast.SEND, ast.RECV | ast.SEND}, // unknown
reflect.RecvDir: {ast.RECV},
reflect.SendDir: {ast.SEND},
reflect.BothDir: {ast.RECV | ast.SEND},
}
func ext۰reflect۰ChanOf(a *analysis, cgn *cgnode) {
// If we have access to the callsite,
// and the channel argument is a constant (as is usual),
// only generate the requested direction.
var dir reflect.ChanDir // unknown
if site := cgn.callersite; site != nil {
if c, ok := site.instr.Common().Args[0].(*ssa.Const); ok {
v, _ := exact.Int64Val(c.Value)
if 0 <= v && v <= int64(reflect.BothDir) {
dir = reflect.ChanDir(v)
}
}
}
params := a.funcParams(cgn.obj)
a.addConstraint(&reflectChanOfConstraint{
cgn: cgn,
t: params + 1,
result: a.funcResults(cgn.obj),
dirs: dirMap[dir],
})
}
@ -617,10 +642,10 @@ func ext۰reflect۰New(a *analysis, cgn *cgnode) {
func ext۰reflect۰NewAt(a *analysis, cgn *cgnode) {
ext۰reflect۰New(a, cgn)
// TODO(adonovan): make it easier to report errors of this form,
// which includes the callsite:
// a.warnf("unsound: main.reflectNewAt contains a reflect.NewAt() call")
a.warnf(cgn.Func().Pos(), "unsound: reflect.NewAt() call")
// TODO(adonovan): also report dynamic calls to unsound intrinsics.
if site := cgn.callersite; site != nil {
a.warnf(site.pos(), "unsound: %s contains a reflect.NewAt() call", site.instr.Parent())
}
}
func ext۰reflect۰PtrTo(a *analysis, cgn *cgnode) {}

View File

@ -26,29 +26,25 @@ func chanreflect2() {
print(r.Interface().(*int)) // @pointsto main.b
}
// TODO(adonovan): the analysis can't yet take advantage of the
// ChanOf(dir) parameter so the results are less precise than they
// should be: all three directions are returned.
func chanOfRecv() {
// MakeChan(<-chan) is a no-op.
t := reflect.ChanOf(reflect.RecvDir, reflect.TypeOf(&a))
print(reflect.Zero(t).Interface()) // @types <-chan *int | chan<- *int | chan *int
print(reflect.Zero(t).Interface()) // @types <-chan *int
print(reflect.MakeChan(t, 0).Interface().(<-chan *int)) // @pointsto
print(reflect.MakeChan(t, 0).Interface().(chan *int)) // @pointsto <alloc in reflect.MakeChan>
print(reflect.MakeChan(t, 0).Interface().(chan *int)) // @pointsto
}
func chanOfSend() {
// MakeChan(chan<-) is a no-op.
t := reflect.ChanOf(reflect.SendDir, reflect.TypeOf(&a))
print(reflect.Zero(t).Interface()) // @types <-chan *int | chan<- *int | chan *int
print(reflect.Zero(t).Interface()) // @types chan<- *int
print(reflect.MakeChan(t, 0).Interface().(chan<- *int)) // @pointsto
print(reflect.MakeChan(t, 0).Interface().(chan *int)) // @pointsto <alloc in reflect.MakeChan>
print(reflect.MakeChan(t, 0).Interface().(chan *int)) // @pointsto
}
func chanOfBoth() {
t := reflect.ChanOf(reflect.BothDir, reflect.TypeOf(&a))
print(reflect.Zero(t).Interface()) // @types <-chan *int | chan<- *int | chan *int
print(reflect.Zero(t).Interface()) // @types chan *int
ch := reflect.MakeChan(t, 0)
print(ch.Interface().(chan *int)) // @pointsto <alloc in reflect.MakeChan>
ch.Send(reflect.ValueOf(&b))

View File

@ -21,9 +21,7 @@ func reflectNewAt() {
print(reflect.NewAt(reflect.TypeOf(3), unsafe.Pointer(&x)).Interface()) // @types *int
}
// TODO(adonovan): report the location of the caller, not NewAt.
// #warning "unsound: main.reflectNewAt contains a reflect.NewAt.. call"
// @warning "unsound: reflect.NewAt.. call"
// @warning "unsound: main.reflectNewAt contains a reflect.NewAt.. call"
func reflectTypeOf() {
t := reflect.TypeOf(3)