github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/oracle/whicherrs14.go (about)

     1  // Copyright 2014 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  // +build !go1.5
     6  
     7  package oracle
     8  
     9  import (
    10  	"fmt"
    11  	"go/ast"
    12  	"go/token"
    13  	"sort"
    14  
    15  	"golang.org/x/tools/go/ast/astutil"
    16  	"golang.org/x/tools/go/loader"
    17  	"golang.org/x/tools/go/ssa"
    18  	"golang.org/x/tools/go/ssa/ssautil"
    19  	"golang.org/x/tools/go/types"
    20  	"golang.org/x/tools/oracle/serial"
    21  )
    22  
    23  var builtinErrorType = types.Universe.Lookup("error").Type()
    24  
    25  // whicherrs takes an position to an error and tries to find all types, constants
    26  // and global value which a given error can point to and which can be checked from the
    27  // scope where the error lives.
    28  // In short, it returns a list of things that can be checked against in order to handle
    29  // an error properly.
    30  //
    31  // TODO(dmorsing): figure out if fields in errors like *os.PathError.Err
    32  // can be queried recursively somehow.
    33  func whicherrs(q *Query) error {
    34  	lconf := loader.Config{Build: q.Build}
    35  
    36  	if err := setPTAScope(&lconf, q.Scope); err != nil {
    37  		return err
    38  	}
    39  
    40  	// Load/parse/type-check the program.
    41  	lprog, err := lconf.Load()
    42  	if err != nil {
    43  		return err
    44  	}
    45  	q.Fset = lprog.Fset
    46  
    47  	qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
    53  
    54  	ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
    55  	if err != nil {
    56  		return err
    57  	}
    58  
    59  	path, action := findInterestingNode(qpos.info, qpos.path)
    60  	if action != actionExpr {
    61  		return fmt.Errorf("whicherrs wants an expression; got %s",
    62  			astutil.NodeDescription(qpos.path[0]))
    63  	}
    64  	var expr ast.Expr
    65  	var obj types.Object
    66  	switch n := path[0].(type) {
    67  	case *ast.ValueSpec:
    68  		// ambiguous ValueSpec containing multiple names
    69  		return fmt.Errorf("multiple value specification")
    70  	case *ast.Ident:
    71  		obj = qpos.info.ObjectOf(n)
    72  		expr = n
    73  	case ast.Expr:
    74  		expr = n
    75  	default:
    76  		return fmt.Errorf("unexpected AST for expr: %T", n)
    77  	}
    78  
    79  	typ := qpos.info.TypeOf(expr)
    80  	if !types.Identical(typ, builtinErrorType) {
    81  		return fmt.Errorf("selection is not an expression of type 'error'")
    82  	}
    83  	// Determine the ssa.Value for the expression.
    84  	var value ssa.Value
    85  	if obj != nil {
    86  		// def/ref of func/var object
    87  		value, _, err = ssaValueForIdent(prog, qpos.info, obj, path)
    88  	} else {
    89  		value, _, 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  	globals := findVisibleErrs(prog, qpos)
    99  	constants := findVisibleConsts(prog, qpos)
   100  
   101  	res := &whicherrsResult{
   102  		qpos:   qpos,
   103  		errpos: expr.Pos(),
   104  	}
   105  
   106  	// TODO(adonovan): the following code is heavily duplicated
   107  	// w.r.t.  "pointsto".  Refactor?
   108  
   109  	// Find the instruction which initialized the
   110  	// global error. If more than one instruction has stored to the global
   111  	// remove the global from the set of values that we want to query.
   112  	allFuncs := ssautil.AllFunctions(prog)
   113  	for fn := range allFuncs {
   114  		for _, b := range fn.Blocks {
   115  			for _, instr := range b.Instrs {
   116  				store, ok := instr.(*ssa.Store)
   117  				if !ok {
   118  					continue
   119  				}
   120  				gval, ok := store.Addr.(*ssa.Global)
   121  				if !ok {
   122  					continue
   123  				}
   124  				gbl, ok := globals[gval]
   125  				if !ok {
   126  					continue
   127  				}
   128  				// we already found a store to this global
   129  				// The normal error define is just one store in the init
   130  				// so we just remove this global from the set we want to query
   131  				if gbl != nil {
   132  					delete(globals, gval)
   133  				}
   134  				globals[gval] = store.Val
   135  			}
   136  		}
   137  	}
   138  
   139  	ptaConfig.AddQuery(value)
   140  	for _, v := range globals {
   141  		ptaConfig.AddQuery(v)
   142  	}
   143  
   144  	ptares := ptrAnalysis(ptaConfig)
   145  	valueptr := ptares.Queries[value]
   146  	for g, v := range globals {
   147  		ptr, ok := ptares.Queries[v]
   148  		if !ok {
   149  			continue
   150  		}
   151  		if !ptr.MayAlias(valueptr) {
   152  			continue
   153  		}
   154  		res.globals = append(res.globals, g)
   155  	}
   156  	pts := valueptr.PointsTo()
   157  	dedup := make(map[*ssa.NamedConst]bool)
   158  	for _, label := range pts.Labels() {
   159  		// These values are either MakeInterfaces or reflect
   160  		// generated interfaces. For the purposes of this
   161  		// analysis, we don't care about reflect generated ones
   162  		makeiface, ok := label.Value().(*ssa.MakeInterface)
   163  		if !ok {
   164  			continue
   165  		}
   166  		constval, ok := makeiface.X.(*ssa.Const)
   167  		if !ok {
   168  			continue
   169  		}
   170  		c := constants[*constval]
   171  		if c != nil && !dedup[c] {
   172  			dedup[c] = true
   173  			res.consts = append(res.consts, c)
   174  		}
   175  	}
   176  	concs := pts.DynamicTypes()
   177  	concs.Iterate(func(conc types.Type, _ interface{}) {
   178  		// go/types is a bit annoying here.
   179  		// We want to find all the types that we can
   180  		// typeswitch or assert to. This means finding out
   181  		// if the type pointed to can be seen by us.
   182  		//
   183  		// For the purposes of this analysis, the type is always
   184  		// either a Named type or a pointer to one.
   185  		// There are cases where error can be implemented
   186  		// by unnamed types, but in that case, we can't assert to
   187  		// it, so we don't care about it for this analysis.
   188  		var name *types.TypeName
   189  		switch t := conc.(type) {
   190  		case *types.Pointer:
   191  			named, ok := t.Elem().(*types.Named)
   192  			if !ok {
   193  				return
   194  			}
   195  			name = named.Obj()
   196  		case *types.Named:
   197  			name = t.Obj()
   198  		default:
   199  			return
   200  		}
   201  		if !isAccessibleFrom(name, qpos.info.Pkg) {
   202  			return
   203  		}
   204  		res.types = append(res.types, &errorType{conc, name})
   205  	})
   206  	sort.Sort(membersByPosAndString(res.globals))
   207  	sort.Sort(membersByPosAndString(res.consts))
   208  	sort.Sort(sorterrorType(res.types))
   209  
   210  	q.result = res
   211  	return nil
   212  }
   213  
   214  // findVisibleErrs returns a mapping from each package-level variable of type "error" to nil.
   215  func findVisibleErrs(prog *ssa.Program, qpos *queryPos) map[*ssa.Global]ssa.Value {
   216  	globals := make(map[*ssa.Global]ssa.Value)
   217  	for _, pkg := range prog.AllPackages() {
   218  		for _, mem := range pkg.Members {
   219  			gbl, ok := mem.(*ssa.Global)
   220  			if !ok {
   221  				continue
   222  			}
   223  			gbltype := gbl.Type()
   224  			// globals are always pointers
   225  			if !types.Identical(deref(gbltype), builtinErrorType) {
   226  				continue
   227  			}
   228  			if !isAccessibleFrom(gbl.Object(), qpos.info.Pkg) {
   229  				continue
   230  			}
   231  			globals[gbl] = nil
   232  		}
   233  	}
   234  	return globals
   235  }
   236  
   237  // findVisibleConsts returns a mapping from each package-level constant assignable to type "error", to nil.
   238  func findVisibleConsts(prog *ssa.Program, qpos *queryPos) map[ssa.Const]*ssa.NamedConst {
   239  	constants := make(map[ssa.Const]*ssa.NamedConst)
   240  	for _, pkg := range prog.AllPackages() {
   241  		for _, mem := range pkg.Members {
   242  			obj, ok := mem.(*ssa.NamedConst)
   243  			if !ok {
   244  				continue
   245  			}
   246  			consttype := obj.Type()
   247  			if !types.AssignableTo(consttype, builtinErrorType) {
   248  				continue
   249  			}
   250  			if !isAccessibleFrom(obj.Object(), qpos.info.Pkg) {
   251  				continue
   252  			}
   253  			constants[*obj.Value] = obj
   254  		}
   255  	}
   256  
   257  	return constants
   258  }
   259  
   260  type membersByPosAndString []ssa.Member
   261  
   262  func (a membersByPosAndString) Len() int { return len(a) }
   263  func (a membersByPosAndString) Less(i, j int) bool {
   264  	cmp := a[i].Pos() - a[j].Pos()
   265  	return cmp < 0 || cmp == 0 && a[i].String() < a[j].String()
   266  }
   267  func (a membersByPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
   268  
   269  type sorterrorType []*errorType
   270  
   271  func (a sorterrorType) Len() int { return len(a) }
   272  func (a sorterrorType) Less(i, j int) bool {
   273  	cmp := a[i].obj.Pos() - a[j].obj.Pos()
   274  	return cmp < 0 || cmp == 0 && a[i].typ.String() < a[j].typ.String()
   275  }
   276  func (a sorterrorType) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
   277  
   278  type errorType struct {
   279  	typ types.Type      // concrete type N or *N that implements error
   280  	obj *types.TypeName // the named type N
   281  }
   282  
   283  type whicherrsResult struct {
   284  	qpos    *queryPos
   285  	errpos  token.Pos
   286  	globals []ssa.Member
   287  	consts  []ssa.Member
   288  	types   []*errorType
   289  }
   290  
   291  func (r *whicherrsResult) display(printf printfFunc) {
   292  	if len(r.globals) > 0 {
   293  		printf(r.qpos, "this error may point to these globals:")
   294  		for _, g := range r.globals {
   295  			printf(g.Pos(), "\t%s", g.RelString(r.qpos.info.Pkg))
   296  		}
   297  	}
   298  	if len(r.consts) > 0 {
   299  		printf(r.qpos, "this error may contain these constants:")
   300  		for _, c := range r.consts {
   301  			printf(c.Pos(), "\t%s", c.RelString(r.qpos.info.Pkg))
   302  		}
   303  	}
   304  	if len(r.types) > 0 {
   305  		printf(r.qpos, "this error may contain these dynamic types:")
   306  		for _, t := range r.types {
   307  			printf(t.obj.Pos(), "\t%s", r.qpos.typeString(t.typ))
   308  		}
   309  	}
   310  }
   311  
   312  func (r *whicherrsResult) toSerial(res *serial.Result, fset *token.FileSet) {
   313  	we := &serial.WhichErrs{}
   314  	we.ErrPos = fset.Position(r.errpos).String()
   315  	for _, g := range r.globals {
   316  		we.Globals = append(we.Globals, fset.Position(g.Pos()).String())
   317  	}
   318  	for _, c := range r.consts {
   319  		we.Constants = append(we.Constants, fset.Position(c.Pos()).String())
   320  	}
   321  	for _, t := range r.types {
   322  		var et serial.WhichErrsType
   323  		et.Type = r.qpos.typeString(t.typ)
   324  		et.Position = fset.Position(t.obj.Pos()).String()
   325  		we.Types = append(we.Types, et)
   326  	}
   327  	res.WhichErrs = we
   328  }