Skip to content
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

lint: enable govet shadow linter and resolve linter warnings #5261

Merged
merged 10 commits into from Jun 1, 2023

Conversation

cce
Copy link
Contributor

@cce cce commented Apr 6, 2023

Summary

This enables the govet shadow analysis pass, implemented in https://github.com/golang/tools/blob/master/go/analysis/passes/shadow/shadow.go and enabled via golangci-lint.

The shadow linter prevents some cases of shadowed variables being used, which especially impacts the re-use of common variable names like err and ok. In this PR I attempted to resolve these issues mostly be just renaming names like err to more specific and unused names like err2 or osErr.

In a few other cases I updated

err := X()
if err != nil {
    return err
}

to

err = X()
if err != nil {
    return err
}

if the check-and-return came right away. I also removed a couple of named error return variables or unnecessary declarations of names earlier in a function that were shadowed later.

The shadow linter strict: false setting is used which comes with the following explanation:

A span stores the minimum range of byte positions in the file in which a given variable (types.Object) is mentioned. It is lexically defined: it spans from the beginning of its first mention to the end of its last mention. A variable is considered shadowed (if strict is off) only if the shadowing variable is declared within the span of the shadowed variable. In other words, if a variable is shadowed but not used after the shadowed variable is declared, it is inconsequential and not worth complaining about. This simple check dramatically reduces the nuisance rate for the shadowing
check, at least until something cleverer comes along.

One wrinkle: A "naked return" is a silent use of a variable that the Span will not capture, but the compilers catch naked returns of shadowed variables so we don't need to.

Cases this gets wrong (TODO):

  • If a for loop's continuation statement mentions a variable redeclared in the block, we should complain about it but don't.
  • A variable declared inside a function literal can falsely be identified as shadowing a variable in the outer function.

Test Plan

Existing tests should pass — in most cases I did not change the logic at all, but I did make some bigger changes to resolve some of the warnings.

@cce cce added the Enhancement label Apr 6, 2023
@codecov
Copy link

codecov bot commented Apr 6, 2023

Codecov Report

Merging #5261 (08f7542) into master (56b7e82) will decrease coverage by 0.02%.
The diff coverage is 29.35%.

@@            Coverage Diff             @@
##           master    #5261      +/-   ##
==========================================
- Coverage   55.43%   55.41%   -0.02%     
==========================================
  Files         452      452              
  Lines       63872    63875       +3     
==========================================
- Hits        35405    35394      -11     
- Misses      26053    26073      +20     
+ Partials     2414     2408       -6     
Impacted Files Coverage Δ
agreement/demux.go 91.20% <0.00%> (ø)
cmd/algocfg/profileCommand.go 29.68% <0.00%> (ø)
cmd/goal/account.go 13.68% <0.00%> (ø)
cmd/goal/application.go 18.19% <0.00%> (ø)
cmd/goal/asset.go 15.80% <0.00%> (ø)
cmd/goal/interact.go 3.42% <0.00%> (ø)
cmd/goal/network.go 14.81% <0.00%> (ø)
cmd/goal/tealsign.go 11.70% <0.00%> (ø)
daemon/algod/api/server/v2/handlers.go 0.82% <0.00%> (ø)
daemon/algod/server.go 4.30% <0.00%> (ø)
... and 25 more

... and 13 files with indirect coverage changes

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@gmalouf
Copy link
Contributor

gmalouf commented Apr 6, 2023

Talked this through with @cce , basically we are mostly okay with the inconsistent handling in some cases (see cmd/goal/asset.go for an example) as long as the linter (which in ways is stricter than needed) is happy with both of them.

For example, we are fine re-using an err variable directly, as long as it is not shadowing (and there is a quick nil check) as in https://github.com/algorand/go-algorand/pull/5261/files#diff-43408fd8ee96ef81b41df4152675473a9d2b76358da39757022f1e2d6ee00872R764 whereas in other situation err2, err3, etc is more appropriate for readability.

@@ -1523,7 +1523,8 @@ func (au *accountUpdates) lookupWithoutRewards(rnd basics.Round, addr basics.Add
}

