Skip to content
This repository was archived by the owner on Jan 5, 2023. It is now read-only.

Commit 6d93f48

Browse files
authored
Merge pull request #147 from owen-mc/redundant-recover
Go: Add query for redundant calls to recover
2 parents e57edcc + 275be36 commit 6d93f48

14 files changed

Lines changed: 226 additions & 0 deletions
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
lgtm,codescanning
2+
* A new query "Redundant call to recover" (`go/redundant-recover`) has been added. The query detects calls to `recover` that have no effect.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
The built-in <code>recover</code> function is only useful inside deferred
9+
functions. Calling it in a function that is never deferred means that it will
10+
always return <code>nil</code> and it will never regain control of a panicking
11+
goroutine. The same is true of calling <code>recover</code> directly in a defer
12+
statement.
13+
</p>
14+
</overview>
15+
16+
<recommendation>
17+
<p>
18+
Carefully inspect the code to determine whether it is a mistake that should be
19+
fixed.
20+
</p>
21+
</recommendation>
22+
23+
<example>
24+
<p>
25+
In the example below, the function <code>fun1</code> is intended to recover
26+
from the panic. However, the function that is deferred calls another function,
27+
which then calls <code>recover</code>:
28+
</p>
29+
<sample src="RedundantRecover1.go" />
30+
<p>
31+
This problem can be fixed by deferring the call to the function which calls
32+
<code>recover</code>:
33+
</p>
34+
<sample src="RedundantRecover1Good.go" />
35+
36+
<p>
37+
In the following example, <code>recover</code> is called directly in a defer
38+
statement, which has no effect, so the panic is not caught.
39+
</p>
40+
<sample src="RedundantRecover2.go" />
41+
<p>
42+
We can fix this by instead deferring an anonymous function which calls
43+
<code>recover</code>.
44+
</p>
45+
<sample src="RedundantRecover2Good.go" />
46+
</example>
47+
48+
<references>
49+
<li>
50+
<a href="https://blog.golang.org/defer-panic-and-recover">Defer, Panic, and Recover - The Go Blog</a>.
51+
</li>
52+
</references>
53+
54+
</qhelp>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @name Redundant call to recover
3+
* @description Calling 'recover' in a function which isn't called using a defer
4+
* statement has no effect. Also, putting 'recover' directly in a
5+
* defer statement has no effect.
6+
* @kind problem
7+
* @problem.severity warning
8+
* @id go/redundant-recover
9+
* @tags maintainability
10+
* correctness
11+
* @precision high
12+
*/
13+
14+
import go
15+
16+
predicate isDeferred(DataFlow::CallNode call) {
17+
exists(DeferStmt defer | defer.getCall() = call.asExpr())
18+
}
19+
20+
from DataFlow::CallNode recoverCall, FuncDef f, string msg
21+
where
22+
recoverCall.getTarget() = Builtin::recover() and
23+
f = recoverCall.getEnclosingCallable() and
24+
(
25+
isDeferred(recoverCall) and
26+
msg = "Deferred calls to 'recover' have no effect."
27+
or
28+
not isDeferred(recoverCall) and
29+
exists(f.getACall()) and
30+
not isDeferred(f.getACall()) and
31+
msg = "This call to 'recover' has no effect because $@ is never called using a defer statement."
32+
)
33+
select recoverCall, msg, f, "the enclosing function"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func callRecover1() {
6+
if recover() != nil {
7+
fmt.Printf("recovered")
8+
}
9+
}
10+
11+
func fun1() {
12+
defer func() {
13+
callRecover1()
14+
}()
15+
panic("1")
16+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func callRecover1Good() {
6+
if recover() != nil {
7+
fmt.Printf("recovered")
8+
}
9+
}
10+
11+
func fun1Good() {
12+
defer callRecover1Good()
13+
panic("1")
14+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package main
2+
3+
func fun2() {
4+
defer recover()
5+
panic("2")
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package main
2+
3+
func fun2Good() {
4+
defer func() { recover() }()
5+
panic("2")
6+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
| RedundantRecover1.go:6:5:6:13 | call to recover | This call to 'recover' has no effect because $@ is never called using a defer statement. | RedundantRecover1.go:5:1:9:1 | function declaration | the enclosing function |
2+
| RedundantRecover2.go:4:8:4:16 | call to recover | Deferred calls to 'recover' have no effect. | RedundantRecover2.go:3:1:6:1 | function declaration | the enclosing function |
3+
| tst.go:8:5:8:13 | call to recover | This call to 'recover' has no effect because $@ is never called using a defer statement. | tst.go:5:1:11:1 | function declaration | the enclosing function |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
RedundantCode/RedundantRecover.ql
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package main
2+
3+
import "fmt"
4+
5+
func callRecover1() {
6+
if recover() != nil {
7+
fmt.Printf("recovered")
8+
}
9+
}
10+
11+
func fun1() {
12+
defer func() {
13+
callRecover1()
14+
}()
15+
panic("1")
16+
}

0 commit comments

Comments
 (0)