1
0
mirror of https://github.com/mgechev/revive.git synced 2025-03-19 21:07:46 +02:00

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 <salvadorcavadini+github@gmail.com>
This commit is contained in:
Steven L 2022-07-24 00:34:16 -07:00 committed by GitHub
parent 0f4df1ca40
commit db56db0b6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 37 additions and 9 deletions

View File

@ -233,12 +233,14 @@ _Configuration_: N/A
## defer ## defer
_Description_: This rule warns on some common mistakes when using `defer` statement. It currently alerts on the following situations: _Description_: This rule warns on some common mistakes when using `defer` statement. It currently alerts on the following situations:
| name | description | | name | description |
|------|-------------| |------|-------------|
| 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)| | 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)|
|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 | |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 |
| method-call| deferring a call to a method can lead to subtle bugs if the method does not have a pointer receiver| | method-call| deferring a call to a method can lead to subtle bugs if the method does not have a pointer receiver|
| recover | calling `recover` outside a deferred function has no effect| | recover | calling `recover` outside a deferred function has no effect|
| 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.|
| return | returning values form a deferred function has no effect| | return | returning values form a deferred function has no effect|
These gotchas are described [here](https://blog.learngoprogramming.com/gotchas-of-defer-in-go-1-8d070894cb01) These gotchas are described [here](https://blog.learngoprogramming.com/gotchas-of-defer-in-go-1-8d070894cb01)

View File

@ -50,6 +50,7 @@ func (*DeferRule) allowFromArgs(args lint.Arguments) map[string]bool {
"method-call": true, "method-call": true,
"return": true, "return": true,
"recover": true, "recover": true,
"immediate-recover": true,
} }
return allow return allow
@ -97,11 +98,29 @@ func (w lintDeferRule) Visit(node ast.Node) ast.Visitor {
} }
case *ast.CallExpr: case *ast.CallExpr:
if !w.inADefer && isIdent(n.Fun, "recover") { if !w.inADefer && isIdent(n.Fun, "recover") {
// func fn() { recover() }
//
// confidence is not 1 because recover can be in a function that is deferred elsewhere // confidence is not 1 because recover can be in a function that is deferred elsewhere
w.newFailure("recover must be called inside a deferred function", n, 0.8, "logic", "recover") w.newFailure("recover must be called inside a deferred function", n, 0.8, "logic", "recover")
} else if w.inADefer && !w.inAFuncLit && isIdent(n.Fun, "recover") {
// defer helper(recover())
//
// confidence is not truly 1 because this could be in a correctly-deferred func,
// but it is very likely to be a misunderstanding of defer's behavior around arguments.
w.newFailure("recover must be called inside a deferred function, this is executing recover immediately", n, 1, "logic", "immediate-recover")
} }
case *ast.DeferStmt: case *ast.DeferStmt:
if isIdent(n.Call.Fun, "recover") {
// defer recover()
//
// confidence is not truly 1 because this could be in a correctly-deferred func,
// but normally this doesn't suppress a panic, and even if it did it would silently discard the value.
w.newFailure("recover must be called inside a deferred function, this is executing recover immediately", n, 1, "logic", "immediate-recover")
}
w.visitSubtree(n.Call.Fun, true, false, false) w.visitSubtree(n.Call.Fun, true, false, false)
for _, a := range n.Call.Args {
w.visitSubtree(a, true, false, false) // check arguments, they should not contain recover()
}
if w.inALoop { if w.inALoop {
w.newFailure("prefer not to defer inside loops", n, 1.0, "bad practice", "loop") 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 {
} }
func (w lintDeferRule) visitSubtree(n ast.Node, inADefer, inALoop, inAFuncLit bool) { func (w lintDeferRule) visitSubtree(n ast.Node, inADefer, inALoop, inAFuncLit bool) {
nw := &lintDeferRule{ nw := lintDeferRule{
onFailure: w.onFailure, onFailure: w.onFailure,
inADefer: inADefer, inADefer: inADefer,
inALoop: inALoop, inALoop: inALoop,

13
testdata/defer.go vendored
View File

@ -17,12 +17,19 @@ func deferrer() {
defer tt.m() // MATCH /be careful when deferring calls to methods without pointer receiver/ defer tt.m() // MATCH /be careful when deferring calls to methods without pointer receiver/
defer func() error { defer func() error {
return errors.New("error") //MATCH /return in a defer function has no effect/ return errors.New("error") // MATCH /return in a defer function has no effect/
}() }()
defer recover() defer recover() // MATCH /recover must be called inside a deferred function, this is executing recover immediately/
recover() //MATCH /recover must be called inside a deferred function/ recover() // MATCH /recover must be called inside a deferred function/
defer deferrer() defer deferrer()
helper := func(_ interface{}) {}
defer helper(recover()) // MATCH /recover must be called inside a deferred function, this is executing recover immediately/
// does not work, but not currently blocked.
defer helper(func() { recover() })
} }