// 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" "code.google.com/p/go.tools/call" "code.google.com/p/go.tools/go/types/typemap" "code.google.com/p/go.tools/ssa" ) // A Config formulates a pointer analysis problem for Analyze(). 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. 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 // Print is invoked during the analysis for each discovered // call to the built-in print(x), providing a convenient way // to identify arbitrary expressions of interest in the tests. // // 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.) // // CanPoint(site.Args[0].Type()) reports whether p is // pointerlike. // Print func(site *ssa.CallCommon, p Pointer) // 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 // Results.{Indirect,}Queries map when it creates the pointer // variable for v or *v. Upon completion the client can // inspect that map for the results. // // If a Value belongs to a function that the analysis treats // context-sensitively, the corresponding Results.{Indirect,}Queries // slice may have multiple Pointers, one per distinct context. // Use PointsToCombined to merge them. // // TODO(adonovan): this API doesn't scale well for batch tools // that want to dump the entire solution. // // TODO(adonovan): need we distinguish contexts? Current // clients always combine them. // Queries map[ssa.Value]struct{} IndirectQueries map[ssa.Value]struct{} // If Log is non-nil, log messages are written to it. // Logging is extremely verbose. Log io.Writer } // AddQuery adds v to Config.Queries. func (c *Config) AddQuery(v ssa.Value) { if c.Queries == nil { c.Queries = make(map[ssa.Value]struct{}) } c.Queries[v] = struct{}{} } // AddQuery adds v to Config.IndirectQueries. func (c *Config) AddIndirectQuery(v ssa.Value) { if c.IndirectQueries == nil { c.IndirectQueries = make(map[ssa.Value]struct{}) } c.IndirectQueries[v] = struct{}{} } 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 call.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 pointerlike values. // // A pointer doesn't have a unique type because pointers of distinct // types may alias the same object. // type Pointer struct { a *analysis cgn *cgnode n nodeid // non-zero } // A PointsToSet is a set of labels (locations or allocations). type PointsToSet struct { a *analysis // may be nil if pts is nil pts nodeset } // Union returns the set containing all the elements of each set in sets. func Union(sets ...PointsToSet) PointsToSet { var union PointsToSet for _, set := range sets { 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...) } func (s PointsToSet) String() string { var buf bytes.Buffer fmt.Fprintf(&buf, "[") sep := "" for l := range s.pts { fmt.Fprintf(&buf, "%s%s", sep, s.a.labelFor(l)) sep = ", " } fmt.Fprintf(&buf, "]") return buf.String() } // PointsTo returns the set of labels that this points-to set // contains. func (s PointsToSet) Labels() []*Label { var labels []*Label for l := range s.pts { labels = append(labels, s.a.labelFor(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 a set of pointer abstractions of // that dynamic type, represented as a []Pointer slice. Use // PointsToCombined to merge them. // // The result is empty unless CanHaveDynamicTypes(T). // func (s PointsToSet) DynamicTypes() *typemap.M { var tmap typemap.M tmap.SetHasher(s.a.hasher) for ifaceObjId := range s.pts { if !s.a.isTaggedObject(ifaceObjId) { continue // !CanHaveDynamicTypes(tDyn) } tDyn, v, indirect := s.a.taggedValue(ifaceObjId) if indirect { panic("indirect tagged object") // implement later } prev, _ := tmap.At(tDyn).([]Pointer) tmap.Set(tDyn, append(prev, Pointer{s.a, nil, v})) } return &tmap } // Intersects reports whether this points-to set and the // argument points-to set contain common members. func (x PointsToSet) Intersects(y PointsToSet) bool { for l := range x.pts { if _, ok := y.pts[l]; ok { return true } } return false } func (p Pointer) String() string { return fmt.Sprintf("n%d", p.n) } // Context returns the context of this pointer, // if it corresponds to a local variable. func (p Pointer) Context() call.GraphNode { return p.cgn } // PointsTo returns the points-to set of this pointer. func (p Pointer) PointsTo() PointsToSet { return PointsToSet{p.a, p.a.nodes[p.n].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() *typemap.M { return p.PointsTo().DynamicTypes() }