Skip to content

Commit 980c274

Browse files
authored
Merge pull request #681 from owen-mc/new-query/wrapped-error-always-nil
Add query "Wrapped error always nil"
2 parents 0f15d71 + 0cd5e52 commit 980c274

11 files changed

Lines changed: 265 additions & 0 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: newQuery
3+
---
4+
* Added a new query, `go/unexpected-nil-value`, to finds calls to `Wrap` from `pkg/errors` where the error argument is always nil.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import (
4+
"github.com/pkg/errors"
5+
)
6+
7+
func f1(input string) error {
8+
if input == "1" {
9+
return errors.Errorf("error in f1")
10+
}
11+
return nil
12+
}
13+
14+
func f2(input string) (bool, error) {
15+
if input == "2" {
16+
return false, errors.Errorf("error in f2")
17+
}
18+
return true, nil
19+
}
20+
21+
func test1(input string) error {
22+
err := f1(input)
23+
if err != nil {
24+
return errors.Wrap(err, "input is the first non-negative integer")
25+
}
26+
if ok2, _ := f2(input); !ok2 {
27+
return errors.Wrap(err, "input is the second non-negative integer")
28+
}
29+
return nil
30+
}
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 <code>pkg.errors</code> package provides the <code>errors.Wrap</code>
9+
function for annotating an error with a stack trace. When passed
10+
<code>nil</code>, this function returns <code>nil</code>.
11+
12+
When the first parameter to <code>errors.Wrap</code> is <em>always</em>
13+
<code>nil</code>, the function call has no effect and likely indicates a
14+
programming mistake.
15+
16+
A common example of this is when an <code>errors.Wrap(err, "message")</code>
17+
call is copied from an earlier error-handling block in the same function and
18+
used in a subsequent error-handling block that does not check
19+
<code>err</code> in its guard. In this case the return of a <code>nil</code>
20+
value to the caller indicates by convention that the operation succeeded, and
21+
so the failure is masked.
22+
</p>
23+
</overview>
24+
25+
<recommendation>
26+
<p>
27+
Usually an <code>err</code> value is being referenced outside its intended
28+
scope. The problem can be fixed by removing that reference, for example by
29+
changing a call of the form <code>errors.Wrap(err, "message")</code> to
30+
<code>errors.New("message")</code>.
31+
</p>
32+
</recommendation>
33+
34+
<example>
35+
<p>
36+
The example below shows an example where the <code>err</code> value returned
37+
from the call to <code>f1</code> is reused in a later call, when it is known
38+
to be <code>nil</code>:
39+
</p>
40+
41+
<sample src="WrappedErrorAlwaysNil.go" />
42+
43+
<p>
44+
One way of fixing this is to create a new error value with
45+
<code>errors.New</code>:
46+
</p>
47+
48+
<sample src="WrappedErrorAlwaysNilGood.go" />
49+
</example>
50+
51+
<references>
52+
<li>Go errors, github.com/pkg/errors: <a href="https://pkg.go.dev/github.com/pkg/errors#Wrap">errors.Wrap</a></li>
53+
</references>
54+
</qhelp>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* @name Wrapped error is always nil
3+
* @description Finds calls to `Wrap` from `pkg/errors` where the error argument is always nil.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @id go/unexpected-nil-value
7+
* @tags reliability
8+
* correctness
9+
* logic
10+
* @precision high
11+
*/
12+
13+
import go
14+
15+
/** Gets package for `github.com/pkg/errors`. */
16+
string packagePath() { result = package("github.com/pkg/errors", "") }
17+
18+
/**
19+
* An equality test which guarantees that an expression is always `nil`.
20+
*/
21+
class NilTestGuard extends DataFlow::BarrierGuard, DataFlow::EqualityTestNode {
22+
DataFlow::Node otherNode;
23+
24+
NilTestGuard() {
25+
this.getAnOperand() = Builtin::nil().getARead() and
26+
otherNode = this.getAnOperand() and
27+
not otherNode = Builtin::nil().getARead()
28+
}
29+
30+
override predicate checks(Expr e, boolean outcome) {
31+
e = otherNode.asExpr() and
32+
outcome = this.getPolarity()
33+
}
34+
}
35+
36+
/** Gets a use of a local variable that has the value `nil`. */
37+
DataFlow::ExprNode getNilFromLocalVariable() {
38+
exists(SsaVariable ssa, Write w |
39+
w.definesSsaVariable(ssa, Builtin::nil().getARead()) and
40+
result.asInstruction() = ssa.getAUse()
41+
)
42+
}
43+
44+
from DataFlow::Node n
45+
where
46+
n = any(Function f | f.hasQualifiedName(packagePath(), "Wrap")).getACall().getArgument(0) and
47+
(
48+
// ```go
49+
// errors.Wrap(nil, "")
50+
// ```
51+
n = Builtin::nil().getARead()
52+
or
53+
// ```go
54+
// var localVar error = nil
55+
// errors.Wrap(localVar, "")
56+
// ```
57+
n = getNilFromLocalVariable()
58+
or
59+
// ```go
60+
// if err != nil {
61+
// return ...
62+
// }
63+
// if ok2, _ := f2(input); !ok2 {
64+
// return errors.Wrap(err, "")
65+
// }
66+
n = any(NilTestGuard ntg).getAGuardedNode()
67+
)
68+
select n, "The first argument to 'errors.Wrap' is always nil"
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package main
2+
3+
import (
4+
"github.com/pkg/errors"
5+
)
6+
7+
func f1(input string) error {
8+
if input == "1" {
9+
return errors.Errorf("error in f1")
10+
}
11+
return nil
12+
}
13+
14+
func f2(input string) (bool, error) {
15+
if input == "2" {
16+
return false, errors.Errorf("error in f2")
17+
}
18+
return true, nil
19+
}
20+
21+
func test1(input string) error {
22+
err := f1(input)
23+
if err != nil {
24+
return errors.Wrap(err, "input is the first non-negative integer")
25+
}
26+
if ok2, _ := f2(input); !ok2 {
27+
return errors.New("input is the second non-negative integer")
28+
}
29+
return nil
30+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| WrappedErrorAlwaysNil.go:31:22:31:24 | err | The first argument to 'errors.Wrap' is always nil |
2+
| WrappedErrorAlwaysNil.go:41:14:41:16 | nil | The first argument to 'errors.Wrap' is always nil |
3+
| WrappedErrorAlwaysNil.go:45:14:45:16 | err | The first argument to 'errors.Wrap' is always nil |
4+
| WrappedErrorAlwaysNil.go:49:14:49:21 | localErr | The first argument to 'errors.Wrap' is always nil |
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package main
2+
3+
//go:generate depstubber -vendor github.com/pkg/errors "" Errorf,Wrap
4+
5+
import (
6+
"github.com/pkg/errors"
7+
)
8+
9+
func f1(input string) error {
10+
if input == "1" {
11+
return errors.Errorf("error in f1")
12+
}
13+
return nil
14+
}
15+
16+
func f2(input string) (bool, error) {
17+
if input == "2" {
18+
return false, errors.Errorf("error in f2")
19+
}
20+
return true, nil
21+
}
22+
23+
func test1(input string) error {
24+
err := f1(input)
25+
if err != nil {
26+
// GOOD: Wrapped error is always non-nil
27+
return errors.Wrap(err, "")
28+
}
29+
if ok2, _ := f2(input); !ok2 {
30+
// BAD: Wrapped error is always nil
31+
return errors.Wrap(err, "")
32+
}
33+
return nil
34+
}
35+
36+
func test2(err error) {
37+
// GOOD: Wrapped error is not always nil
38+
errors.Wrap(err, "")
39+
40+
// BAD: Wrapped error is always nil
41+
errors.Wrap(nil, "")
42+
43+
err = nil
44+
// BAD: Wrapped error is always nil
45+
errors.Wrap(err, "")
46+
47+
var localErr error = nil
48+
// BAD: Wrapped error is always nil
49+
errors.Wrap(localErr, "")
50+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
InconsistentCode/WrappedErrorAlwaysNil.ql
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module main
2+
3+
go 1.14
4+
5+
require github.com/pkg/errors v0.9.1

ql/test/query-tests/InconsistentCode/WrappedErrorAlwaysNil/vendor/github.com/pkg/errors/stub.go

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)