// getCreatorForRound returns the asset/app creator for a given asset/app index at a given round
func (au *accountUpdates) getCreatorForRound(rnd basics.Round, cidx basics.CreatableIndex, ctype basics.CreatableType, synchronized bool) (creator basics.Address, ok bool, err error) {
func (au *accountUpdates) getCreatorForRound(rnd basics.Round, cidx basics.CreatableIndex, ctype basics.CreatableType, synchronized bool) (
Copy link
Contributor

Choose a reason for hiding this comment

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

Falls in the bucket of "this function is too long for naming return variables"

Copy link
Contributor

Choose a reason for hiding this comment

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

I wish we could enforce a rule around not naming return variables when functions are too long strictly - but doubt linter picks it up.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't thinking naming them is the problem, naked returns are the problem. You might name return values if you are trying to distinguish two returned ints - you want to explain which one is which, in the signature.

But naked returns are confusing. It's so easy to not realize what you're returning, especially in the presence of shadowing. So I would much rather ban naked returns in long functions. I very often return things explicitly, even if a naked return would do the same thing. In a long function, it's much easier to understand without a moment's hesitation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree, naked returns are the primary culprit. The return value naming is useful for readability, but if we can't stop naked returns in those cases seems not worth it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can ban naked returns for functions longer than a certain number of lines. What should we pick? 50 lines?

Copy link
Contributor

Choose a reason for hiding this comment

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

It depends on how many failures currently but ideally even less (enough for very simple if / else).

Copy link
Contributor

Choose a reason for hiding this comment

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

I was thinking 15-20 lines max - not sure it's a blocker for this PR but a quick follow-up.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do in future PR to add the nakedret linter maybe with perhaps 50 lines to start

accumulatedChanges := 0

for i := 0; i < accountsDeltas.len(); i++ {
delta := accountsDeltas.getByIdx(i)
if !delta.oldAcct.AccountData.IsEmpty() {
deleteHash := trackerdb.AccountHashBuilderV6(delta.address, &delta.oldAcct.AccountData, protocol.Encode(&delta.oldAcct.AccountData))
deleted, err = ct.balancesTrie.Delete(deleteHash)
deleted, err := ct.balancesTrie.Delete(deleteHash)
Copy link
Contributor

Choose a reason for hiding this comment

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

These are safe without naming the error variable in function signature, frankly more readable too.

ledger/catchupaccessor.go Outdated Show resolved Hide resolved
@@ -754,7 +754,7 @@ func (c *catchpointCatchupAccessorImpl) BuildMerkleTrie(ctx context.Context, pro
defer wg.Done()
defer close(writerQueue)

err := dbs.Snapshot(func(transactionCtx context.Context, tx trackerdb.SnapshotScope) (err error) {
dbErr := dbs.Snapshot(func(transactionCtx context.Context, tx trackerdb.SnapshotScope) (err error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why didn't we rename/remove the err error declaration in the signature of this function (its pretty long)?

Copy link
Contributor Author

@cce cce May 30, 2023

Choose a reason for hiding this comment

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

Taking out named return variables seemed like a whole can of worms that could make this PR get pretty big — I was going for the minimal set to make the linter happy

gmalouf
gmalouf previously approved these changes May 23, 2023
cmd/loadgenerator/main.go Outdated Show resolved Hide resolved
cmd/loadgenerator/main.go Outdated Show resolved Hide resolved
Copy link
Contributor

@tzaffi tzaffi left a comment

Choose a reason for hiding this comment

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

I agree with the vast majority of the changes. I left a couple of questions/suggestions in places that I disagreed or could spot an alternative.

tzaffi
tzaffi previously approved these changes May 26, 2023
Copy link
Contributor

@tzaffi tzaffi left a comment

Choose a reason for hiding this comment

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

LGTM

jannotti
jannotti previously approved these changes May 30, 2023
Copy link
Contributor

@jannotti jannotti left a comment

Choose a reason for hiding this comment

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

I'll try to look at more later, but this looks mergable to me.

cmd/goal/account.go Show resolved Hide resolved
cmd/goal/asset.go Show resolved Hide resolved
cmd/netgoal/network.go Outdated Show resolved Hide resolved
cmd/pingpong/runCmd.go Show resolved Hide resolved
ledger/acctupdates.go Outdated Show resolved Hide resolved
@cce cce dismissed stale reviews from jannotti and tzaffi via 81c8461 May 30, 2023 01:26
@cce cce requested a review from jannotti June 1, 2023 15:50
@gmalouf gmalouf merged commit 6e245e7 into algorand:master Jun 1, 2023
24 checks passed
tzaffi pushed a commit to tzaffi/go-algorand that referenced this pull request Jun 1, 2023
@cce cce deleted the govet-shadow-linter branch June 6, 2023 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants