New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add query to check for deferred calls to functions which may return errors #11410
base: main
Are you sure you want to change the base?
Conversation
|
QHelp previews: go/ql/src/InconsistentCode/DeferredErrorCall.qhelpDeferred call may return an errorWhen 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. RecommendationExamine the deferred function call carefully to check whether any errors that may arise should be handled explicitly. ExampleIn the following example, a call to 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)
}
}To correct this issue, do not defer the function call that may return an error and handle the error explicitly instead: 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)
}
}References
|
| from DeferStmt defer, SignatureType sig | ||
| where | ||
| // match all deferred function calls and obtain their type signatures | ||
| sig = defer.getCall().getCalleeExpr().getType().(SignatureType) and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| sig = defer.getCall().getCalleeExpr().getType().(SignatureType) and | |
| sig = defer.getCall().getCalleeExpr().getType() and |
(I see ql-for-ql has also picked this up.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in c907a9e
| // check that one of the results is an error | ||
| sig.getResultType(_).implements(Builtin::error().getType().getUnderlyingType()) | ||
| select defer, | ||
| "Deferred a call to " + defer.getCall().getCalleeName() + ", which may return an error." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getCalleeName isn't always defined, so we would lose any alerts for those cases, though I can't remember exactly what they are at the moment. Is it worth trying to provide a different string when getCalleeName doesn't exist, to cover those cases? People defer anonymous functions quite a bit, though it would be weird to write an anonymous function with return values and then defer it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤷🏻♂️ I have improved this in 5620d24 in any case.
|
|
||
| func deferredCalls() { | ||
| defer returnsPair() | ||
| defer checkSomething() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to be thorough, you can cover some more cases: (1) assign a function to a variable and use the variable in the defer statement; (2) defer a function literal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in 5620d24
|
Oh, I just realised that it needs a change note. Luckily there's an automatic check for that. Oh, and it needs docs review. I think you have to add the |
A simple query which checks that there are no deferred calls to functions which may return errors, since it prevents errors from being handled.