-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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 looppointer linter #1924
Add looppointer linter #1924
Conversation
838821d
to
4f6dce5
Compare
Hello, I think that a decision has been already done about the support of looppointer: #1404 (comment) ping @sayboras |
Hi, @ldez! I found the looppointer useful, and it catches some critical bugs when I work on a huge Go project. So, I used it by default in all my projects and templates
I thought it would be helpful not only for me but also for anyone to enable it when needed. And I agree entirely with #1404 (comment) |
The test data looppointer$ go run ./cmd/golangci-lint/ run --no-config --disable-all --enable=looppointer ./test/testdata/looppointer.go
test/testdata/looppointer.go:11:32: taking a pointer for the loop variable p (looppointer)
intSlice = append(intSlice, &p) // ERROR "taking a pointer for the loop variable p"
^
exit status 1 exportloopref$ go run ./cmd/golangci-lint/ run --no-config --disable-all --enable=exportloopref ./test/testdata/looppointer.go
test/testdata/looppointer.go:12:32: exporting a pointer for the loop variable p (exportloopref)
intSlice = append(intSlice, &p) // ERROR "taking a pointer for the loop variable p"
^
exit status 1 If I use the test data looppointer$ go run ./cmd/golangci-lint/ run --no-config --disable-all --enable=looppointer ./test/testdata/exportloopref.go
test/testdata/exportloopref.go:14:11: taking a pointer for the loop variable p (looppointer)
printp(&p)
^
test/testdata/exportloopref.go:15:26: taking a pointer for the loop variable p (looppointer)
slice = append(slice, &p) // ERROR "exporting a pointer for the loop variable p"
^
test/testdata/exportloopref.go:16:15: taking a pointer for the loop variable p (looppointer)
array[i] = &p // ERROR "exporting a pointer for the loop variable p"
^ exportloopref$ go run ./cmd/golangci-lint/ run --no-config --disable-all --enable=exportloopref ./test/testdata/exportloopref.go
test/testdata/exportloopref.go:15:26: exporting a pointer for the loop variable p (exportloopref)
slice = append(slice, &p) // ERROR "exporting a pointer for the loop variable p"
^
test/testdata/exportloopref.go:16:15: exporting a pointer for the loop variable p (exportloopref)
array[i] = &p // ERROR "exporting a pointer for the loop variable p"
^
test/testdata/exportloopref.go:18:11: exporting a pointer for the loop variable p (exportloopref)
ref = &p // ERROR "exporting a pointer for the loop variable p"
^
exit status 1 With this sample you can see some
proofpackage main
func main() {
for _, v := range []int{10, 11, 12, 13} {
printp(&v)
}
}
func printp(p *int) {
println(*p)
} go run main.go
10
11
12
13
proofpackage main
func main() {
var ref *int
for _, v := range []int{10, 11, 12, 13} {
if v == 11 {
ref = &v
}
}
printp(ref)
}
func printp(p *int) {
println(*p)
} $ go run main.go
13 so for me, the arguments from #1041 (comment) are still valid. |
Thanks for examples! I will be back with others or close the pr. |
4f6dce5
to
1d7b6c3
Compare
Does anyone know how to fix that?
@ldez I updated the example. We had it on production and I'm ok with false positive result but really want to exclude similar cases in the future. compare:
@(go run ./cmd/golangci-lint/ run --no-config --disable-all --enable=looppointer ./test/testdata/looppointer.go && echo nothing) || true
@echo ---
@(go run ./cmd/golangci-lint/ run --no-config --disable-all --enable=exportloopref ./test/testdata/looppointer.go && echo nothig) || true
.PHONY: compare $ make compare
test/testdata/looppointer.go:22:18: taking a pointer for the loop variable d (looppointer)
p.property = &d.prop // ERROR "taking a pointer for the loop variable d"
^
exit status 1
---
nothig |
@winmillwill, @scorpionknifes 👋 do you have some critical cases in your practice? |
Maybe @kyoh86 could add more extra knowledge. |
This comment has been minimized.
This comment has been minimized.
@golangci/team, what I have to do next? Does anyone have another point of view/can add alternative feedback/etc? |
For now, your PR is blocked. The arguments from #1041 (comment) are still valid. My opinion is to close this PR but keep the issue open (I will add my example in the issue) |
We can make it disabled by default and add a possibility to allow it explicitly. What the problem with this option? |
It's not possible because a lot of people use |
I showed the example which didn't catch by other linters in a box. I'm conscious enough to make an informed decision about whether I want to catch non-obvious errors but deal with false positives. |
Keep this rule in view:
I prefer to close the PR and wait for the improvements that @kyoh86 can do on false-positives, then later (re)open a PR. |
I do not have the option of solving all of the false positives that There is a tradeoff between false positives and oversights, and I haven't found a way to solve both at the same time. I don't expect the golangci-linter to be safe or comprehensive, so I have no opinion on its choice not to incorporate |
I don't really have a strong feeling but I still want to add that I don't think the fact that people use I'm one of those using |
So, looppointer will always have a case of false The fix seems to be a better option for users who are abusing --enable-all. Even without that fix, the downside is vanishingly small. @ldez it sounds like you already have a list of linters that can throw false |
I prefer to contribute to linter to remove false positives instead of creating an option. @kyoh86 It can be great if you could merge the 2 linters together and create an option for switching between the 2 behaviors. Another option is to create a kind of "merge" inside example: linters-settings:
exportloopref:
looppointer-mode: true I will work on this approach and see what I do on that. |
It's great to let the user choose the linters. |
golangci-lint also provides "one thing". |
I understand that you don't care about which linter is integrated but you are the author of both. I'm trying to satisfy everyone then I'm trying to find solutions.
It's your choice to split this analysis into 2 differents pieces but we can see that as only one, it's just a point of view. I want the best default behavior, with the few false-positives, by using FYI I created a branch master...ldez:feat/looppointer |
You think that they do same thing. But I don't think so. If users cannot do it, that's my fault. |
Users don't always use golangci-lint. |
|
Just to be clear, the decision has been already made to choose I don't like false positives, it's a personal opinion, I want to trust a linter without having to think about the meaning of reports. In the context of In conclusion, we don't really care about my personal opinion about false positives, the only decision we have to take is: are we ok to add a linter with expected false positives? |
I might've been a bit unclear. I mostly agree with you and I think it's a bit weird to add a linter that we know has false positives and it's intended. I think that to say the false positives are bad as an argument makes more sense than the fact that users run So to clarify, I'm not a fan of adding linters with checks with known issues. I think that would reduce the overall quality feeling of If it's easy to do I think your suggestion about merging them inside |
I completely agree with this point. The only reason I tried to add the linter is that it can catch some issues that appeared in my practice. Folks, what do you think about excluding some linters with issues from |
We have AFAIK, using Personally I have mixed opinion on current topic and can't decide what is better. |
The "known issue" is that it makes you do an unnecessary copy in exactly one vanishingly rare case where you may not need to copy, depending on the exact implementation of the function to which you pass the pointer, which obviously changes independent of the call site. So, in this exact case, it would be false to say "you took the address of a loop variable and then used it unsafely", but it would be true to say "you took the address of a loop variable, which you should almost never do, and it's easy to actually never do it and never have the bugs it can cause, so just don't do it". If the expectation is that the linter will only mark code that definitely causes odd behavior due to taking the address of the loop variable, then this is a false positive. If the expectation is that your code should be readable and maintainable, then this is NOT a false positive. It's strange to think of this behavior as any kind of obstacle given the choice of linters that are enabled by default: gosimple will flag code that doesn't have any bugs at all BY DESIGN, so everything it does is a false positive if you apply the rationale being used to evaluate looppointer. Conversely, if you just expect linters to help you make your code easier to read and maintain, then looppointer will always do that. |
I didn't get the impression that this linter warned about style but about safety. The docs for the linter clearly states:
There's a lot of linters that only warns about style such as If the linters intended purpose is to force you to always make a copy of the pointer value though, for whatever reason and not just safety or avoiding bugs, I would agree with you. Maybe it just boils down to wording, I'm totally open for the fact that I misinterpreted the use case for the linter. |
I don't think you misinterpreted the docs, I think it's a case where the documented behavior of software implies many possible uses. In this case, style would be informed by the most straightforward way to avoid a class of bugs. I guess I just don't see the original intent of the author of a linter as relevant to the user, beyond the extent to which it informs ongoing development. I don't see how the stated purpose of looppointer can allow a faithful implementation not to support my use case, so I'm satisfied, and false positives in terms of provable bugs won't be false positives to me, because they signify that we can't easily prove the bugs aren't present, which I consider a "true positive" when it can be solved easily. The value I see is as you described: just have every contributor add a copy in this case and be done with it, and now it's part of the style of all the code in the repo, in addition to never having this class of bugs. If we have to avoid the copy for some reason, the linter still forced us to have the conversation in the pr because the author had to add the |
https://github.com/kyoh86/looppointer
fix #1404