github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/pointer/api.go (about) 1 // Copyright 2013 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package pointer 6 7 import ( 8 "bytes" 9 "fmt" 10 "go/token" 11 "io" 12 13 "golang.org/x/tools/container/intsets" 14 "golang.org/x/tools/go/callgraph" 15 "golang.org/x/tools/go/ssa" 16 "golang.org/x/tools/go/types/typeutil" 17 ) 18 19 // A Config formulates a pointer analysis problem for Analyze. It is 20 // only usable for a single invocation of Analyze and must not be 21 // reused. 22 type Config struct { 23 // Mains contains the set of 'main' packages to analyze 24 // Clients must provide the analysis with at least one 25 // package defining a main() function. 26 // 27 // Non-main packages in the ssa.Program that are not 28 // dependencies of any main package may still affect the 29 // analysis result, because they contribute runtime types and 30 // thus methods. 31 // 32 // TODO(adonovan): investigate whether this is desirable. 33 // 34 // Calls to generic functions will be unsound unless packages 35 // are built using the ssa.InstantiateGenerics builder mode. 36 Mains []*ssa.Package 37 38 // Reflection determines whether to handle reflection 39 // operators soundly, which is currently rather slow since it 40 // causes constraint to be generated during solving 41 // proportional to the number of constraint variables, which 42 // has not yet been reduced by presolver optimisation. 43 Reflection bool 44 45 // BuildCallGraph determines whether to construct a callgraph. 46 // If enabled, the graph will be available in Result.CallGraph. 47 BuildCallGraph bool 48 49 // The client populates Queries[v] or IndirectQueries[v] 50 // for each ssa.Value v of interest, to request that the 51 // points-to sets pts(v) or pts(*v) be computed. If the 52 // client needs both points-to sets, v may appear in both 53 // maps. 54 // 55 // (IndirectQueries is typically used for Values corresponding 56 // to source-level lvalues, e.g. an *ssa.Global.) 57 // 58 // The analysis populates the corresponding 59 // Result.{Indirect,}Queries map when it creates the pointer 60 // variable for v or *v. Upon completion the client can 61 // inspect that map for the results. 62 // 63 // TODO(adonovan): this API doesn't scale well for batch tools 64 // that want to dump the entire solution. Perhaps optionally 65 // populate a map[*ssa.DebugRef]Pointer in the Result, one 66 // entry per source expression. 67 // 68 Queries map[ssa.Value]struct{} 69 IndirectQueries map[ssa.Value]struct{} 70 extendedQueries map[ssa.Value][]*extendedQuery 71 72 // If Log is non-nil, log messages are written to it. 73 // Logging is extremely verbose. 74 Log io.Writer 75 } 76 77 type track uint32 78 79 const ( 80 trackChan track = 1 << iota // track 'chan' references 81 trackMap // track 'map' references 82 trackPtr // track regular pointers 83 trackSlice // track slice references 84 85 trackAll = ^track(0) 86 ) 87 88 // AddQuery adds v to Config.Queries. 89 // Precondition: CanPoint(v.Type()). 90 func (c *Config) AddQuery(v ssa.Value) { 91 if !CanPoint(v.Type()) { 92 panic(fmt.Sprintf("%s is not a pointer-like value: %s", v, v.Type())) 93 } 94 if c.Queries == nil { 95 c.Queries = make(map[ssa.Value]struct{}) 96 } 97 c.Queries[v] = struct{}{} 98 } 99 100 // AddIndirectQuery adds v to Config.IndirectQueries. 101 // Precondition: CanPoint(v.Type().Underlying().(*types.Pointer).Elem()). 102 func (c *Config) AddIndirectQuery(v ssa.Value) { 103 if c.IndirectQueries == nil { 104 c.IndirectQueries = make(map[ssa.Value]struct{}) 105 } 106 if !CanPoint(mustDeref(v.Type())) { 107 panic(fmt.Sprintf("%s is not the address of a pointer-like value: %s", v, v.Type())) 108 } 109 c.IndirectQueries[v] = struct{}{} 110 } 111 112 // AddExtendedQuery adds an extended, AST-based query on v to the 113 // analysis. The query, which must be a single Go expression, allows 114 // destructuring the value. 115 // 116 // The query must operate on a variable named 'x', which represents 117 // the value, and result in a pointer-like object. Only a subset of 118 // Go expressions are permitted in queries, namely channel receives, 119 // pointer dereferences, field selectors, array/slice/map/tuple 120 // indexing and grouping with parentheses. The specific indices when 121 // indexing arrays, slices and maps have no significance. Indices used 122 // on tuples must be numeric and within bounds. 123 // 124 // All field selectors must be explicit, even ones usually elided 125 // due to promotion of embedded fields. 126 // 127 // The query 'x' is identical to using AddQuery. The query '*x' is 128 // identical to using AddIndirectQuery. 129 // 130 // On success, AddExtendedQuery returns a Pointer to the queried 131 // value. This Pointer will be initialized during analysis. Using it 132 // before analysis has finished has undefined behavior. 133 // 134 // Example: 135 // 136 // // given v, which represents a function call to 'fn() (int, []*T)', and 137 // // 'type T struct { F *int }', the following query will access the field F. 138 // c.AddExtendedQuery(v, "x[1][0].F") 139 func (c *Config) AddExtendedQuery(v ssa.Value, query string) (*Pointer, error) { 140 ops, _, err := parseExtendedQuery(v.Type(), query) 141 if err != nil { 142 return nil, fmt.Errorf("invalid query %q: %s", query, err) 143 } 144 if c.extendedQueries == nil { 145 c.extendedQueries = make(map[ssa.Value][]*extendedQuery) 146 } 147 148 ptr := &Pointer{} 149 c.extendedQueries[v] = append(c.extendedQueries[v], &extendedQuery{ops: ops, ptr: ptr}) 150 return ptr, nil 151 } 152 153 func (c *Config) prog() *ssa.Program { 154 for _, main := range c.Mains { 155 return main.Prog 156 } 157 panic("empty scope") 158 } 159 160 type Warning struct { 161 Pos token.Pos 162 Message string 163 } 164 165 // A Result contains the results of a pointer analysis. 166 // 167 // See Config for how to request the various Result components. 168 type Result struct { 169 CallGraph *callgraph.Graph // discovered call graph 170 Queries map[ssa.Value]Pointer // pts(v) for each v in Config.Queries. 171 IndirectQueries map[ssa.Value]Pointer // pts(*v) for each v in Config.IndirectQueries. 172 Warnings []Warning // warnings of unsoundness 173 } 174 175 // A Pointer is an equivalence class of pointer-like values. 176 // 177 // A Pointer doesn't have a unique type because pointers of distinct 178 // types may alias the same object. 179 type Pointer struct { 180 a *analysis 181 n nodeid 182 } 183 184 // A PointsToSet is a set of labels (locations or allocations). 185 type PointsToSet struct { 186 a *analysis // may be nil if pts is nil 187 pts *nodeset 188 } 189 190 func (s PointsToSet) String() string { 191 var buf bytes.Buffer 192 buf.WriteByte('[') 193 if s.pts != nil { 194 var space [50]int 195 for i, l := range s.pts.AppendTo(space[:0]) { 196 if i > 0 { 197 buf.WriteString(", ") 198 } 199 buf.WriteString(s.a.labelFor(nodeid(l)).String()) 200 } 201 } 202 buf.WriteByte(']') 203 return buf.String() 204 } 205 206 // PointsTo returns the set of labels that this points-to set 207 // contains. 208 func (s PointsToSet) Labels() []*Label { 209 var labels []*Label 210 if s.pts != nil { 211 var space [50]int 212 for _, l := range s.pts.AppendTo(space[:0]) { 213 labels = append(labels, s.a.labelFor(nodeid(l))) 214 } 215 } 216 return labels 217 } 218 219 // If this PointsToSet came from a Pointer of interface kind 220 // or a reflect.Value, DynamicTypes returns the set of dynamic 221 // types that it may contain. (For an interface, they will 222 // always be concrete types.) 223 // 224 // The result is a mapping whose keys are the dynamic types to which 225 // it may point. For each pointer-like key type, the corresponding 226 // map value is the PointsToSet for pointers of that type. 227 // 228 // The result is empty unless CanHaveDynamicTypes(T). 229 func (s PointsToSet) DynamicTypes() *typeutil.Map { 230 var tmap typeutil.Map 231 tmap.SetHasher(s.a.hasher) 232 if s.pts != nil { 233 var space [50]int 234 for _, x := range s.pts.AppendTo(space[:0]) { 235 ifaceObjID := nodeid(x) 236 if !s.a.isTaggedObject(ifaceObjID) { 237 continue // !CanHaveDynamicTypes(tDyn) 238 } 239 tDyn, v, indirect := s.a.taggedValue(ifaceObjID) 240 if indirect { 241 panic("indirect tagged object") // implement later 242 } 243 pts, ok := tmap.At(tDyn).(PointsToSet) 244 if !ok { 245 pts = PointsToSet{s.a, new(nodeset)} 246 tmap.Set(tDyn, pts) 247 } 248 pts.pts.addAll(&s.a.nodes[v].solve.pts) 249 } 250 } 251 return &tmap 252 } 253 254 // Intersects reports whether this points-to set and the 255 // argument points-to set contain common members. 256 func (s PointsToSet) Intersects(y PointsToSet) bool { 257 if s.pts == nil || y.pts == nil { 258 return false 259 } 260 // This takes Θ(|x|+|y|) time. 261 var z intsets.Sparse 262 z.Intersection(&s.pts.Sparse, &y.pts.Sparse) 263 return !z.IsEmpty() 264 } 265 266 func (p Pointer) String() string { 267 return fmt.Sprintf("n%d", p.n) 268 } 269 270 // PointsTo returns the points-to set of this pointer. 271 func (p Pointer) PointsTo() PointsToSet { 272 if p.n == 0 { 273 return PointsToSet{} 274 } 275 return PointsToSet{p.a, &p.a.nodes[p.n].solve.pts} 276 } 277 278 // MayAlias reports whether the receiver pointer may alias 279 // the argument pointer. 280 func (p Pointer) MayAlias(q Pointer) bool { 281 return p.PointsTo().Intersects(q.PointsTo()) 282 } 283 284 // DynamicTypes returns p.PointsTo().DynamicTypes(). 285 func (p Pointer) DynamicTypes() *typeutil.Map { 286 return p.PointsTo().DynamicTypes() 287 }