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
|
// TODO: doc
package defererr
import (
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/analysis"
)
var Analyzer = &analysis.Analyzer{
Name: "defererr",
Doc: "reports issues returning errors from defer",
Run: run,
}
func run(pass *analysis.Pass) (interface{}, error) {
// TODO: Find defer closure
// Does it set error defined in outer scope?
// Does outer scope declare error variable in signature?
// Is err variable returned after closure?
for _, file := range pass.Files {
checkFunctions(pass, file)
}
return nil, nil
}
type functionState struct {
firstErrorDeferEndPos token.Pos
deferErrorVar *ast.Ident
}
func newFunctionState() *functionState {
return &functionState{
firstErrorDeferEndPos: -1,
}
}
func (s *functionState) setFirstErrorDeferEndPos(pos token.Pos) {
if s.firstErrorDeferEndPos != -1 {
return
}
s.firstErrorDeferEndPos = pos
}
func (s *functionState) deferAssignsError() bool {
return s.deferErrorVar != nil
}
func checkFunctions(pass *analysis.Pass, node ast.Node) {
ast.Inspect(
node,
func(node ast.Node) bool {
// Begin by looking at each declared function.
funcDecl, ok := node.(*ast.FuncDecl)
if !ok {
return true
}
// var buf bytes.Buffer
// err := printer.Fprint(&buf, pass.Fset, funcDecl)
// if err != nil {
// panic(err)
// }
// fmt.Println(buf.String())
// Since we only care about functions that return errors, ignore
// those that don't have return values.
if funcDecl.Type.Results == nil {
return true
}
// Look for a return value that has an `error` type. Store the
// index of the last one.
errorReturnIndex := -1
for i, returnVal := range funcDecl.Type.Results.List {
returnIdent, ok := returnVal.Type.(*ast.Ident)
if !ok {
continue
}
if returnIdent.Name == "error" {
errorReturnIndex = i
}
}
// If the function doesn't return an error, ignore the function.
if errorReturnIndex == -1 {
return true
}
// Get the error return field in case we need to report a problem
// with error declaration.
errorReturnField := funcDecl.Type.Results.List[errorReturnIndex]
fState := newFunctionState()
checkFunctionBody(
pass,
funcDecl.Body,
errorReturnField,
fState,
)
// Stop if the `defer` closure does not assign to an error
// variable.
if !fState.deferAssignsError() {
return true
}
checkFunctionReturns(pass, funcDecl.Body, errorReturnIndex, fState)
return true
},
)
}
// TODO: doc
func checkFunctionBody(
pass *analysis.Pass,
funcBody *ast.BlockStmt,
errorReturnField *ast.Field,
fState *functionState,
) {
ast.Inspect(
funcBody,
func(node ast.Node) bool {
deferStmt, ok := node.(*ast.DeferStmt)
if !ok {
return true
}
// Get a function closure run by `defer`.
funcLit, ok := deferStmt.Call.Fun.(*ast.FuncLit)
if !ok {
return true
}
checkErrorAssignedInDefer(pass, funcLit, errorReturnField, fState)
return true
},
)
}
// TODO: doc
func checkFunctionReturns(
pass *analysis.Pass,
funcBody *ast.BlockStmt,
errorReturnIndex int,
fState *functionState,
) {
ast.Inspect(
funcBody,
func(node ast.Node) bool {
returnStmt, ok := node.(*ast.ReturnStmt)
if !ok {
return true
}
// Ignore any `return` statements before the end of the `defer`
// closure.
if returnStmt.Pos() <= fState.firstErrorDeferEndPos {
return true
}
if returnStmt.Results == nil {
return true
}
// Get the value used when returning the error.
returnErrorExpr := returnStmt.Results[errorReturnIndex]
returnErrorIdent, ok := returnErrorExpr.(*ast.Ident)
if !ok {
return true
}
_, isReturnErrorNamedType :=
pass.TypesInfo.Types[returnErrorExpr].Type.(*types.Named)
// Ensure the value used when returning the error is a named type
// (checking that no nil constant is used), and that the name of
// the error variable used in the `return` statement matches the
// name of the error variable assigned in the `defer` closure.
if !isReturnErrorNamedType ||
returnErrorIdent.Name != fState.deferErrorVar.Name {
pass.Reportf(
returnErrorIdent.Pos(),
"does not return '%s'",
fState.deferErrorVar,
)
}
return true
},
)
}
// TODO: doc
func checkErrorAssignedInDefer(
pass *analysis.Pass,
deferFuncLit *ast.FuncLit,
errorReturnField *ast.Field,
fState *functionState,
) {
ast.Inspect(
deferFuncLit.Body,
func(node ast.Node) bool {
// Look for error assignments in the defer closure.
assignStmt, ok := node.(*ast.AssignStmt)
if !ok {
return true
}
// Ensure the assignment is not `:=`, or `token.DEFINE`. We only
// want to look for issues if the closure sets an error variable
// declared outside its scope.
if assignStmt.Tok == token.DEFINE {
return true
}
// This sentinel tracks whether we've seen an assignment to an
// error variable in this node in the closure.
deferAssignsError := false
for _, lhsExpr := range assignStmt.Lhs {
lhsIdent, ok := lhsExpr.(*ast.Ident)
if !ok {
continue
}
if lhsIdent.Obj.Decl == nil {
continue
}
// Get the lhs variable's declaration so we can find out
// whether it was declared in the closure using a `token.VAR`
// declaration.
var lhsIdentDecl ast.Node = lhsIdent.Obj.Decl.(ast.Node)
// If this lhs was declared inside the defer closure, it should
// be ignored, as it doesn't set data in the parent scope.
if isVariableDeclaredInsideDeferClosure(deferFuncLit, lhsIdentDecl) {
continue
}
// Get the type of the lhs.
lhsNamedType, ok := pass.TypesInfo.Types[lhsExpr].Type.(*types.Named)
if !ok {
continue
}
// We only care about lhs with an `error` type.
if lhsNamedType.Obj().Name() != "error" {
continue
}
deferAssignsError = true
// Store `lhsIdent` so we can reference its name when reporting
// issues with subsequent `return` statements.
fState.deferErrorVar = lhsIdent
// Check whether the lhs variable name is the same as one of
// the error variables declared in the function's return
// signature.
isErrorNameInReturnSignature := false
for _, errorReturnIdent := range errorReturnField.Names {
if lhsIdent.Name == errorReturnIdent.Name {
isErrorNameInReturnSignature = true
}
}
// If the variable name doesn't match any declared in the
// function's return signature, report a diagnostic on the
// return signature, requiring the error variable to be
// declared with the error variable name used in the closure
// assignment.
if !isErrorNameInReturnSignature {
pass.Reportf(
errorReturnField.Pos(),
// TODO: Get the actual signature and set the error
// name in front of the error type.
"return signature should be '(%s error)'",
lhsIdent,
)
break
}
}
if !deferAssignsError {
return true
}
// Store the position of the end of the `defer` closure. We will
// only verify `return` statements occurring after this position.
//
// Do this only if the `defer` closure contained an error
// assignment.
fState.setFirstErrorDeferEndPos(deferFuncLit.Body.Rbrace)
return true
},
)
}
// TODO: doc
func isVariableDeclaredInsideDeferClosure(
deferFuncLit *ast.FuncLit,
variableDecl ast.Node,
) bool {
return deferFuncLit.Body.Lbrace < variableDecl.Pos() &&
variableDecl.Pos() < deferFuncLit.Body.Rbrace
}
|