Compare commits

...

5 Commits

Author SHA1 Message Date
Michael B. Gale
059d33f033 Fix typo in documentation
Co-authored-by: hubwriter <hubwriter@github.com>
2022-11-28 12:07:15 +00:00
Michael B. Gale
b5b23f12a5 Add change note 2022-11-25 11:07:32 +00:00
Michael B. Gale
5620d247fd Fix query not working for anonymous functions 2022-11-24 13:51:34 +00:00
Michael B. Gale
c907a9e707 Remove unnecessary cast 2022-11-24 13:21:36 +00:00
Michael B. Gale
a71245abc2 Add query to check for deferred errors 2022-11-24 11:01:48 +00:00
8 changed files with 157 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
package main
import (
"io"
"log"
"os"
)
func example() {
defer io.WriteString(os.Stdout, "All done!")
if _, err := io.WriteString(os.Stdout, "Hello"); err != nil {
log.Fatal(err)
}
}

View File

@@ -0,0 +1,45 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
When calling a function which may return an error, we should check whether an error has
occurred and handle it in some meaningful way. Deferring a function call does not allow
us to perform such a check. Therefore, calls to functions which may return errors should
not be deferred.
</p>
</overview>
<recommendation>
<p>
Examine the deferred function call carefully to check whether any errors that may arise should be
handled explicitly.
</p>
</recommendation>
<example>
<p>
In the following example, a call to <code>io.WriteLine</code> is deferred with the intention of
appending some data to a file after some other data has been written to it. However, the call
may not succeed and return and error, as demonstrated in the other use of
<code>io.WriteLine</code>.
</p>
<sample src="DeferredErrorCall.go" />
<p>
To correct this issue, do not defer the function call that may return an error and handle the
error explicitly instead:
</p>
<sample src="DeferredErrorCallGood.go" />
</example>
<references>
<li>The Go Programming Language Specification: <a href="https://go.dev/ref/spec#Defer_statements">Defer statements</a>.</li>
<li>The Go Programming Language Specification: <a href="https://go.dev/ref/spec#Errors">Errors</a>.</li>
</references>
</qhelp>

View File

@@ -0,0 +1,28 @@
/**
* @name Deferred call may return an error
* @description Deferring a call to a function which may return an error means that an error may not be handled.
* @kind problem
* @problem.severity warning
* @id go/deferred-error-call
* @tags maintainability
* correctness
* call
* defer
*/
import go
string getFunctionName(CallExpr call) {
if exists(call.getCalleeName())
then result = call.getCalleeName()
else result = "an anonymous function"
}
from DeferStmt defer, SignatureType sig
where
// match all deferred function calls and obtain their type signatures
sig = defer.getCall().getCalleeExpr().getType() and
// check that one of the results is an error
sig.getResultType(_).implements(Builtin::error().getType().getUnderlyingType())
select defer, "Deferred a call to $@, which may return an error.", defer.getCall().getCalleeExpr(),
getFunctionName(defer.getCall())

View File

@@ -0,0 +1,17 @@
package main
import (
"io"
"log"
"os"
)
func example() {
if _, err := io.WriteString(os.Stdout, "Hello"); err != nil {
log.Fatal(err)
}
if _, err := io.WriteString(os.Stdout, "All done!"); err != nil {
log.Fatal(err)
}
}

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* Added a new query, `go/deferred-error-call`, to detect deferred calls to functions which may return errors.

View File

@@ -0,0 +1,4 @@
| tests.go:17:2:17:20 | defer statement | Deferred a call to $@, which may return an error. | tests.go:17:8:17:18 | returnsPair | returnsPair |
| tests.go:18:2:18:23 | defer statement | Deferred a call to $@, which may return an error. | tests.go:18:8:18:21 | checkSomething | checkSomething |
| tests.go:21:2:21:12 | defer statement | Deferred a call to $@, which may return an error. | tests.go:21:8:21:10 | fun | fun |
| tests.go:23:2:23:36 | defer statement | Deferred a call to $@, which may return an error. | tests.go:23:8:23:34 | function literal | an anonymous function |

View File

@@ -0,0 +1 @@
InconsistentCode/DeferredErrorCall.ql

View File

@@ -0,0 +1,43 @@
package test
import (
"errors"
"log"
)
func returnsPair() (string, error) {
return "", errors.New("Test error")
}
func checkSomething() error {
return nil
}
func deferredCalls() {
defer returnsPair()
defer checkSomething()
var fun = checkSomething
defer fun()
defer func() error { return nil }()
}
func notDeferred() {
if _, err := returnsPair(); err != nil {
log.Fatal(err)
}
if err := checkSomething(); err != nil {
log.Fatal(err)
}
var fun = checkSomething
if err := fun(); err != nil {
log.Fatal(err)
}
if err := func() error { return nil }(); err != nil {
log.Fatal(err)
}
}