aboutsummaryrefslogtreecommitdiffstats
path: root/defererr.go
blob: c43eece170c0c44d46c005e992e4f8945de3dd0b (plain)
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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
// Copyright (c) 2023  Teddy Wing
//
// This file is part of Godefererr.
//
// Godefererr is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Godefererr is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Godefererr. If not, see <https://www.gnu.org/licenses/>.

// Package defererr defines an Analyzer that checks whether an error set in a
// defer closure is correctly returned.
//
// # Analyzer defererr
//
// defererr: report incorrectly returned errors from defer closures.
//
// Errors can be returned from a defer closure by setting a captured error
// variable within the closure. In order for this to work, the error variable
// must be declared in the function signature, and must be returned somewhere
// in the function. This analyzer checks to make sure that captured error
// variables assigned in defer closures are correctly declared and returned.
//
// For example:
//
//	func returnErrorFromDefer() error { // return signature should be '(err error)'
//		var err error = nil
//		if err != nil {
//			return err
//		}
//
//		defer func() {
//			err = errors.New("defer error")
//		}()
//
//		return nil // return should use 'err'
//	}
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) {
	// Look at each function and check if it returns an error.
	// If so, look for a defer inside the function.
	// If a captured error variable is defined in the defer's closure, ensure
	// that the variable is declared in the function's signature, and that any
	// returns after the defer closure use the assigned error variable.

	for _, file := range pass.Files {
		checkFunctions(pass, file)
	}

	return nil, nil
}

// functionState stores information about functions needed to report problems
// returning errors from defer.
type functionState struct {
	// The end position of the first defer closure.
	firstErrorDeferEndPos token.Pos

	// Error variable assigned in defer.
	deferErrorVar *ast.Ident
}

// newFunctionState initialises a new functionState.
func newFunctionState() functionState {
	return functionState{
		firstErrorDeferEndPos: -1,
	}
}

// setFirstErrorDeferEndPos sets the firstErrorDeferEndPos field of s to pos
// unless it has already been set.
func (s *functionState) setFirstErrorDeferEndPos(pos token.Pos) {
	if s.firstErrorDeferEndPos != -1 {
		return
	}

	s.firstErrorDeferEndPos = pos
}

// deferAssignsError returns true if the deferErrorVar field of s was assigned.
func (s *functionState) deferAssignsError() bool {
	return s.deferErrorVar != nil
}

// checkFunctions looks at each function and runs defer error checks.
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
		},
	)
}

// checkFunctionBody looks for defer statements in a function body and runs
// error checks.
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
		},
	)
}

// checkFunctionReturns checks whether the return statements after a defer
// closure use the error assigned in the defer.
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
		},
	)
}

// checkErrorAssignedInDefer checks whether an error value is assigned to a
// captured variable in a defer closure.
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(),
						"return signature should use named error parameter %s",
						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
		},
	)
}

// isVariableDeclaredInsideDeferClosure returns true if the position of
// variableDecl is between the start and end of deferFuncLit.
func isVariableDeclaredInsideDeferClosure(
	deferFuncLit *ast.FuncLit,
	variableDecl ast.Node,
) bool {
	return deferFuncLit.Body.Lbrace < variableDecl.Pos() &&
		variableDecl.Pos() < deferFuncLit.Body.Rbrace
}