mirror of
https://github.com/golang/go
synced 2024-11-19 06:54:39 -07:00
7a5597c226
This is a short-term usability measure. Longer term, we need to audit each conversion to decide whether it should be ignored or modelled by an analytic summary. R=crawshaw CC=golang-dev https://golang.org/cl/13263050
1088 lines
30 KiB
Go
1088 lines
30 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 pointer
|
|
|
|
// This file defines the constraint generation phase.
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
|
|
"code.google.com/p/go.tools/go/types"
|
|
"code.google.com/p/go.tools/ssa"
|
|
)
|
|
|
|
var (
|
|
tEface = types.NewInterface(nil)
|
|
tInvalid = types.Typ[types.Invalid]
|
|
tUnsafePtr = types.Typ[types.UnsafePointer]
|
|
)
|
|
|
|
// ---------- Node creation ----------
|
|
|
|
// nextNode returns the index of the next unused node.
|
|
func (a *analysis) nextNode() nodeid {
|
|
return nodeid(len(a.nodes))
|
|
}
|
|
|
|
// addNodes creates nodes for all scalar elements in type typ, and
|
|
// returns the id of the first one, or zero if the type was
|
|
// analytically uninteresting.
|
|
//
|
|
// comment explains the origin of the nodes, as a debugging aid.
|
|
//
|
|
func (a *analysis) addNodes(typ types.Type, comment string) nodeid {
|
|
id := a.nextNode()
|
|
for _, fi := range a.flatten(typ) {
|
|
a.addOneNode(fi.typ, comment, fi)
|
|
}
|
|
if id == a.nextNode() {
|
|
return 0 // type contained no pointers
|
|
}
|
|
return id
|
|
}
|
|
|
|
// addOneNode creates a single node with type typ, and returns its id.
|
|
//
|
|
// typ should generally be scalar (except for interface.conctype nodes
|
|
// and struct/array identity nodes). Use addNodes for non-scalar types.
|
|
//
|
|
// comment explains the origin of the nodes, as a debugging aid.
|
|
// subelement indicates the subelement, e.g. ".a.b[*].c".
|
|
//
|
|
func (a *analysis) addOneNode(typ types.Type, comment string, subelement *fieldInfo) nodeid {
|
|
id := a.nextNode()
|
|
a.nodes = append(a.nodes, &node{typ: typ, subelement: subelement})
|
|
if a.log != nil {
|
|
fmt.Fprintf(a.log, "\tcreate n%d %s for %s%s\n",
|
|
id, typ, comment, subelement.path())
|
|
}
|
|
return id
|
|
}
|
|
|
|
// setValueNode associates node id with the value v.
|
|
// TODO(adonovan): disambiguate v by its CallGraphNode, if it's a local.
|
|
func (a *analysis) setValueNode(v ssa.Value, id nodeid) {
|
|
a.valNode[v] = id
|
|
if a.log != nil {
|
|
fmt.Fprintf(a.log, "\tval[%s] = n%d (%T)\n", v.Name(), id, v)
|
|
}
|
|
|
|
// Record the (v, id) relation if the client has queried v.
|
|
if indirect, ok := a.config.QueryValues[v]; ok {
|
|
if indirect {
|
|
tmp := a.addNodes(v.Type(), "query.indirect")
|
|
a.load(tmp, id, a.sizeof(v.Type()))
|
|
id = tmp
|
|
}
|
|
ptrs := a.config.QueryResults
|
|
if ptrs == nil {
|
|
ptrs = make(map[ssa.Value][]Pointer)
|
|
a.config.QueryResults = ptrs
|
|
}
|
|
ptrs[v] = append(ptrs[v], ptr{a, id})
|
|
}
|
|
}
|
|
|
|
// endObject marks the end of a sequence of calls to addNodes denoting
|
|
// a single object allocation.
|
|
//
|
|
// obj is the start node of the object, from a prior call to nextNode.
|
|
// Its size, flags and (optionally) data will be updated.
|
|
//
|
|
func (a *analysis) endObject(obj nodeid, data ssa.Value) {
|
|
// Ensure object is non-empty by padding;
|
|
// the pad will be the object node.
|
|
size := uint32(a.nextNode() - obj)
|
|
if size == 0 {
|
|
a.addOneNode(tInvalid, "padding", nil)
|
|
}
|
|
objNode := a.nodes[obj]
|
|
objNode.size = size // excludes padding
|
|
objNode.flags = ntObject
|
|
|
|
if data != nil {
|
|
objNode.data = data
|
|
if a.log != nil {
|
|
fmt.Fprintf(a.log, "\tobj[%s] = n%d\n", data, obj)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
func (a *analysis) makeFunctionObject(fn *ssa.Function) 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()
|
|
sig := fn.Signature
|
|
a.addOneNode(sig, "func.cgnode", nil) // (scalar with Signature type)
|
|
if recv := sig.Recv(); recv != nil {
|
|
a.addNodes(recv.Type(), "func.recv")
|
|
}
|
|
a.addNodes(sig.Params(), "func.params")
|
|
a.addNodes(sig.Results(), "func.results")
|
|
a.endObject(obj, fn)
|
|
|
|
if a.log != nil {
|
|
fmt.Fprintf(a.log, "\t----\n")
|
|
}
|
|
|
|
cgn := &cgnode{fn: fn, obj: obj}
|
|
a.nodes[obj].flags |= ntFunction
|
|
a.nodes[obj].data = cgn
|
|
|
|
// Queue it up for constraint processing.
|
|
a.genq = append(a.genq, cgn)
|
|
|
|
return obj
|
|
}
|
|
|
|
// makeFunction creates the shared function object (aka contour) for
|
|
// function fn and returns a 'func' value node that points to it.
|
|
//
|
|
func (a *analysis) makeFunction(fn *ssa.Function) nodeid {
|
|
obj := a.makeFunctionObject(fn)
|
|
a.funcObj[fn] = obj
|
|
|
|
var comment string
|
|
if a.log != nil {
|
|
comment = fn.String()
|
|
}
|
|
id := a.addOneNode(fn.Type(), comment, nil)
|
|
a.addressOf(id, obj)
|
|
return id
|
|
}
|
|
|
|
// makeGlobal creates the value node and object node for global g,
|
|
// and returns the value node.
|
|
//
|
|
// The value node represents the address of the global variable, and
|
|
// points to the object (and nothing else).
|
|
//
|
|
// The object consists of the global variable itself (conceptually,
|
|
// the BSS address).
|
|
//
|
|
func (a *analysis) makeGlobal(g *ssa.Global) nodeid {
|
|
var comment string
|
|
if a.log != nil {
|
|
fmt.Fprintf(a.log, "\t---- makeGlobal %s\n", g)
|
|
comment = g.FullName()
|
|
}
|
|
|
|
// The nodes representing the object itself.
|
|
obj := a.nextNode()
|
|
a.addNodes(mustDeref(g.Type()), "global")
|
|
a.endObject(obj, g)
|
|
|
|
if a.log != nil {
|
|
fmt.Fprintf(a.log, "\t----\n")
|
|
}
|
|
|
|
// The node representing the address of the global.
|
|
id := a.addOneNode(g.Type(), comment, nil)
|
|
a.addressOf(id, obj)
|
|
|
|
return id
|
|
}
|
|
|
|
// makeConstant creates the value node and object node (if needed) for
|
|
// constant c, and returns the value node.
|
|
// An object node is created only for []byte or []rune constants.
|
|
// The value node points to the object node, iff present.
|
|
//
|
|
func (a *analysis) makeConstant(l *ssa.Const) nodeid {
|
|
id := a.addNodes(l.Type(), "const")
|
|
if !l.IsNil() {
|
|
// []byte or []rune?
|
|
if t, ok := l.Type().Underlying().(*types.Slice); ok {
|
|
// Treat []T like *[1]T, 'make []T' like new([1]T).
|
|
obj := a.nextNode()
|
|
a.addNodes(sliceToArray(t), "array in slice constant")
|
|
a.endObject(obj, l)
|
|
|
|
a.addressOf(id, obj)
|
|
}
|
|
}
|
|
return id
|
|
}
|
|
|
|
// valueNode returns the id of the value node for v, creating it (and
|
|
// the association) as needed. It may return zero for uninteresting
|
|
// values containing no pointers.
|
|
//
|
|
// Nodes for locals are created en masse during genFunc and are
|
|
// implicitly contextualized by the function currently being analyzed
|
|
// (i.e. parameter to genFunc).
|
|
//
|
|
func (a *analysis) valueNode(v ssa.Value) nodeid {
|
|
id, ok := a.valNode[v]
|
|
if !ok {
|
|
switch v := v.(type) {
|
|
case *ssa.Function:
|
|
id = a.makeFunction(v)
|
|
|
|
case *ssa.Global:
|
|
id = a.makeGlobal(v)
|
|
|
|
case *ssa.Const:
|
|
id = a.makeConstant(v)
|
|
|
|
case *ssa.Capture:
|
|
// TODO(adonovan): treat captures context-sensitively.
|
|
id = a.addNodes(v.Type(), "capture")
|
|
|
|
default:
|
|
// *ssa.Parameters and ssa.Instruction values
|
|
// are created by genFunc.
|
|
// *Builtins are not true values.
|
|
panic(v)
|
|
}
|
|
a.setValueNode(v, id)
|
|
}
|
|
return id
|
|
}
|
|
|
|
// valueOffsetNode ascertains the node for tuple/struct value v,
|
|
// then returns the node for its subfield #index.
|
|
//
|
|
func (a *analysis) valueOffsetNode(v ssa.Value, index int) nodeid {
|
|
id := a.valueNode(v)
|
|
if id == 0 {
|
|
panic(fmt.Sprintf("cannot offset within n0: %s = %s", v.Name(), v))
|
|
}
|
|
return id + nodeid(a.offsetOf(v.Type(), index))
|
|
}
|
|
|
|
// interfaceValue returns the (first node of) the value, and the
|
|
// concrete type, of the interface object (flags&ntInterface) starting
|
|
// at id.
|
|
//
|
|
func (a *analysis) interfaceValue(id nodeid) (nodeid, types.Type) {
|
|
n := a.nodes[id]
|
|
if n.flags&ntInterface == 0 {
|
|
panic(fmt.Sprintf("interfaceValue(n%d): not an interface object; typ=%s", id, n.typ))
|
|
}
|
|
return id + 1, n.typ
|
|
}
|
|
|
|
// funcParams returns the first node of the params block of the
|
|
// function whose object node (flags&ntFunction) is id.
|
|
//
|
|
func (a *analysis) funcParams(id nodeid) nodeid {
|
|
if a.nodes[id].flags&ntFunction == 0 {
|
|
panic(fmt.Sprintf("funcParams(n%d): not a function object block", id))
|
|
}
|
|
return id + 1
|
|
}
|
|
|
|
// funcResults returns the first node of the results block of the
|
|
// function whose object node (flags&ntFunction) is id.
|
|
//
|
|
func (a *analysis) funcResults(id nodeid) nodeid {
|
|
n := a.nodes[id]
|
|
if n.flags&ntFunction == 0 {
|
|
panic(fmt.Sprintf("funcResults(n%d): not a function object block", id))
|
|
}
|
|
sig := n.typ.(*types.Signature)
|
|
id += 1 + nodeid(a.sizeof(sig.Params()))
|
|
if sig.Recv() != nil {
|
|
id += nodeid(a.sizeof(sig.Recv().Type()))
|
|
}
|
|
return id
|
|
}
|
|
|
|
// ---------- Constraint creation ----------
|
|
|
|
// copy creates a constraint of the form dst = src.
|
|
// sizeof is the width (in logical fields) of the copied type.
|
|
//
|
|
func (a *analysis) copy(dst, src nodeid, sizeof uint32) {
|
|
if src == dst || sizeof == 0 {
|
|
return // trivial
|
|
}
|
|
if src == 0 || dst == 0 {
|
|
panic(fmt.Sprintf("ill-typed copy dst=n%d src=n%d", dst, src))
|
|
}
|
|
for i := uint32(0); i < sizeof; i++ {
|
|
a.addConstraint(©Constraint{dst, src})
|
|
src++
|
|
dst++
|
|
}
|
|
}
|
|
|
|
// addressOf creates a constraint of the form id = &obj.
|
|
func (a *analysis) addressOf(id, obj nodeid) {
|
|
if id == 0 {
|
|
panic("addressOf: zero id")
|
|
}
|
|
if obj == 0 {
|
|
panic("addressOf: zero obj")
|
|
}
|
|
a.addConstraint(&addrConstraint{id, obj})
|
|
}
|
|
|
|
// load creates a load constraint of the form dst = *src.
|
|
// sizeof is the width (in logical fields) of the loaded type.
|
|
//
|
|
func (a *analysis) load(dst, src nodeid, sizeof uint32) {
|
|
a.loadOffset(dst, src, 0, sizeof)
|
|
}
|
|
|
|
// loadOffset creates a load constraint of the form dst = src[offset].
|
|
// offset is the pointer offset in logical fields.
|
|
// sizeof is the width (in logical fields) of the loaded type.
|
|
//
|
|
func (a *analysis) loadOffset(dst, src nodeid, offset uint32, sizeof uint32) {
|
|
if dst == 0 {
|
|
return // load of non-pointerlike value
|
|
}
|
|
if src == 0 && dst == 0 {
|
|
return // non-pointerlike operation
|
|
}
|
|
if src == 0 || dst == 0 {
|
|
panic(fmt.Sprintf("ill-typed load dst=n%d src=n%d", dst, src))
|
|
}
|
|
for i := uint32(0); i < sizeof; i++ {
|
|
a.addConstraint(&loadConstraint{offset, dst, src})
|
|
offset++
|
|
dst++
|
|
}
|
|
}
|
|
|
|
// store creates a store constraint of the form *dst = src.
|
|
// sizeof is the width (in logical fields) of the stored type.
|
|
//
|
|
func (a *analysis) store(dst, src nodeid, sizeof uint32) {
|
|
a.storeOffset(dst, src, 0, sizeof)
|
|
}
|
|
|
|
// storeOffset creates a store constraint of the form dst[offset] = src.
|
|
// offset is the pointer offset in logical fields.
|
|
// sizeof is the width (in logical fields) of the stored type.
|
|
//
|
|
func (a *analysis) storeOffset(dst, src nodeid, offset uint32, sizeof uint32) {
|
|
if src == 0 {
|
|
return // store of non-pointerlike value
|
|
}
|
|
if src == 0 && dst == 0 {
|
|
return // non-pointerlike operation
|
|
}
|
|
if src == 0 || dst == 0 {
|
|
panic(fmt.Sprintf("ill-typed store dst=n%d src=n%d", dst, src))
|
|
}
|
|
for i := uint32(0); i < sizeof; i++ {
|
|
a.addConstraint(&storeConstraint{offset, dst, src})
|
|
offset++
|
|
src++
|
|
}
|
|
}
|
|
|
|
// offsetAddr creates an offsetAddr constraint of the form dst = &src.#offset.
|
|
// offset is the field offset in logical fields.
|
|
//
|
|
func (a *analysis) offsetAddr(dst, src nodeid, offset uint32) {
|
|
if offset == 0 {
|
|
// Simplify dst = &src->f0
|
|
// to dst = src
|
|
// (NB: this optimisation is defeated by the identity
|
|
// field prepended to struct and array objects.)
|
|
a.copy(dst, src, 1)
|
|
} else {
|
|
a.addConstraint(&offsetAddrConstraint{offset, dst, src})
|
|
}
|
|
}
|
|
|
|
// addConstraint adds c to the constraint set.
|
|
func (a *analysis) addConstraint(c constraint) {
|
|
a.constraints = append(a.constraints, c)
|
|
if a.log != nil {
|
|
fmt.Fprintf(a.log, "\t%s\n", c)
|
|
}
|
|
}
|
|
|
|
// copyElems generates load/store constraints for *dst = *src,
|
|
// where src and dst are slices or *arrays.
|
|
// (If pts(·) of either is a known singleton, this is suboptimal.)
|
|
//
|
|
func (a *analysis) copyElems(typ types.Type, dst, src nodeid) {
|
|
tmp := a.addNodes(typ, "copy")
|
|
sz := a.sizeof(typ)
|
|
a.loadOffset(tmp, src, 1, sz)
|
|
a.storeOffset(dst, tmp, 1, sz)
|
|
}
|
|
|
|
// ---------- Constraint generation ----------
|
|
|
|
// genConv generates constraints for the conversion operation conv.
|
|
func (a *analysis) genConv(conv *ssa.Convert) {
|
|
res := a.valueNode(conv)
|
|
if res == 0 {
|
|
return // result is non-pointerlike
|
|
}
|
|
|
|
tSrc := conv.X.Type()
|
|
tDst := conv.Type()
|
|
|
|
switch utSrc := tSrc.Underlying().(type) {
|
|
case *types.Slice:
|
|
// []byte/[]rune -> string?
|
|
return
|
|
|
|
case *types.Pointer:
|
|
// *T -> unsafe.Pointer?
|
|
if tDst == tUnsafePtr {
|
|
// ignore for now
|
|
// a.copy(res, a.valueNode(conv.X), 1)
|
|
return
|
|
}
|
|
|
|
case *types.Basic:
|
|
switch utDst := tDst.Underlying().(type) {
|
|
case *types.Pointer:
|
|
// unsafe.Pointer -> *T? (currently unsound)
|
|
if utSrc == tUnsafePtr {
|
|
// For now, suppress unsafe.Pointer conversion
|
|
// warnings on "syscall" package.
|
|
// TODO(adonovan): audit for soundness.
|
|
if conv.Parent().Pkg.Object.Path() != "syscall" {
|
|
a.warnf(conv.Pos(),
|
|
"unsound: %s contains an unsafe.Pointer conversion (to %s)",
|
|
conv.Parent(), tDst)
|
|
}
|
|
|
|
// For now, we treat unsafe.Pointer->*T
|
|
// conversion like new(T) and create an
|
|
// unaliased object. In future we may handle
|
|
// unsafe conversions soundly; see TODO file.
|
|
obj := a.addNodes(mustDeref(tDst), "unsafe.Pointer conversion")
|
|
a.endObject(obj, conv)
|
|
a.addressOf(res, obj)
|
|
return
|
|
}
|
|
|
|
case *types.Slice:
|
|
// string -> []byte/[]rune (or named aliases)?
|
|
if utSrc.Info()&types.IsString != 0 {
|
|
obj := a.addNodes(sliceToArray(tDst), "convert")
|
|
a.endObject(obj, conv)
|
|
a.addressOf(res, obj)
|
|
return
|
|
}
|
|
|
|
case *types.Basic:
|
|
// TODO(adonovan):
|
|
// unsafe.Pointer -> uintptr?
|
|
// uintptr -> unsafe.Pointer
|
|
//
|
|
// The language doesn't adequately specify the
|
|
// behaviour of these operations, but almost
|
|
// all uses of these conversions (even in the
|
|
// spec) seem to imply a non-moving garbage
|
|
// collection strategy, or implicit "pinning"
|
|
// semantics for unsafe.Pointer conversions.
|
|
|
|
// TODO(adonovan): we need more work before we can handle
|
|
// cryptopointers well.
|
|
if utSrc == tUnsafePtr || utDst == tUnsafePtr {
|
|
// Ignore for now. See TODO file for ideas.
|
|
return
|
|
}
|
|
|
|
return // ignore all other basic type conversions
|
|
}
|
|
}
|
|
|
|
panic(fmt.Sprintf("illegal *ssa.Convert %s -> %s: %s", tSrc, tDst, conv.Parent()))
|
|
}
|
|
|
|
// genAppend generates constraints for a call to append.
|
|
func (a *analysis) genAppend(instr *ssa.Call) {
|
|
// Consider z = append(x, y). y is optional.
|
|
// This may allocate a new [1]T array; call its object w.
|
|
// We get the following constraints:
|
|
// z = x
|
|
// z = &w
|
|
// *z = *y
|
|
|
|
x := a.valueNode(instr.Call.Args[0])
|
|
|
|
z := a.valueNode(instr)
|
|
a.copy(z, x, 1) // z = x
|
|
|
|
if len(instr.Call.Args) == 1 {
|
|
return // no allocation for z = append(x) or _ = append(x).
|
|
}
|
|
|
|
// TODO(adonovan): test append([]byte, ...string) []byte.
|
|
|
|
y := a.valueNode(instr.Call.Args[1])
|
|
tArray := sliceToArray(instr.Call.Args[0].Type())
|
|
|
|
var w nodeid
|
|
w = a.nextNode()
|
|
a.addNodes(tArray, "append")
|
|
a.endObject(w, instr)
|
|
|
|
a.copyElems(tArray.Elem(), z, y) // *z = *y
|
|
a.addressOf(z, w) // z = &w
|
|
}
|
|
|
|
// genBuiltinCall generates contraints for a call to a built-in.
|
|
func (a *analysis) genBuiltinCall(instr ssa.CallInstruction) {
|
|
call := instr.Common()
|
|
switch call.Value.(*ssa.Builtin).Object().Name() {
|
|
case "append":
|
|
// Safe cast: append cannot appear in a go or defer statement.
|
|
a.genAppend(instr.(*ssa.Call))
|
|
|
|
case "copy":
|
|
tElem := call.Args[0].Type().Underlying().(*types.Slice).Elem()
|
|
a.copyElems(tElem, a.valueNode(call.Args[0]), a.valueNode(call.Args[1]))
|
|
|
|
case "panic":
|
|
a.copy(a.panicNode, a.valueNode(call.Args[0]), 1)
|
|
|
|
case "recover":
|
|
if v := instr.Value(); v != nil {
|
|
a.copy(a.valueNode(v), a.panicNode, 1)
|
|
}
|
|
|
|
case "print":
|
|
// Analytically print is a no-op, but it's a convenient hook
|
|
// for testing the pts of an expression, so we notify the client.
|
|
// Existing uses in Go core libraries are few and harmless.
|
|
if Print := a.config.Print; Print != nil {
|
|
// Due to context-sensitivity, we may encounter
|
|
// the same print() call in many contexts, so
|
|
// we merge them to a canonical node.
|
|
probe := a.probes[call]
|
|
t := call.Args[0].Type()
|
|
|
|
// First time? Create the canonical probe node.
|
|
if probe == 0 {
|
|
probe = a.addNodes(t, "print")
|
|
a.probes[call] = probe
|
|
Print(call, ptr{a, probe}) // notify client
|
|
}
|
|
|
|
a.copy(probe, a.valueNode(call.Args[0]), a.sizeof(t))
|
|
}
|
|
|
|
default:
|
|
// No-ops: close len cap real imag complex println delete.
|
|
}
|
|
}
|
|
|
|
// shouldUseContext defines the context-sensitivity policy. It
|
|
// returns true if we should analyse all static calls to fn anew.
|
|
//
|
|
// Obviously this interface rather limits how much freedom we have to
|
|
// choose a policy. The current policy, rather arbitrarily, is true
|
|
// for intrinsics and accessor methods (actually: short, single-block,
|
|
// call-free functions). This is just a starting point.
|
|
//
|
|
func (a *analysis) shouldUseContext(fn *ssa.Function) bool {
|
|
if a.findIntrinsic(fn) != nil {
|
|
return true // treat intrinsics context-sensitively
|
|
}
|
|
if len(fn.Blocks) != 1 {
|
|
return false // too expensive
|
|
}
|
|
blk := fn.Blocks[0]
|
|
if len(blk.Instrs) > 10 {
|
|
return false // too expensive
|
|
}
|
|
if fn.Synthetic != "" && (fn.Pkg == nil || fn != fn.Pkg.Func("init")) {
|
|
return true // treat synthetic wrappers context-sensitively
|
|
}
|
|
for _, instr := range blk.Instrs {
|
|
switch instr := instr.(type) {
|
|
case ssa.CallInstruction:
|
|
// Disallow function calls (except to built-ins)
|
|
// because of the danger of unbounded recursion.
|
|
if _, ok := instr.Common().Value.(*ssa.Builtin); !ok {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
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 {
|
|
// 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
|
|
} else {
|
|
a.valueNode(fn) // ensure shared contour was created
|
|
obj = a.funcObj[fn] // ordinary (shared) contour.
|
|
}
|
|
|
|
sig := call.Signature()
|
|
targets := a.addOneNode(sig, "call.targets", nil)
|
|
a.addressOf(targets, obj) // (a singleton)
|
|
|
|
// Copy receiver, if any.
|
|
params := a.funcParams(obj)
|
|
args := call.Args
|
|
if sig.Recv() != nil {
|
|
sz := a.sizeof(sig.Recv().Type())
|
|
a.copy(params, a.valueNode(args[0]), sz)
|
|
params += nodeid(sz)
|
|
args = args[1:]
|
|
}
|
|
|
|
// Copy actual parameters into formal params block.
|
|
// Must loop, since the actuals aren't contiguous.
|
|
for i, arg := range args {
|
|
sz := a.sizeof(sig.Params().At(i).Type())
|
|
a.copy(params, a.valueNode(arg), sz)
|
|
params += nodeid(sz)
|
|
}
|
|
|
|
// Copy formal results block to actual result.
|
|
if result != 0 {
|
|
a.copy(result, a.funcResults(obj), a.sizeof(sig.Results()))
|
|
}
|
|
|
|
return 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(call *ssa.CallCommon, result nodeid) nodeid {
|
|
fn := a.valueNode(call.Value)
|
|
sig := call.Signature()
|
|
|
|
// We add dynamic closure rules that store the arguments into,
|
|
// and load the results from, the P/R block of each function
|
|
// discovered in pts(fn).
|
|
|
|
var offset uint32 = 1 // P/R block starts at offset 1
|
|
for i, arg := range call.Args {
|
|
sz := a.sizeof(sig.Params().At(i).Type())
|
|
a.storeOffset(fn, a.valueNode(arg), offset, sz)
|
|
offset += sz
|
|
}
|
|
if result != 0 {
|
|
a.loadOffset(result, fn, offset, a.sizeof(sig.Results()))
|
|
}
|
|
return 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 {
|
|
sig := call.Signature()
|
|
|
|
// Allocate a contiguous targets/params/results block for this call.
|
|
block := a.nextNode()
|
|
targets := a.addOneNode(sig, "invoke.targets", nil)
|
|
p := a.addNodes(sig.Params(), "invoke.params")
|
|
r := a.addNodes(sig.Results(), "invoke.results")
|
|
|
|
// Copy the actual parameters into the call's params block.
|
|
for i, n := 0, sig.Params().Len(); i < n; i++ {
|
|
sz := a.sizeof(sig.Params().At(i).Type())
|
|
a.copy(p, a.valueNode(call.Args[i]), sz)
|
|
p += nodeid(sz)
|
|
}
|
|
// Copy the call's results block to the actual results.
|
|
if result != 0 {
|
|
a.copy(result, r, a.sizeof(sig.Results()))
|
|
}
|
|
|
|
// We add a dynamic invoke constraint that will add
|
|
// 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
|
|
}
|
|
|
|
// genCall generates contraints for call instruction instr.
|
|
func (a *analysis) genCall(caller *cgnode, instr ssa.CallInstruction) {
|
|
call := instr.Common()
|
|
|
|
// Intrinsic implementations of built-in functions.
|
|
if _, ok := call.Value.(*ssa.Builtin); ok {
|
|
a.genBuiltinCall(instr)
|
|
return
|
|
}
|
|
|
|
var result nodeid
|
|
if v := instr.Value(); v != nil {
|
|
result = a.valueNode(v)
|
|
}
|
|
|
|
// The node whose pts(·) will contain all targets of the call.
|
|
var targets nodeid
|
|
switch {
|
|
case call.StaticCallee() != nil:
|
|
targets = a.genStaticCall(call, result)
|
|
case call.IsInvoke():
|
|
targets = a.genInvoke(call, result)
|
|
default:
|
|
targets = a.genDynamicCall(call, result)
|
|
}
|
|
|
|
site := &callsite{
|
|
caller: caller,
|
|
targets: targets,
|
|
instr: instr,
|
|
pos: instr.Pos(),
|
|
}
|
|
a.callsites = append(a.callsites, site)
|
|
if a.log != nil {
|
|
fmt.Fprintf(a.log, "\t%s to targets %s from %s\n",
|
|
site.Description(), site.targets, site.caller)
|
|
}
|
|
}
|
|
|
|
// genInstr generates contraints for instruction instr in context cgn.
|
|
func (a *analysis) genInstr(cgn *cgnode, instr ssa.Instruction) {
|
|
if a.log != nil {
|
|
var prefix string
|
|
if val, ok := instr.(ssa.Value); ok {
|
|
prefix = val.Name() + " = "
|
|
}
|
|
fmt.Fprintf(a.log, "; %s%s\n", prefix, instr)
|
|
}
|
|
|
|
switch instr := instr.(type) {
|
|
case *ssa.DebugRef:
|
|
// no-op.
|
|
|
|
case *ssa.UnOp:
|
|
switch instr.Op {
|
|
case token.ARROW: // <-x
|
|
// We can ignore instr.CommaOk because the node we're
|
|
// altering is always at zero offset relative to instr.
|
|
a.load(a.valueNode(instr), a.valueNode(instr.X), a.sizeof(instr.Type()))
|
|
|
|
case token.MUL: // *x
|
|
a.load(a.valueNode(instr), a.valueNode(instr.X), a.sizeof(instr.Type()))
|
|
|
|
default:
|
|
// NOT, SUB, XOR: no-op.
|
|
}
|
|
|
|
case *ssa.BinOp:
|
|
// All no-ops.
|
|
|
|
case ssa.CallInstruction: // *ssa.Call, *ssa.Go, *ssa.Defer
|
|
a.genCall(cgn, instr)
|
|
|
|
case *ssa.ChangeType:
|
|
a.copy(a.valueNode(instr), a.valueNode(instr.X), 1)
|
|
|
|
case *ssa.Convert:
|
|
a.genConv(instr)
|
|
|
|
case *ssa.Extract:
|
|
a.copy(a.valueNode(instr),
|
|
a.valueOffsetNode(instr.Tuple, instr.Index),
|
|
a.sizeof(instr.Type()))
|
|
|
|
case *ssa.FieldAddr:
|
|
a.offsetAddr(a.valueNode(instr), a.valueNode(instr.X),
|
|
a.offsetOf(mustDeref(instr.X.Type()), instr.Field))
|
|
|
|
case *ssa.IndexAddr:
|
|
a.offsetAddr(a.valueNode(instr), a.valueNode(instr.X), 1)
|
|
|
|
case *ssa.Field:
|
|
a.copy(a.valueNode(instr),
|
|
a.valueOffsetNode(instr.X, instr.Field),
|
|
a.sizeof(instr.Type()))
|
|
|
|
case *ssa.Index:
|
|
a.copy(a.valueNode(instr), 1+a.valueNode(instr.X), a.sizeof(instr.Type()))
|
|
|
|
case *ssa.Select:
|
|
recv := a.valueOffsetNode(instr, 2) // instr : (index, recvOk, recv0, ... recv_n-1)
|
|
for _, st := range instr.States {
|
|
elemSize := a.sizeof(st.Chan.Type().Underlying().(*types.Chan).Elem())
|
|
switch st.Dir {
|
|
case ast.RECV:
|
|
a.load(recv, a.valueNode(st.Chan), elemSize)
|
|
recv++
|
|
|
|
case ast.SEND:
|
|
a.store(a.valueNode(st.Chan), a.valueNode(st.Send), elemSize)
|
|
}
|
|
}
|
|
|
|
case *ssa.Ret:
|
|
results := a.funcResults(cgn.obj)
|
|
for _, r := range instr.Results {
|
|
sz := a.sizeof(r.Type())
|
|
a.copy(results, a.valueNode(r), sz)
|
|
results += nodeid(sz)
|
|
}
|
|
|
|
case *ssa.Send:
|
|
a.store(a.valueNode(instr.Chan), a.valueNode(instr.X), a.sizeof(instr.X.Type()))
|
|
|
|
case *ssa.Store:
|
|
a.store(a.valueNode(instr.Addr), a.valueNode(instr.Val), a.sizeof(instr.Val.Type()))
|
|
|
|
case *ssa.Alloc:
|
|
obj := a.nextNode()
|
|
a.addNodes(mustDeref(instr.Type()), "alloc")
|
|
a.endObject(obj, instr)
|
|
a.addressOf(a.valueNode(instr), obj)
|
|
|
|
case *ssa.MakeSlice:
|
|
obj := a.nextNode()
|
|
a.addNodes(sliceToArray(instr.Type()), "makeslice")
|
|
a.endObject(obj, instr)
|
|
a.addressOf(a.valueNode(instr), obj)
|
|
|
|
case *ssa.MakeChan:
|
|
obj := a.nextNode()
|
|
a.addNodes(instr.Type().Underlying().(*types.Chan).Elem(), "makechan")
|
|
a.endObject(obj, instr)
|
|
a.addressOf(a.valueNode(instr), obj)
|
|
|
|
case *ssa.MakeMap:
|
|
obj := a.nextNode()
|
|
tmap := instr.Type().Underlying().(*types.Map)
|
|
a.addNodes(tmap.Key(), "makemap.key")
|
|
a.addNodes(tmap.Elem(), "makemap.value")
|
|
a.endObject(obj, instr)
|
|
a.addressOf(a.valueNode(instr), obj)
|
|
|
|
case *ssa.MakeInterface:
|
|
tConc := instr.X.Type()
|
|
// Create nodes and constraints for all methods of the type.
|
|
// Ascertaining which will be needed is undecidable in general.
|
|
mset := tConc.MethodSet()
|
|
for i, n := 0, mset.Len(); i < n; i++ {
|
|
a.valueNode(a.prog.Method(mset.At(i)))
|
|
}
|
|
obj := a.addOneNode(tConc, "iface.conctype", nil) // NB: type may be non-scalar!
|
|
vnode := a.addNodes(tConc, "iface.value")
|
|
a.endObject(obj, instr)
|
|
a.nodes[obj].flags |= ntInterface
|
|
// Copy the value into it, if nontrivial.
|
|
if x := a.valueNode(instr.X); x != 0 {
|
|
a.copy(vnode, x, a.sizeof(tConc))
|
|
}
|
|
a.addressOf(a.valueNode(instr), obj)
|
|
|
|
case *ssa.ChangeInterface:
|
|
a.copy(a.valueNode(instr), a.valueNode(instr.X), 1)
|
|
|
|
case *ssa.TypeAssert:
|
|
dst, src := a.valueNode(instr), a.valueNode(instr.X)
|
|
a.addConstraint(&typeAssertConstraint{instr.AssertedType, dst, src})
|
|
|
|
case *ssa.Slice:
|
|
a.copy(a.valueNode(instr), a.valueNode(instr.X), 1)
|
|
|
|
case *ssa.If, *ssa.Jump:
|
|
// no-op.
|
|
|
|
case *ssa.Phi:
|
|
sz := a.sizeof(instr.Type())
|
|
for _, e := range instr.Edges {
|
|
a.copy(a.valueNode(instr), a.valueNode(e), sz)
|
|
}
|
|
|
|
case *ssa.MakeClosure:
|
|
fn := instr.Fn.(*ssa.Function)
|
|
a.copy(a.valueNode(instr), a.valueNode(fn), 1)
|
|
// Free variables are treated like global variables.
|
|
for i, b := range instr.Bindings {
|
|
a.copy(a.valueNode(fn.FreeVars[i]), a.valueNode(b), a.sizeof(b.Type()))
|
|
}
|
|
|
|
case *ssa.RunDefers:
|
|
// The analysis is flow insensitive, so we just "call"
|
|
// defers as we encounter them.
|
|
|
|
case *ssa.Range:
|
|
// Do nothing. Next{Iter: *ssa.Range} handles this case.
|
|
|
|
case *ssa.Next:
|
|
if !instr.IsString { // map
|
|
// Assumes that Next is always directly applied to a Range result.
|
|
theMap := instr.Iter.(*ssa.Range).X
|
|
tMap := theMap.Type().Underlying().(*types.Map)
|
|
ksize := a.sizeof(tMap.Key())
|
|
vsize := a.sizeof(tMap.Elem())
|
|
|
|
// Load from the map's (k,v) into the tuple's (ok, k, v).
|
|
a.load(a.valueNode(instr)+1, a.valueNode(theMap), ksize+vsize)
|
|
}
|
|
|
|
case *ssa.Lookup:
|
|
if tMap, ok := instr.X.Type().Underlying().(*types.Map); ok {
|
|
// CommaOk can be ignored: field 0 is a no-op.
|
|
ksize := a.sizeof(tMap.Key())
|
|
vsize := a.sizeof(tMap.Elem())
|
|
a.loadOffset(a.valueNode(instr), a.valueNode(instr.X), ksize, vsize)
|
|
}
|
|
|
|
case *ssa.MapUpdate:
|
|
tmap := instr.Map.Type().Underlying().(*types.Map)
|
|
ksize := a.sizeof(tmap.Key())
|
|
vsize := a.sizeof(tmap.Elem())
|
|
m := a.valueNode(instr.Map)
|
|
a.store(m, a.valueNode(instr.Key), ksize)
|
|
a.storeOffset(m, a.valueNode(instr.Value), ksize, vsize)
|
|
|
|
case *ssa.Panic:
|
|
a.copy(a.panicNode, a.valueNode(instr.X), 1)
|
|
|
|
default:
|
|
panic(fmt.Sprintf("unimplemented: %T", instr))
|
|
}
|
|
}
|
|
|
|
// genRootCalls generates the synthetic root of the callgraph and the
|
|
// initial calls from it to the analysis scope, such as main, a test
|
|
// or a library.
|
|
//
|
|
func (a *analysis) genRootCalls() *cgnode {
|
|
r := ssa.NewFunction("<root>", new(types.Signature), "root of callgraph")
|
|
r.Prog = a.prog // hack.
|
|
r.Enclosing = r // hack, so Function.String() doesn't crash
|
|
r.String() // (asserts that it doesn't crash)
|
|
root := &cgnode{fn: r}
|
|
|
|
// For each main package, call main.init(), main.main().
|
|
for _, mainPkg := range a.config.Mains {
|
|
main := mainPkg.Func("main")
|
|
if main == nil {
|
|
panic(fmt.Sprintf("%s has no main function", mainPkg))
|
|
}
|
|
|
|
targets := a.addOneNode(main.Signature, "root.targets", nil)
|
|
site := &callsite{
|
|
caller: root,
|
|
targets: targets,
|
|
}
|
|
a.callsites = append(a.callsites, site)
|
|
for _, fn := range [2]*ssa.Function{mainPkg.Func("init"), main} {
|
|
if a.log != nil {
|
|
fmt.Fprintf(a.log, "\troot call to %s:\n", fn)
|
|
}
|
|
a.copy(targets, a.valueNode(fn), 1)
|
|
}
|
|
}
|
|
|
|
return root
|
|
}
|
|
|
|
// genFunc generates constraints for function fn.
|
|
func (a *analysis) genFunc(cgn *cgnode) {
|
|
fn := cgn.fn
|
|
if a.log != nil {
|
|
fmt.Fprintln(a.log)
|
|
fmt.Fprintln(a.log)
|
|
cgn.fn.DumpTo(a.log)
|
|
}
|
|
|
|
if impl := a.findIntrinsic(fn); impl != nil {
|
|
impl(a, cgn)
|
|
return
|
|
}
|
|
|
|
if fn.Blocks == nil {
|
|
// External function with no intrinsic treatment.
|
|
// We'll warn about calls to such functions at the end.
|
|
return
|
|
}
|
|
|
|
// The value nodes for the params are in the func object block.
|
|
params := a.funcParams(cgn.obj)
|
|
for _, p := range fn.Params {
|
|
// TODO(adonovan): record the context (cgn) too.
|
|
a.setValueNode(p, params)
|
|
params += nodeid(a.sizeof(p.Type()))
|
|
}
|
|
|
|
// Free variables are treated like global variables:
|
|
// the outer function sets them with MakeClosure;
|
|
// the inner function accesses them with Capture.
|
|
|
|
// Create value nodes for all value instructions.
|
|
// (Clobbers any previous nodes from same fn in different context.)
|
|
if a.log != nil {
|
|
fmt.Fprintln(a.log, "; Creating instruction values")
|
|
}
|
|
for _, b := range fn.Blocks {
|
|
for _, instr := range b.Instrs {
|
|
switch instr := instr.(type) {
|
|
case *ssa.Range:
|
|
// do nothing: it has a funky type.
|
|
|
|
case ssa.Value:
|
|
var comment string
|
|
if a.log != nil {
|
|
comment = instr.Name()
|
|
}
|
|
id := a.addNodes(instr.Type(), comment)
|
|
// TODO(adonovan): record the context (cgn) too.
|
|
a.setValueNode(instr, id)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate constraints for instructions.
|
|
for _, b := range fn.Blocks {
|
|
for _, instr := range b.Instrs {
|
|
a.genInstr(cgn, instr)
|
|
}
|
|
}
|
|
|
|
// (Instruction Values will hang around in the environment.)
|
|
}
|
|
|
|
// generate generates offline constraints for the entire program.
|
|
// It returns the synthetic root of the callgraph.
|
|
//
|
|
func (a *analysis) generate() *cgnode {
|
|
// Create a dummy node since we use the nodeid 0 for
|
|
// non-pointerlike variables.
|
|
a.addNodes(tInvalid, "(zero)")
|
|
|
|
// Create the global node for panic values.
|
|
a.panicNode = a.addNodes(tEface, "panic")
|
|
|
|
root := a.genRootCalls()
|
|
|
|
// Generate constraints for entire program.
|
|
// (Actually just the RTA-reachable portion of the program.
|
|
// See Bacon & Sweeney, OOPSLA'96).
|
|
for len(a.genq) > 0 {
|
|
cgn := a.genq[0]
|
|
a.genq = a.genq[1:]
|
|
a.genFunc(cgn)
|
|
}
|
|
|
|
// Create a dummy node to avoid out-of-range indexing in case
|
|
// the last allocated type was of zero length.
|
|
a.addNodes(tInvalid, "(max)")
|
|
|
|
return root
|
|
}
|