blob: 4a8b926432c503885a6ba30dda21710736fe2777 [file] [log] [blame]
// 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
import (
"fmt"
"go/token"
"io"
"code.google.com/p/go.tools/go/types/typemap"
"code.google.com/p/go.tools/ssa"
)
type Config struct {
// -------- Scope of the analysis --------
// Clients must provide the analysis with at least one package defining a main() function.
Mains []*ssa.Package // set of 'main' packages to analyze
root *ssa.Function // synthetic analysis root
// -------- Optional callbacks invoked by the analysis --------
// Call is invoked for each discovered call-graph edge. The
// call-graph is a multigraph over CallGraphNodes with edges
// labelled by the CallSite that gives rise to the edge.
// (The caller node is available as site.Caller())
//
// Clients that wish to construct a call graph may provide
// CallGraph.AddEdge here.
//
// The callgraph may be context-sensitive, i.e. it may
// distinguish separate calls to the same function depending
// on the context.
//
Call func(site CallSite, callee CallGraphNode)
// CallSite is invoked for each call-site encountered in the
// program.
//
// The callgraph may be context-sensitive, i.e. it may
// distinguish separate calls to the same function depending
// on the context.
//
CallSite func(site CallSite)
// Warn is invoked for each warning encountered by the analysis,
// e.g. unknown external function, unsound use of unsafe.Pointer.
// pos may be zero if the position is not known.
Warn func(pos token.Pos, format string, args ...interface{})
// Print is invoked during the analysis for each discovered
// call to the built-in print(x).
//
// Pointer p may be saved until the analysis is complete, at
// which point its methods provide access to the analysis
// (The result of callings its methods within the Print
// callback is undefined.) p is nil if x is non-pointerlike.
//
// TODO(adonovan): this was a stop-gap measure for identifing
// arbitrary expressions of interest in the tests. Now that
// ssa.ValueForExpr exists, we should use that instead.
//
Print func(site *ssa.CallCommon, p Pointer)
// The client populates QueryValues[v] for each ssa.Value v
// of interest.
//
// The boolean (Indirect) indicates whether to compute the
// points-to set for v (false) or *v (true): the latter is
// typically wanted for Values corresponding to source-level
// lvalues, e.g. an *ssa.Global.
//
// The pointer analysis will populate the corresponding
// QueryResults value when it creates the pointer variable
// for v or *v. Upon completion the client can inspect the
// map for the results.
//
// If a Value belongs to a function that the analysis treats
// context-sensitively, the corresponding QueryResults slice
// may have multiple Pointers, one per distinct context. Use
// PointsToCombined to merge them.
//
// TODO(adonovan): refactor the API: separate all results of
// Analyze() into a dedicated Result struct.
//
QueryValues map[ssa.Value]Indirect
QueryResults map[ssa.Value][]Pointer
// -------- Other configuration options --------
// If Log is non-nil, a log messages are written to it.
// Logging is extremely verbose.
Log io.Writer
}
type Indirect bool // map[ssa.Value]Indirect is not a set
func (c *Config) prog() *ssa.Program {
for _, main := range c.Mains {
return main.Prog
}
panic("empty scope")
}
// A Pointer is an equivalence class of pointerlike values.
//
// TODO(adonovan): add a method
// Context() CallGraphNode
// for pointers corresponding to local variables,
//
type Pointer interface {
// PointsTo returns the points-to set of this pointer.
PointsTo() PointsToSet
// MayAlias reports whether the receiver pointer may alias
// the argument pointer.
MayAlias(Pointer) bool
String() string
}
// A PointsToSet is a set of labels (locations or allocations).
//
type PointsToSet interface {
// PointsTo returns the set of labels that this points-to set
// contains.
Labels() []*Label
// Intersects reports whether this points-to set and the
// argument points-to set contain common members.
Intersects(PointsToSet) bool
// If this PointsToSet came from a Pointer of interface kind,
// ConcreteTypes returns the set of concrete types the
// interface may contain.
//
// The result is a mapping whose keys are the concrete types
// to which this interface may point. For each pointer-like
// key type, the corresponding map value is a set of pointer
// abstractions of that concrete type, represented as a
// []Pointer slice. Use PointsToCombined to merge them.
ConcreteTypes() *typemap.M
}
// Union returns the set containing all the elements of each set in sets.
func Union(sets ...PointsToSet) PointsToSet {
var union ptset
for _, set := range sets {
set := set.(ptset)
union.a = set.a
union.pts.addAll(set.pts)
}
return union
}
// PointsToCombined returns the combined points-to set of all the
// specified pointers.
func PointsToCombined(ptrs []Pointer) PointsToSet {
var ptsets []PointsToSet
for _, ptr := range ptrs {
ptsets = append(ptsets, ptr.PointsTo())
}
return Union(ptsets...)
}
// ---- PointsToSet public interface
type ptset struct {
a *analysis // may be nil if pts is nil
pts nodeset
}
func (s ptset) Labels() []*Label {
var labels []*Label
for l := range s.pts {
// Scan back to the previous object start.
for i := l; i >= 0; i-- {
n := s.a.nodes[i]
if n.flags&ntObject != 0 {
// TODO(adonovan): do bounds-check against n.size.
var v ssa.Value
if n.flags&ntFunction != 0 {
v = n.data.(*cgnode).fn
} else {
v = n.data.(ssa.Value)
// TODO(adonovan): what if v is nil?
}
labels = append(labels, &Label{
Value: v,
subelement: s.a.nodes[l].subelement,
})
break
}
}
}
return labels
}
func (s ptset) ConcreteTypes() *typemap.M {
var tmap typemap.M // default hasher // TODO(adonovan): opt: memoize per analysis
for ifaceObjId := range s.pts {
if s.a.nodes[ifaceObjId].flags&ntInterface == 0 {
// ConcreteTypes called on non-interface PT set.
continue // shouldn't happen
}
v, tconc := s.a.interfaceValue(ifaceObjId)
prev, _ := tmap.At(tconc).([]Pointer)
tmap.Set(tconc, append(prev, ptr{s.a, v}))
}
return &tmap
}
func (x ptset) Intersects(y_ PointsToSet) bool {
y := y_.(ptset)
for l := range x.pts {
if _, ok := y.pts[l]; ok {
return true
}
}
return false
}
// ---- Pointer public interface
// ptr adapts a node to the Pointer interface.
type ptr struct {
a *analysis
n nodeid // non-zero
}
func (p ptr) String() string {
return fmt.Sprintf("n%d", p.n)
}
func (p ptr) PointsTo() PointsToSet {
return ptset{p.a, p.a.nodes[p.n].pts}
}
func (p ptr) MayAlias(q Pointer) bool {
return p.PointsTo().Intersects(q.PointsTo())
}
func (p ptr) ConcreteTypes() *typemap.M {
return p.PointsTo().ConcreteTypes()
}