github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/cmd/guru/pointsto.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 main 6 7 import ( 8 "fmt" 9 "go/ast" 10 "go/token" 11 "go/types" 12 "sort" 13 14 "golang.org/x/tools/cmd/guru/serial" 15 "golang.org/x/tools/go/ast/astutil" 16 "golang.org/x/tools/go/loader" 17 "golang.org/x/tools/go/pointer" 18 "golang.org/x/tools/go/ssa" 19 "golang.org/x/tools/go/ssa/ssautil" 20 ) 21 22 // pointsto runs the pointer analysis on the selected expression, 23 // and reports its points-to set (for a pointer-like expression) 24 // or its dynamic types (for an interface, reflect.Value, or 25 // reflect.Type expression) and their points-to sets. 26 // 27 // All printed sets are sorted to ensure determinism. 28 func pointsto(q *Query) error { 29 lconf := loader.Config{Build: q.Build} 30 31 if err := setPTAScope(&lconf, q.Scope); err != nil { 32 return err 33 } 34 35 // Load/parse/type-check the program. 36 lprog, err := loadWithSoftErrors(&lconf) 37 if err != nil { 38 return err 39 } 40 41 qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos 42 if err != nil { 43 return err 44 } 45 46 prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) 47 48 ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) 49 if err != nil { 50 return err 51 } 52 53 path, action := findInterestingNode(qpos.info, qpos.path) 54 if action != actionExpr { 55 return fmt.Errorf("pointer analysis wants an expression; got %s", 56 astutil.NodeDescription(qpos.path[0])) 57 } 58 59 var expr ast.Expr 60 var obj types.Object 61 switch n := path[0].(type) { 62 case *ast.ValueSpec: 63 // ambiguous ValueSpec containing multiple names 64 return fmt.Errorf("multiple value specification") 65 case *ast.Ident: 66 obj = qpos.info.ObjectOf(n) 67 expr = n 68 case ast.Expr: 69 expr = n 70 default: 71 // TODO(adonovan): is this reachable? 72 return fmt.Errorf("unexpected AST for expr: %T", n) 73 } 74 75 // Reject non-pointerlike types (includes all constants---except nil). 76 // TODO(adonovan): reject nil too. 77 typ := qpos.info.TypeOf(expr) 78 if !pointer.CanPoint(typ) { 79 return fmt.Errorf("pointer analysis wants an expression of reference type; got %s", typ) 80 } 81 82 // Determine the ssa.Value for the expression. 83 var value ssa.Value 84 var isAddr bool 85 if obj != nil { 86 // def/ref of func/var object 87 value, isAddr, err = ssaValueForIdent(prog, qpos.info, obj, path) 88 } else { 89 value, isAddr, err = ssaValueForExpr(prog, qpos.info, path) 90 } 91 if err != nil { 92 return err // e.g. trivially dead code 93 } 94 95 // Defer SSA construction till after errors are reported. 96 prog.Build() 97 98 // Run the pointer analysis. 99 ptrs, err := runPTA(ptaConfig, value, isAddr) 100 if err != nil { 101 return err // e.g. analytically unreachable 102 } 103 104 q.Output(lprog.Fset, &pointstoResult{ 105 qpos: qpos, 106 typ: typ, 107 ptrs: ptrs, 108 }) 109 return nil 110 } 111 112 // ssaValueForIdent returns the ssa.Value for the ast.Ident whose path 113 // to the root of the AST is path. isAddr reports whether the 114 // ssa.Value is the address denoted by the ast.Ident, not its value. 115 func ssaValueForIdent(prog *ssa.Program, qinfo *loader.PackageInfo, obj types.Object, path []ast.Node) (value ssa.Value, isAddr bool, err error) { 116 switch obj := obj.(type) { 117 case *types.Var: 118 pkg := prog.Package(qinfo.Pkg) 119 pkg.Build() 120 if v, addr := prog.VarValue(obj, pkg, path); v != nil { 121 return v, addr, nil 122 } 123 return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name()) 124 125 case *types.Func: 126 fn := prog.FuncValue(obj) 127 if fn == nil { 128 return nil, false, fmt.Errorf("%s is an interface method", obj) 129 } 130 // TODO(adonovan): there's no point running PTA on a *Func ident. 131 // Eliminate this feature. 132 return fn, false, nil 133 } 134 panic(obj) 135 } 136 137 // ssaValueForExpr returns the ssa.Value of the non-ast.Ident 138 // expression whose path to the root of the AST is path. 139 func ssaValueForExpr(prog *ssa.Program, qinfo *loader.PackageInfo, path []ast.Node) (value ssa.Value, isAddr bool, err error) { 140 pkg := prog.Package(qinfo.Pkg) 141 pkg.SetDebugMode(true) 142 pkg.Build() 143 144 fn := ssa.EnclosingFunction(pkg, path) 145 if fn == nil { 146 return nil, false, fmt.Errorf("no SSA function built for this location (dead code?)") 147 } 148 149 if v, addr := fn.ValueForExpr(path[0].(ast.Expr)); v != nil { 150 return v, addr, nil 151 } 152 153 return nil, false, fmt.Errorf("can't locate SSA Value for expression in %s", fn) 154 } 155 156 // runPTA runs the pointer analysis of the selected SSA value or address. 157 func runPTA(conf *pointer.Config, v ssa.Value, isAddr bool) (ptrs []pointerResult, err error) { 158 T := v.Type() 159 if isAddr { 160 conf.AddIndirectQuery(v) 161 T = deref(T) 162 } else { 163 conf.AddQuery(v) 164 } 165 ptares := ptrAnalysis(conf) 166 167 var ptr pointer.Pointer 168 if isAddr { 169 ptr = ptares.IndirectQueries[v] 170 } else { 171 ptr = ptares.Queries[v] 172 } 173 if ptr == (pointer.Pointer{}) { 174 return nil, fmt.Errorf("pointer analysis did not find expression (dead code?)") 175 } 176 pts := ptr.PointsTo() 177 178 if pointer.CanHaveDynamicTypes(T) { 179 // Show concrete types for interface/reflect.Value expression. 180 if concs := pts.DynamicTypes(); concs.Len() > 0 { 181 concs.Iterate(func(conc types.Type, pta interface{}) { 182 labels := pta.(pointer.PointsToSet).Labels() 183 sort.Sort(byPosAndString(labels)) // to ensure determinism 184 ptrs = append(ptrs, pointerResult{conc, labels}) 185 }) 186 } 187 } else { 188 // Show labels for other expressions. 189 labels := pts.Labels() 190 sort.Sort(byPosAndString(labels)) // to ensure determinism 191 ptrs = append(ptrs, pointerResult{T, labels}) 192 } 193 sort.Sort(byTypeString(ptrs)) // to ensure determinism 194 return ptrs, nil 195 } 196 197 type pointerResult struct { 198 typ types.Type // type of the pointer (always concrete) 199 labels []*pointer.Label // set of labels 200 } 201 202 type pointstoResult struct { 203 qpos *queryPos 204 typ types.Type // type of expression 205 ptrs []pointerResult // pointer info (typ is concrete => len==1) 206 } 207 208 func (r *pointstoResult) PrintPlain(printf printfFunc) { 209 if pointer.CanHaveDynamicTypes(r.typ) { 210 // Show concrete types for interface, reflect.Type or 211 // reflect.Value expression. 212 213 if len(r.ptrs) > 0 { 214 printf(r.qpos, "this %s may contain these dynamic types:", r.qpos.typeString(r.typ)) 215 for _, ptr := range r.ptrs { 216 var obj types.Object 217 if nt, ok := deref(ptr.typ).(*types.Named); ok { 218 obj = nt.Obj() 219 } 220 if len(ptr.labels) > 0 { 221 printf(obj, "\t%s, may point to:", r.qpos.typeString(ptr.typ)) 222 printLabels(printf, ptr.labels, "\t\t") 223 } else { 224 printf(obj, "\t%s", r.qpos.typeString(ptr.typ)) 225 } 226 } 227 } else { 228 printf(r.qpos, "this %s cannot contain any dynamic types.", r.typ) 229 } 230 } else { 231 // Show labels for other expressions. 232 if ptr := r.ptrs[0]; len(ptr.labels) > 0 { 233 printf(r.qpos, "this %s may point to these objects:", 234 r.qpos.typeString(r.typ)) 235 printLabels(printf, ptr.labels, "\t") 236 } else { 237 printf(r.qpos, "this %s may not point to anything.", 238 r.qpos.typeString(r.typ)) 239 } 240 } 241 } 242 243 func (r *pointstoResult) JSON(fset *token.FileSet) []byte { 244 var pts []serial.PointsTo 245 for _, ptr := range r.ptrs { 246 var namePos string 247 if nt, ok := deref(ptr.typ).(*types.Named); ok { 248 namePos = fset.Position(nt.Obj().Pos()).String() 249 } 250 var labels []serial.PointsToLabel 251 for _, l := range ptr.labels { 252 labels = append(labels, serial.PointsToLabel{ 253 Pos: fset.Position(l.Pos()).String(), 254 Desc: l.String(), 255 }) 256 } 257 pts = append(pts, serial.PointsTo{ 258 Type: r.qpos.typeString(ptr.typ), 259 NamePos: namePos, 260 Labels: labels, 261 }) 262 } 263 return toJSON(pts) 264 } 265 266 type byTypeString []pointerResult 267 268 func (a byTypeString) Len() int { return len(a) } 269 func (a byTypeString) Less(i, j int) bool { return a[i].typ.String() < a[j].typ.String() } 270 func (a byTypeString) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 271 272 type byPosAndString []*pointer.Label 273 274 func (a byPosAndString) Len() int { return len(a) } 275 func (a byPosAndString) Less(i, j int) bool { 276 cmp := a[i].Pos() - a[j].Pos() 277 return cmp < 0 || (cmp == 0 && a[i].String() < a[j].String()) 278 } 279 func (a byPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 280 281 func printLabels(printf printfFunc, labels []*pointer.Label, prefix string) { 282 // TODO(adonovan): due to context-sensitivity, many of these 283 // labels may differ only by context, which isn't apparent. 284 for _, label := range labels { 285 printf(label, "%s%s", prefix, label) 286 } 287 }