|
|
// Copyright 2014 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 main
import ( "fmt" "go/ast" "go/token" "go/types" "sort"
"golang.org/x/tools/cmd/guru/serial" "golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/loader" "golang.org/x/tools/go/pointer" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/ssautil" )
var builtinErrorType = types.Universe.Lookup("error").Type()
// whicherrs takes an position to an error and tries to find all types, constants
// and global value which a given error can point to and which can be checked from the
// scope where the error lives.
// In short, it returns a list of things that can be checked against in order to handle
// an error properly.
//
// TODO(dmorsing): figure out if fields in errors like *os.PathError.Err
// can be queried recursively somehow.
func whicherrs(q *Query) error { lconf := loader.Config{Build: q.Build}
if err := setPTAScope(&lconf, q.Scope); err != nil { return err }
// Load/parse/type-check the program.
lprog, err := loadWithSoftErrors(&lconf) if err != nil { return err }
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
if err != nil { return err }
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { return err }
path, action := findInterestingNode(qpos.info, qpos.path) if action != actionExpr { return fmt.Errorf("whicherrs wants an expression; got %s", astutil.NodeDescription(qpos.path[0])) } var expr ast.Expr var obj types.Object switch n := path[0].(type) { case *ast.ValueSpec: // ambiguous ValueSpec containing multiple names
return fmt.Errorf("multiple value specification") case *ast.Ident: obj = qpos.info.ObjectOf(n) expr = n case ast.Expr: expr = n default: return fmt.Errorf("unexpected AST for expr: %T", n) }
typ := qpos.info.TypeOf(expr) if !types.Identical(typ, builtinErrorType) { return fmt.Errorf("selection is not an expression of type 'error'") } // Determine the ssa.Value for the expression.
var value ssa.Value if obj != nil { // def/ref of func/var object
value, _, err = ssaValueForIdent(prog, qpos.info, obj, path) } else { value, _, err = ssaValueForExpr(prog, qpos.info, path) } if err != nil { return err // e.g. trivially dead code
}
// Defer SSA construction till after errors are reported.
prog.Build()
globals := findVisibleErrs(prog, qpos) constants := findVisibleConsts(prog, qpos)
res := &whicherrsResult{ qpos: qpos, errpos: expr.Pos(), }
// TODO(adonovan): the following code is heavily duplicated
// w.r.t. "pointsto". Refactor?
// Find the instruction which initialized the
// global error. If more than one instruction has stored to the global
// remove the global from the set of values that we want to query.
allFuncs := ssautil.AllFunctions(prog) for fn := range allFuncs { for _, b := range fn.Blocks { for _, instr := range b.Instrs { store, ok := instr.(*ssa.Store) if !ok { continue } gval, ok := store.Addr.(*ssa.Global) if !ok { continue } gbl, ok := globals[gval] if !ok { continue } // we already found a store to this global
// The normal error define is just one store in the init
// so we just remove this global from the set we want to query
if gbl != nil { delete(globals, gval) } globals[gval] = store.Val } } }
ptaConfig.AddQuery(value) for _, v := range globals { ptaConfig.AddQuery(v) }
ptares := ptrAnalysis(ptaConfig) valueptr := ptares.Queries[value] if valueptr == (pointer.Pointer{}) { return fmt.Errorf("pointer analysis did not find expression (dead code?)") } for g, v := range globals { ptr, ok := ptares.Queries[v] if !ok { continue } if !ptr.MayAlias(valueptr) { continue } res.globals = append(res.globals, g) } pts := valueptr.PointsTo() dedup := make(map[*ssa.NamedConst]bool) for _, label := range pts.Labels() { // These values are either MakeInterfaces or reflect
// generated interfaces. For the purposes of this
// analysis, we don't care about reflect generated ones
makeiface, ok := label.Value().(*ssa.MakeInterface) if !ok { continue } constval, ok := makeiface.X.(*ssa.Const) if !ok { continue } c := constants[*constval] if c != nil && !dedup[c] { dedup[c] = true res.consts = append(res.consts, c) } } concs := pts.DynamicTypes() concs.Iterate(func(conc types.Type, _ interface{}) { // go/types is a bit annoying here.
// We want to find all the types that we can
// typeswitch or assert to. This means finding out
// if the type pointed to can be seen by us.
//
// For the purposes of this analysis, we care only about
// TypeNames of Named or pointer-to-Named types.
// We ignore other types (e.g. structs) that implement error.
var name *types.TypeName switch t := conc.(type) { case *types.Pointer: named, ok := t.Elem().(*types.Named) if !ok { return } name = named.Obj() case *types.Named: name = t.Obj() default: return } if !isAccessibleFrom(name, qpos.info.Pkg) { return } res.types = append(res.types, &errorType{conc, name}) }) sort.Sort(membersByPosAndString(res.globals)) sort.Sort(membersByPosAndString(res.consts)) sort.Sort(sorterrorType(res.types))
q.Output(lprog.Fset, res) return nil }
// findVisibleErrs returns a mapping from each package-level variable of type "error" to nil.
func findVisibleErrs(prog *ssa.Program, qpos *queryPos) map[*ssa.Global]ssa.Value { globals := make(map[*ssa.Global]ssa.Value) for _, pkg := range prog.AllPackages() { for _, mem := range pkg.Members { gbl, ok := mem.(*ssa.Global) if !ok { continue } gbltype := gbl.Type() // globals are always pointers
if !types.Identical(deref(gbltype), builtinErrorType) { continue } if !isAccessibleFrom(gbl.Object(), qpos.info.Pkg) { continue } globals[gbl] = nil } } return globals }
// findVisibleConsts returns a mapping from each package-level constant assignable to type "error", to nil.
func findVisibleConsts(prog *ssa.Program, qpos *queryPos) map[ssa.Const]*ssa.NamedConst { constants := make(map[ssa.Const]*ssa.NamedConst) for _, pkg := range prog.AllPackages() { for _, mem := range pkg.Members { obj, ok := mem.(*ssa.NamedConst) if !ok { continue } consttype := obj.Type() if !types.AssignableTo(consttype, builtinErrorType) { continue } if !isAccessibleFrom(obj.Object(), qpos.info.Pkg) { continue } constants[*obj.Value] = obj } }
return constants }
type membersByPosAndString []ssa.Member
func (a membersByPosAndString) Len() int { return len(a) } func (a membersByPosAndString) Less(i, j int) bool { cmp := a[i].Pos() - a[j].Pos() return cmp < 0 || cmp == 0 && a[i].String() < a[j].String() } func (a membersByPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type sorterrorType []*errorType
func (a sorterrorType) Len() int { return len(a) } func (a sorterrorType) Less(i, j int) bool { cmp := a[i].obj.Pos() - a[j].obj.Pos() return cmp < 0 || cmp == 0 && a[i].typ.String() < a[j].typ.String() } func (a sorterrorType) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
type errorType struct { typ types.Type // concrete type N or *N that implements error
obj *types.TypeName // the named type N
}
type whicherrsResult struct { qpos *queryPos errpos token.Pos globals []ssa.Member consts []ssa.Member types []*errorType }
func (r *whicherrsResult) PrintPlain(printf printfFunc) { if len(r.globals) > 0 { printf(r.qpos, "this error may point to these globals:") for _, g := range r.globals { printf(g.Pos(), "\t%s", g.RelString(r.qpos.info.Pkg)) } } if len(r.consts) > 0 { printf(r.qpos, "this error may contain these constants:") for _, c := range r.consts { printf(c.Pos(), "\t%s", c.RelString(r.qpos.info.Pkg)) } } if len(r.types) > 0 { printf(r.qpos, "this error may contain these dynamic types:") for _, t := range r.types { printf(t.obj.Pos(), "\t%s", r.qpos.typeString(t.typ)) } } }
func (r *whicherrsResult) JSON(fset *token.FileSet) []byte { we := &serial.WhichErrs{} we.ErrPos = fset.Position(r.errpos).String() for _, g := range r.globals { we.Globals = append(we.Globals, fset.Position(g.Pos()).String()) } for _, c := range r.consts { we.Constants = append(we.Constants, fset.Position(c.Pos()).String()) } for _, t := range r.types { var et serial.WhichErrsType et.Type = r.qpos.typeString(t.typ) et.Position = fset.Position(t.obj.Pos()).String() we.Types = append(we.Types, et) } return toJSON(we) }
|