// 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() }