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 | 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 | var 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. |
32 | func whicherrs(q *Query) error { |
33 | lconf := loader.Config{Build: q.Build} |
34 | |
35 | if err := setPTAScope(&lconf, q.Scope); err != nil { |
36 | return err |
37 | } |
38 | |
39 | // Load/parse/type-check the program. |
40 | lprog, err := loadWithSoftErrors(&lconf) |
41 | if err != nil { |
42 | return err |
43 | } |
44 | |
45 | qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos |
46 | if err != nil { |
47 | return err |
48 | } |
49 | |
50 | prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) |
51 | |
52 | ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) |
53 | if err != nil { |
54 | return err |
55 | } |
56 | |
57 | path, action := findInterestingNode(qpos.info, qpos.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(typ, builtinErrorType) { |
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(prog, qpos.info, obj, path) |
86 | } else { |
87 | value, _, err = ssaValueForExpr(prog, qpos.info, path) |
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(prog, qpos) |
97 | constants := findVisibleConsts(prog, qpos) |
98 | |
99 | res := &whicherrsResult{ |
100 | qpos: qpos, |
101 | errpos: expr.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 | store, ok := instr.(*ssa.Store) |
115 | if !ok { |
116 | continue |
117 | } |
118 | gval, ok := store.Addr.(*ssa.Global) |
119 | if !ok { |
120 | continue |
121 | } |
122 | gbl, ok := 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(globals, gval) |
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 g, v := range globals { |
148 | ptr, ok := ptares.Queries[v] |
149 | if !ok { |
150 | continue |
151 | } |
152 | if !ptr.MayAlias(valueptr) { |
153 | continue |
154 | } |
155 | res.globals = append(res.globals, g) |
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 | makeiface, ok := label.Value().(*ssa.MakeInterface) |
164 | if !ok { |
165 | continue |
166 | } |
167 | constval, ok := 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.consts, c) |
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 | named, ok := 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(name, qpos.info.Pkg) { |
201 | return |
202 | } |
203 | res.types = append(res.types, &errorType{conc, name}) |
204 | }) |
205 | sort.Sort(membersByPosAndString(res.globals)) |
206 | sort.Sort(membersByPosAndString(res.consts)) |
207 | sort.Sort(sorterrorType(res.types)) |
208 | |
209 | q.Output(lprog.Fset, res) |
210 | return nil |
211 | } |
212 | |
213 | // findVisibleErrs returns a mapping from each package-level variable of type "error" to nil. |
214 | func findVisibleErrs(prog *ssa.Program, qpos *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 | gbl, ok := 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. |
237 | func findVisibleConsts(prog *ssa.Program, qpos *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 | obj, ok := mem.(*ssa.NamedConst) |
242 | if !ok { |
243 | continue |
244 | } |
245 | consttype := obj.Type() |
246 | if !types.AssignableTo(consttype, builtinErrorType) { |
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 | |
259 | type membersByPosAndString []ssa.Member |
260 | |
261 | func (a membersByPosAndString) Len() int { return len(a) } |
262 | func (a membersByPosAndString) Less(i, j int) bool { |
263 | cmp := a[i].Pos() - a[j].Pos() |
264 | return cmp < 0 || cmp == 0 && a[i].String() < a[j].String() |
265 | } |
266 | func (a membersByPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
267 | |
268 | type sorterrorType []*errorType |
269 | |
270 | func (a sorterrorType) Len() int { return len(a) } |
271 | func (a sorterrorType) Less(i, j int) bool { |
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 | } |
275 | func (a sorterrorType) Swap(i, j int) { a[i], a[j] = a[j], a[i] } |
276 | |
277 | type errorType struct { |
278 | typ types.Type // concrete type N or *N that implements error |
279 | obj *types.TypeName // the named type N |
280 | } |
281 | |
282 | type whicherrsResult struct { |
283 | qpos *queryPos |
284 | errpos token.Pos |
285 | globals []ssa.Member |
286 | consts []ssa.Member |
287 | types []*errorType |
288 | } |
289 | |
290 | func (r *whicherrsResult) PrintPlain(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 | |
311 | func (r *whicherrsResult) JSON(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.Globals, fset.Position(g.Pos()).String()) |
316 | } |
317 | for _, c := range r.consts { |
318 | we.Constants = append(we.Constants, fset.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.Types, et) |
325 | } |
326 | return toJSON(we) |
327 | } |
328 |
Members