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  }