Skip to content

Commit db56db0

Browse files
Groxxchavacava
andauthored
Capture yet more bad defer / recover patterns (#719)
* Yet more bad defer / recover patterns `recover()` is an eternal font of excitement. * demonstrating another flavor of failure * removing leftover code * update documentation * removes test not related to the rule itself Co-authored-by: chavacava <[email protected]>
1 parent 0f4df1c commit db56db0

File tree

3 files changed

+37
-9
lines changed

3 files changed

+37
-9
lines changed

RULES_DESCRIPTIONS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,14 @@ _Configuration_: N/A
233233
## defer
234234

235235
_Description_: This rule warns on some common mistakes when using `defer` statement. It currently alerts on the following situations:
236+
236237
| name | description |
237238
|------|-------------|
238239
| call-chain| even if deferring call-chains of the form `foo()()` is valid, it does not helps code understanding (only the last call is deferred)|
239240
|loop | deferring inside loops can be misleading (deferred functions are not executed at the end of the loop iteration but of the current function) and it could lead to exhausting the execution stack |
240241
| method-call| deferring a call to a method can lead to subtle bugs if the method does not have a pointer receiver|
241242
| recover | calling `recover` outside a deferred function has no effect|
243+
| immediate-recover | calling `recover` at the time a defer is registered, rather than as part of the deferred callback. e.g. `defer recover()` or equivalent.|
242244
| return | returning values form a deferred function has no effect|
243245

244246
These gotchas are described [here](https://blog.learngoprogramming.com/gotchas-of-defer-in-go-1-8d070894cb01)

rule/defer.go

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,12 @@ func (*DeferRule) Name() string {
4545
func (*DeferRule) allowFromArgs(args lint.Arguments) map[string]bool {
4646
if len(args) < 1 {
4747
allow := map[string]bool{
48-
"loop": true,
49-
"call-chain": true,
50-
"method-call": true,
51-
"return": true,
52-
"recover": true,
48+
"loop": true,
49+
"call-chain": true,
50+
"method-call": true,
51+
"return": true,
52+
"recover": true,
53+
"immediate-recover": true,
5354
}
5455

5556
return allow
@@ -97,11 +98,29 @@ func (w lintDeferRule) Visit(node ast.Node) ast.Visitor {
9798
}
9899
case *ast.CallExpr:
99100
if !w.inADefer && isIdent(n.Fun, "recover") {
101+
// func fn() { recover() }
102+
//
100103
// confidence is not 1 because recover can be in a function that is deferred elsewhere
101104
w.newFailure("recover must be called inside a deferred function", n, 0.8, "logic", "recover")
105+
} else if w.inADefer && !w.inAFuncLit && isIdent(n.Fun, "recover") {
106+
// defer helper(recover())
107+
//
108+
// confidence is not truly 1 because this could be in a correctly-deferred func,
109+
// but it is very likely to be a misunderstanding of defer's behavior around arguments.
110+
w.newFailure("recover must be called inside a deferred function, this is executing recover immediately", n, 1, "logic", "immediate-recover")
102111
}
103112
case *ast.DeferStmt:
113+
if isIdent(n.Call.Fun, "recover") {
114+
// defer recover()
115+
//
116+
// confidence is not truly 1 because this could be in a correctly-deferred func,
117+
// but normally this doesn't suppress a panic, and even if it did it would silently discard the value.
118+
w.newFailure("recover must be called inside a deferred function, this is executing recover immediately", n, 1, "logic", "immediate-recover")
119+
}
104120
w.visitSubtree(n.Call.Fun, true, false, false)
121+
for _, a := range n.Call.Args {
122+
w.visitSubtree(a, true, false, false) // check arguments, they should not contain recover()
123+
}
105124

106125
if w.inALoop {
107126
w.newFailure("prefer not to defer inside loops", n, 1.0, "bad practice", "loop")
@@ -125,7 +144,7 @@ func (w lintDeferRule) Visit(node ast.Node) ast.Visitor {
125144
}
126145

127146
func (w lintDeferRule) visitSubtree(n ast.Node, inADefer, inALoop, inAFuncLit bool) {
128-
nw := &lintDeferRule{
147+
nw := lintDeferRule{
129148
onFailure: w.onFailure,
130149
inADefer: inADefer,
131150
inALoop: inALoop,

testdata/defer.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,19 @@ func deferrer() {
1717
defer tt.m() // MATCH /be careful when deferring calls to methods without pointer receiver/
1818

1919
defer func() error {
20-
return errors.New("error") //MATCH /return in a defer function has no effect/
20+
return errors.New("error") // MATCH /return in a defer function has no effect/
2121
}()
2222

23-
defer recover()
23+
defer recover() // MATCH /recover must be called inside a deferred function, this is executing recover immediately/
2424

25-
recover() //MATCH /recover must be called inside a deferred function/
25+
recover() // MATCH /recover must be called inside a deferred function/
2626

2727
defer deferrer()
28+
29+
helper := func(_ interface{}) {}
30+
31+
defer helper(recover()) // MATCH /recover must be called inside a deferred function, this is executing recover immediately/
32+
33+
// does not work, but not currently blocked.
34+
defer helper(func() { recover() })
2835
}

0 commit comments

Comments
 (0)