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

proposal: cmd/vet: warn if sort.Interface implementation does not use both i and j #45780

Closed
nik0sc opened this issue Apr 26, 2021 · 11 comments
Closed
Labels
Milestone

Comments

@nik0sc
Copy link

@nik0sc nik0sc commented Apr 26, 2021

What version of Go are you using (go version)?

$ go version
go version go1.16.3 darwin/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE="on"
GOARCH="amd64"
GOBIN=""
GOCACHE="/Users/nikos.chan/Library/Caches/go-build"
GOENV="/Users/nikos.chan/Library/Application Support/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOINSECURE=""
GOMODCACHE="/Users/nikos.chan/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="darwin"
GOPATH="/Users/nikos.chan/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/Users/nikos.chan/sdk/go1.16.3"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/Users/nikos.chan/sdk/go1.16.3/pkg/tool/darwin_amd64"
GOVCS=""
GOVERSION="go1.16.3"
GCCGO="gccgo"
AR="ar"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD="/Users/nikos.chan/go/src/playground-1.16/go.mod"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -arch x86_64 -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/_l/vdhxyyys4t3dtb5dg0c2786n_gdp3q/T/go-build2117747930=/tmp/go-build -gno-record-gcc-switches -fno-common"

What did you do?

In case of mixing up i and j in sort.Interface, it is possible to produce broken code that compiles without any warning from go vet. (This is really easy since those characters appear similar in monospaced fonts.)

I wrote a small buggy program to test this behavior:

package main

import (
	"fmt"
	"sort"
)

type opaqueList struct {
	l []int
}

func (o opaqueList) Len() int {
	return len(o.l)
}

func (o opaqueList) Less(i, j int) bool {
	return o.l[i] < o.l[i] // <-- bug here
}

func (o opaqueList) Swap(i, j int) {
	o.l[i], o.l[j] = o.l[j], o.l[i]
}

func main() {
	o := opaqueList{l: []int{3,2,1}}
	sort.Sort(o)
	fmt.Println(o) // prints {[3 2 1]}
}

What did you expect to see?

Some warning that this is probably wrong, as (sort.Interface).Less needs to use both i and j in the function body for it to be useful.

What did you see instead?

No output by go vet

@gopherbot gopherbot added this to the Proposal milestone Apr 26, 2021
@mdlayher
Copy link
Member

@mdlayher mdlayher commented Apr 26, 2021

/cc @dominikh for staticcheck consideration

@guodongli-google
Copy link

@guodongli-google guodongli-google commented Apr 26, 2021

I would generalize slightly to checking something else:

// Check whether it always return true or false
func (o opaqueList) Less(i, j int) bool {
	return o.l[i] < o.l[i] // <-- bug here
}

// Check whether these are useless assignments if it is: o.l[i], o.l[i] = o.l[j], o.l[i].
func (o opaqueList) Swap(i, j int) {
	o.l[i], o.l[j] = o.l[j], o.l[i]
}
@komuw
Copy link
Contributor

@komuw komuw commented Apr 27, 2021

for staticcheck consideration

@mdlayher already exists: https://staticcheck.io/docs/checks#SA4000

@bcmills
Copy link
Member

@bcmills bcmills commented Apr 27, 2021

Does https://staticcheck.io/docs/checks#SA4018 also cover @guodongli-google's “useless assignment” case? (It's not obvious to me whether it would recognize index expressions as “variables”.)

@zihengCat
Copy link

@zihengCat zihengCat commented Apr 28, 2021

@nik0sc Your expectation could be easily handled by a static check tool, like staticcheck.

https://github.com/dominikh/go-tools

@bcmills
Copy link
Member

@bcmills bcmills commented Apr 28, 2021

@zihengCat

Your expectation could be easily handled by a static check tool,

cmd/vet is a static checking tool. That is literally what this issue is asking for. 😅

@timothy-king
Copy link
Contributor

@timothy-king timothy-king commented Apr 28, 2021

@bcmills and @guodongli-google return o.l[i] < o.l[i] is caught by staticcheck as SA4000. o.l[i], o.l[i] = o.l[j], o.l[i] is caught as SA4018. Tested using staticcheck 2020.2.3 (v0.1.3).

@timothy-king
Copy link
Contributor

@timothy-king timothy-king commented Apr 28, 2021

One thing to consider with the proposal is lexicographic order for a struct:

type pair struct {
  x int
  y int
}
...
func (o pairList) Less(i, j int) bool {
         if o.l[i].x < o.l[j].x {
           return true
        }
	return o.l[i].x == o.l[i].x && o.l[i].y < o.l[i].y // <-- bug here
}

The proposed checker would not report this as both i and j get used. Just not in each comparison. staticcheck's SA4000 would likely still catch this. My hunch is that each comparison is roughly equally likely to be buggy. But I could be wrong. @nik0sc If you have a non-minimized example, you might be able to invalidate my hunch.

An alternative check to this proposal is to have vet warn when a parameter is not used (and not named "_"). This generalization would also not handle the example I just gave. I assume this proposal has come up before if someone has a reference.

@nik0sc
Copy link
Author

@nik0sc nik0sc commented May 1, 2021

@timothy-king I now realize that a rule like I proposed would not help in complex implementations of Less like yours. The more general rule of warning for unused parameters is better, but would probably break some CI pipelines (I know I've written code that ignores parameters just so it fulfills an interface...)

@nik0sc nik0sc closed this May 1, 2021
@nik0sc
Copy link
Author

@nik0sc nik0sc commented May 1, 2021

But what is the relationship between go vet and staticcheck? Is staticcheck simply the more inclusive version of go vet?

@dominikh
Copy link
Member

@dominikh dominikh commented May 1, 2021

@nik0sc vet and Staticcheck are two independent static analysis tools for Go. vet is part of the Go project and focuses on a smaller number of checks. Staticcheck is a third party tool with more checks than vet, but it is not a superset of vet – that is, vet has some checks that aren't in Staticcheck.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
9 participants