This repository has been archived by the owner on Jan 13, 2024. It is now read-only.
forked from golang/tools
-
Notifications
You must be signed in to change notification settings - Fork 1
/
whicherrs.go
328 lines (296 loc) · 8.6 KB
/
whicherrs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
// 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.
// +build go1.5
package oracle
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"sort"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
"golang.org/x/tools/oracle/serial"
)
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 := lconf.Load()
if err != nil {
return err
}
q.Fset = lprog.Fset
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]
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, the type is always
// either a Named type or a pointer to one.
// There are cases where error can be implemented
// by unnamed types, but in that case, we can't assert to
// it, so we don't care about it for this analysis.
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.result = 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) display(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) toSerial(res *serial.Result, fset *token.FileSet) {
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)
}
res.WhichErrs = we
}