--- /dev/null
+// 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 (
+ "bytes"
+ "fmt"
+ "go/token"
+ "io"
+
+ "golang.org/x/tools/container/intsets"
+ "golang.org/x/tools/go/callgraph"
+ "golang.org/x/tools/go/ssa"
+ "golang.org/x/tools/go/types/typeutil"
+)
+
+// A Config formulates a pointer analysis problem for Analyze. It is
+// only usable for a single invocation of Analyze and must not be
+// reused.
+type Config struct {
+ // Mains contains the set of 'main' packages to analyze
+ // Clients must provide the analysis with at least one
+ // package defining a main() function.
+ //
+ // Non-main packages in the ssa.Program that are not
+ // dependencies of any main package may still affect the
+ // analysis result, because they contribute runtime types and
+ // thus methods.
+ // TODO(adonovan): investigate whether this is desirable.
+ Mains []*ssa.Package
+
+ // Reflection determines whether to handle reflection
+ // operators soundly, which is currently rather slow since it
+ // causes constraint to be generated during solving
+ // proportional to the number of constraint variables, which
+ // has not yet been reduced by presolver optimisation.
+ Reflection bool
+
+ // BuildCallGraph determines whether to construct a callgraph.
+ // If enabled, the graph will be available in Result.CallGraph.
+ BuildCallGraph bool
+
+ // The client populates Queries[v] or IndirectQueries[v]
+ // for each ssa.Value v of interest, to request that the
+ // points-to sets pts(v) or pts(*v) be computed. If the
+ // client needs both points-to sets, v may appear in both
+ // maps.
+ //
+ // (IndirectQueries is typically used for Values corresponding
+ // to source-level lvalues, e.g. an *ssa.Global.)
+ //
+ // The analysis populates the corresponding
+ // Result.{Indirect,}Queries map when it creates the pointer
+ // variable for v or *v. Upon completion the client can
+ // inspect that map for the results.
+ //
+ // TODO(adonovan): this API doesn't scale well for batch tools
+ // that want to dump the entire solution. Perhaps optionally
+ // populate a map[*ssa.DebugRef]Pointer in the Result, one
+ // entry per source expression.
+ //
+ Queries map[ssa.Value]struct{}
+ IndirectQueries map[ssa.Value]struct{}
+ extendedQueries map[ssa.Value][]*extendedQuery
+
+ // If Log is non-nil, log messages are written to it.
+ // Logging is extremely verbose.
+ Log io.Writer
+}
+
+type track uint32
+
+const (
+ trackChan track = 1 << iota // track 'chan' references
+ trackMap // track 'map' references
+ trackPtr // track regular pointers
+ trackSlice // track slice references
+
+ trackAll = ^track(0)
+)
+
+// AddQuery adds v to Config.Queries.
+// Precondition: CanPoint(v.Type()).
+func (c *Config) AddQuery(v ssa.Value) {
+ if !CanPoint(v.Type()) {
+ panic(fmt.Sprintf("%s is not a pointer-like value: %s", v, v.Type()))
+ }
+ if c.Queries == nil {
+ c.Queries = make(map[ssa.Value]struct{})
+ }
+ c.Queries[v] = struct{}{}
+}
+
+// AddQuery adds v to Config.IndirectQueries.
+// Precondition: CanPoint(v.Type().Underlying().(*types.Pointer).Elem()).
+func (c *Config) AddIndirectQuery(v ssa.Value) {
+ if c.IndirectQueries == nil {
+ c.IndirectQueries = make(map[ssa.Value]struct{})
+ }
+ if !CanPoint(mustDeref(v.Type())) {
+ panic(fmt.Sprintf("%s is not the address of a pointer-like value: %s", v, v.Type()))
+ }
+ c.IndirectQueries[v] = struct{}{}
+}
+
+// AddExtendedQuery adds an extended, AST-based query on v to the
+// analysis. The query, which must be a single Go expression, allows
+// destructuring the value.
+//
+// The query must operate on a variable named 'x', which represents
+// the value, and result in a pointer-like object. Only a subset of
+// Go expressions are permitted in queries, namely channel receives,
+// pointer dereferences, field selectors, array/slice/map/tuple
+// indexing and grouping with parentheses. The specific indices when
+// indexing arrays, slices and maps have no significance. Indices used
+// on tuples must be numeric and within bounds.
+//
+// All field selectors must be explicit, even ones usually elided
+// due to promotion of embedded fields.
+//
+// The query 'x' is identical to using AddQuery. The query '*x' is
+// identical to using AddIndirectQuery.
+//
+// On success, AddExtendedQuery returns a Pointer to the queried
+// value. This Pointer will be initialized during analysis. Using it
+// before analysis has finished has undefined behavior.
+//
+// Example:
+// // given v, which represents a function call to 'fn() (int, []*T)', and
+// // 'type T struct { F *int }', the following query will access the field F.
+// c.AddExtendedQuery(v, "x[1][0].F")
+func (c *Config) AddExtendedQuery(v ssa.Value, query string) (*Pointer, error) {
+ ops, _, err := parseExtendedQuery(v.Type(), query)
+ if err != nil {
+ return nil, fmt.Errorf("invalid query %q: %s", query, err)
+ }
+ if c.extendedQueries == nil {
+ c.extendedQueries = make(map[ssa.Value][]*extendedQuery)
+ }
+
+ ptr := &Pointer{}
+ c.extendedQueries[v] = append(c.extendedQueries[v], &extendedQuery{ops: ops, ptr: ptr})
+ return ptr, nil
+}
+
+func (c *Config) prog() *ssa.Program {
+ for _, main := range c.Mains {
+ return main.Prog
+ }
+ panic("empty scope")
+}
+
+type Warning struct {
+ Pos token.Pos
+ Message string
+}
+
+// A Result contains the results of a pointer analysis.
+//
+// See Config for how to request the various Result components.
+//
+type Result struct {
+ CallGraph *callgraph.Graph // discovered call graph
+ Queries map[ssa.Value]Pointer // pts(v) for each v in Config.Queries.
+ IndirectQueries map[ssa.Value]Pointer // pts(*v) for each v in Config.IndirectQueries.
+ Warnings []Warning // warnings of unsoundness
+}
+
+// A Pointer is an equivalence class of pointer-like values.
+//
+// A Pointer doesn't have a unique type because pointers of distinct
+// types may alias the same object.
+//
+type Pointer struct {
+ a *analysis
+ n nodeid
+}
+
+// A PointsToSet is a set of labels (locations or allocations).
+type PointsToSet struct {
+ a *analysis // may be nil if pts is nil
+ pts *nodeset
+}
+
+func (s PointsToSet) String() string {
+ var buf bytes.Buffer
+ buf.WriteByte('[')
+ if s.pts != nil {
+ var space [50]int
+ for i, l := range s.pts.AppendTo(space[:0]) {
+ if i > 0 {
+ buf.WriteString(", ")
+ }
+ buf.WriteString(s.a.labelFor(nodeid(l)).String())
+ }
+ }
+ buf.WriteByte(']')
+ return buf.String()
+}
+
+// PointsTo returns the set of labels that this points-to set
+// contains.
+func (s PointsToSet) Labels() []*Label {
+ var labels []*Label
+ if s.pts != nil {
+ var space [50]int
+ for _, l := range s.pts.AppendTo(space[:0]) {
+ labels = append(labels, s.a.labelFor(nodeid(l)))
+ }
+ }
+ return labels
+}
+
+// If this PointsToSet came from a Pointer of interface kind
+// or a reflect.Value, DynamicTypes returns the set of dynamic
+// types that it may contain. (For an interface, they will
+// always be concrete types.)
+//
+// The result is a mapping whose keys are the dynamic types to which
+// it may point. For each pointer-like key type, the corresponding
+// map value is the PointsToSet for pointers of that type.
+//
+// The result is empty unless CanHaveDynamicTypes(T).
+//
+func (s PointsToSet) DynamicTypes() *typeutil.Map {
+ var tmap typeutil.Map
+ tmap.SetHasher(s.a.hasher)
+ if s.pts != nil {
+ var space [50]int
+ for _, x := range s.pts.AppendTo(space[:0]) {
+ ifaceObjID := nodeid(x)
+ if !s.a.isTaggedObject(ifaceObjID) {
+ continue // !CanHaveDynamicTypes(tDyn)
+ }
+ tDyn, v, indirect := s.a.taggedValue(ifaceObjID)
+ if indirect {
+ panic("indirect tagged object") // implement later
+ }
+ pts, ok := tmap.At(tDyn).(PointsToSet)
+ if !ok {
+ pts = PointsToSet{s.a, new(nodeset)}
+ tmap.Set(tDyn, pts)
+ }
+ pts.pts.addAll(&s.a.nodes[v].solve.pts)
+ }
+ }
+ return &tmap
+}
+
+// Intersects reports whether this points-to set and the
+// argument points-to set contain common members.
+func (s PointsToSet) Intersects(y PointsToSet) bool {
+ if s.pts == nil || y.pts == nil {
+ return false
+ }
+ // This takes Θ(|x|+|y|) time.
+ var z intsets.Sparse
+ z.Intersection(&s.pts.Sparse, &y.pts.Sparse)
+ return !z.IsEmpty()
+}
+
+func (p Pointer) String() string {
+ return fmt.Sprintf("n%d", p.n)
+}
+
+// PointsTo returns the points-to set of this pointer.
+func (p Pointer) PointsTo() PointsToSet {
+ if p.n == 0 {
+ return PointsToSet{}
+ }
+ return PointsToSet{p.a, &p.a.nodes[p.n].solve.pts}
+}
+
+// MayAlias reports whether the receiver pointer may alias
+// the argument pointer.
+func (p Pointer) MayAlias(q Pointer) bool {
+ return p.PointsTo().Intersects(q.PointsTo())
+}
+
+// DynamicTypes returns p.PointsTo().DynamicTypes().
+func (p Pointer) DynamicTypes() *typeutil.Map {
+ return p.PointsTo().DynamicTypes()
+}