Skip to content

Go: Add query to check for deferred calls to functions which may return errors #11410

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

Closed
wants to merge 5 commits into from

Conversation

mbg
Copy link
Member

@mbg mbg commented Nov 24, 2022

A simple query which checks that there are no deferred calls to functions which may return errors, since it prevents errors from being handled.

@mbg mbg added the Go label Nov 24, 2022
@mbg mbg requested a review from a team as a code owner November 24, 2022 11:06
@mbg mbg self-assigned this Nov 24, 2022
@github-actions
Copy link
Contributor

github-actions bot commented Nov 24, 2022

QHelp previews:

go/ql/src/InconsistentCode/DeferredErrorCall.qhelp

Deferred call may return an error

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.

Recommendation

Examine the deferred function call carefully to check whether any errors that may arise should be handled explicitly.

Example

In the following example, a call to io.WriteLine 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 io.WriteLine.

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

  • The Go Programming Language Specification: Defer statements.
  • The Go Programming Language Specification: Errors.

owen-mc
owen-mc previously approved these changes Nov 24, 2022
owen-mc
owen-mc previously approved these changes Nov 24, 2022
Copy link
Contributor

@owen-mc owen-mc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great.

@owen-mc
Copy link
Contributor

owen-mc commented Nov 24, 2022

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 ready-for-doc-review label and they will come and do it.

@mbg mbg changed the title Add query to check for deferred calls to functions which may return errors Go: Add query to check for deferred calls to functions which may return errors Nov 25, 2022
@mbg mbg added the ready-for-doc-review This PR requires and is ready for review from the GitHub docs team. label Nov 25, 2022
hubwriter
hubwriter previously approved these changes Nov 25, 2022
Copy link
Contributor

@hubwriter hubwriter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs review: one typo, otherwise LGTM 👍

Comment on lines +12 to +14
if _, err := io.WriteString(os.Stdout, "Hello"); err != nil {
log.Fatal(err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to present a code example that preserves the behaviour we get from the usage of defer, while still handling any errors that that callee may produce i.e. what if I wanted to defer and also handle errors?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question! It seems like there are techniques for doing this by deferring an anonymous function which captures and writes to an error variable, that is then returned by the parent function. I will modify the example to make use of that technique.

Co-authored-by: hubwriter <hubwriter@github.com>
@mbg
Copy link
Member Author

mbg commented Nov 28, 2022

From running this query on LGTM, it seems that it is currently quite noisy since it seems like a common (anti?)-pattern to defer calls to .Close() for file handles, which is probably fine for read-only handles, but not for writable ones. @smowton and I discussed this and I will experiment a bit more to see if I can come up with a less noisy query.

@mbg
Copy link
Member Author

mbg commented Nov 29, 2022

Since this query is very noisy and I am now working on more specific ones that are more targeted, I will close this PR for now in favour of new ones for the new queries as they are ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Go ready-for-doc-review This PR requires and is ready for review from the GitHub docs team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants