Go: proposal: cmd/vet: flag accidental integer division inside float casts

Created on 21 Jan 2020  Â·  8Comments  Â·  Source: golang/go

We have caught bugs of the form seen in https://github.com/kubernetes/kubernetes/pull/83132#pullrequestreview-346205014 a number of times in Kubernetes — namely, code that writes float64(some_int / another_int) when what is clearly meant is float64(some_int) / another_int.

It strikes me that the likelihood that anyone would ever intentionally lose precision with an integer divison and then cast the known-to-be-integral result to float64 is not necessarily exactly zero, but very low. In the rare cases where that is what's desired, it's easy to replace f := float64(a/b) with t := a/b; f := float64(t) and doing so more clearly communicates the intentional truncation.

The proposal is to have go vet flag expressions of the form float32(x / y) or float64(x / y) where x and y are expressions yielding a result of integral type.

Proposal

Most helpful comment

Adopting this check would mean forcing people to write float64(int(a/b)) for integer a, b to express "no I really mean what the code says". That's pretty unfortunate. In general we do assume that people write what they mean. Vet flags obvious mistakes, but this is not an obvious mistake. There is a way to write an integer divide and a way to write a float divide. Both are reasonable in different circumstances.

Note cmd/vet/README's "Precision" requirement, in particular that a check must not have a significant number of false positives. To move forward with this, we'd need evidence that essentially _all_ instances of float64(a/b) are bugs.

All 8 comments

I'm not convinced that float64(a / b) is always an error for integer a and b. It would be interesting to hear how many times such a check fired on the whole Kubernetes repo.

Basically I'm not sure this rises to the level of a vet check, which is a fairly high level. It might be more appropriate for some other checker.

cc @dominikh @mvdan

Adopting this check would mean forcing people to write float64(int(a/b)) for integer a, b to express "no I really mean what the code says". That's pretty unfortunate. In general we do assume that people write what they mean. Vet flags obvious mistakes, but this is not an obvious mistake. There is a way to write an integer divide and a way to write a float divide. Both are reasonable in different circumstances.

Note cmd/vet/README's "Precision" requirement, in particular that a check must not have a significant number of false positives. To move forward with this, we'd need evidence that essentially _all_ instances of float64(a/b) are bugs.

We need evidence to move forward with this, and we don't have any evidence. This seems like a likely decline.

I also note that staticcheck doesn't check this either.

Sorry for the delay.

I mocked up a check to run over the k8s codebase, and came back with five hits, of which four are copies of each other (and harmless, if silly, since in context it amounts to float64((2 * some int) / 2)) and the other is definitely a bug.

This is possibly a slight underestimate since I was only looking for literal float{32,64} casts and there could be some other type with an underlying float type, but I'll accept that this doesn't meet the quality bar for a go vet check. Thanks for the review, though!

Even time.Second / time.Nanosecond is OK, since the time package ensures that there is an integral number of nanoseconds in a second.

I also note that staticcheck doesn't check this either.

Correct, I share your concern about false positives.

Staticcheck has a related check, however, which flags math.Ceil(float64(<integer expression>)) (and identically for math.Floor). This catches one of the common instances of the mistake, with no real false positives to my knowledge. However, that check likely doesn't meet vet's frequency criterion.

Thanks for taking the time to dig up numbers.
No change in consensus, so declining.

Was this page helpful?
0 / 5 - 0 ratings