GoPLS Viewer

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