x/tools/go/analysis/passes/shift:允许全宽度整数移位

mw3dktmi  于 2个月前  发布在  Go
关注(0)|答案(9)|浏览(47)

位移传递拒绝了一个常数64位移位的uint64,以及在32位平台上一个uint移位32位,这两种情况都出现在正确的代码中。以下是两个示例:

从最近的x/sys/unix CL中:

func offs2lohi(offs int64) (lo, hi uintptr) {
	const longBits = SizeofLong * 8
	return uintptr(offs), uintptr(uint64(offs) >> longBits)
}

在64位系统上,目标是返回offs,0,该表达式确实做到了这一点,第二个表达式中使用了uint64 >> 64。我将发送一个CL,用(longBits - 1) >> 1替换>> longBits,以使旧的审查员对这段代码满意。

从最近的math/big CL中:

// bits.Len uses a lookup table for the low-order bits on some
// architectures. Neutralize any input-dependent behavior by setting all
// bits after the first one bit.
top := uint(x[i])
top |= top >> 1
top |= top >> 2
top |= top >> 4
top |= top >> 8
top |= top >> 16
top |= top >> 16 >> 16 // ">> 32" doesn't compile on 32-bit architectures
return i*_W + bits.Len(top)

注意注解说“无法编译”,但实际上问题是没有进行审查。
审查不应该重新定义语言中允许的有效代码。因为这些程序是有效的,我们应该放宽passes/shift/shift.go中的检查条件,将其更改为amt > maxSize,而不是amt >= maxSize。

snz8szmq

snz8szmq1#

https://go.dev/cl/463675提到了这个问题:unix: avoid false positive in vet shift check

fumotvh3

fumotvh32#

另一个可能的放宽是:(1)在所有平台上将uint和uintptr视为64位;(2)不警告关于计算常量值的移位。因此,给定

var u64 uint64
var up uintptr
var u uint
var u32 uint32

u32>>32 and u64>>64会被标记,但u>>32 and up>>32不会,即使在32位构建中也不会,u64>>(computed expression == 64)也不会。

oiopk7p5

oiopk7p53#

我认为在这种情况下,vet仍然有帮助:

func alt(offs int64) (lo, hi uintptr) {
	return uintptr(offs), uintptr(uint64(offs) >> 64)
}

关于机器字大小的歧义似乎涉及到所有这些问题。当存在可能的大小歧义(如可能的架构大小差异或y的计算值)时,是否可以放宽到">"?对于"x << y",当x或y是int、uint或uintptr类型,或者y不是BasicLit时,可以放宽。
也许vet可以尝试变得更聪明一些,尝试确定y的值是否依赖于标识符(无论是类型名还是常量值似乎值得停止)?这可能对*BasicLit来说太聪明了,收益不大。对于x << (y+1-1)被认为是模糊的可能是不幸的(但可能不是什么大问题)。

q3qa4bjr

q3qa4bjr4#

这是一个古老的警告(可以追溯到https://golang.org/cl/134780043),但我从未对这种警告感到满意。在便携式代码中,我可以使用构建标签定义类型和常量,以便在不同的系统上有所不同。当工具阻止我编写直接的代码,只是恰好在特定平台上有些奇怪时,这会让人很恼火。(我对禁止常数除以零的语言规则也有类似的担忧。)
所以正如我认为你所说的,警告混合预声明类型和超出范围的字面常量可能是可以接受的。但是我们不应该在不使用预声明类型时发出警告,也不应该在不使用字面常量时发出警告。

oxalkeyp

oxalkeyp5#

关于这个问题,我们想捕获一个常见的错误示例:

func make64(lo, hi uint32) uint64 {
    return uint64(lo) | uint64(hi<<32)
}

(正确的代码是 uint64(hi)<<32。)
这个版本也有问题,但我不确定我们能否在不捕获数学/大整数常量的情况下捕获它:

func make64(lo, hi uint) uint64 {
    return uint64(lo) | uint64(hi<<32)
}

可能可以忽略,因为大多数沿着这条线的代码都使用了有大小的整数。
这里有一个潜在的规则:

  1. 检查仅适用于数值常量表达式。(7+1) 是,(N+1) 否。
  2. 在检查的目的上,将 int、uint 和 uintptr 视为所有系统的 64 位。
    (已经有一条规则可以豁免左侧有常量的移位操作,例如 ^uint(0) >> 63。那将保留。)
    想法?
1cklez4t

1cklez4t6#

我觉得这只是在问题边缘徘徊,对于一个GOARCH看起来令人困惑的代码实际上对另一个GOARCH是合理的。
正确的解决方案是运行所有可能的GOARCH检查,只有在所有检查触发时才报告错误。听起来有点疯狂,但我们有什么理由不能这样做呢?

5cnsuln7

5cnsuln77#

对于shift检查的具体情况,在所有GOARCHes上运行相当于如果检查将在32位和64位系统上发生,那么触发该检查。由于检查只是一个 >= 或 > 比较,假设64位正好具有这种效果。

一般来说,我们需要编译后的依赖项来实际更改GOARCH,这在许多情况下可能还需要更改GOOS。例如,没有darwin/ppc64le。API检查确实适用于所有GOOS/GOARCH组合,虽然可行,但肯定要慢10倍甚至更多。

vulvrdjw

vulvrdjw8#

检查仅适用于数值常量表达式。(7+1) 是,(N+1) 否。
在所有系统上,将 int、uint 和 uintptr 视为 64 位,以进行检查的目的。
我们之前支持像 unsafe.Sizeof(i) 这样的所有常量表达式作为副作用 go go/types 。我们可能需要构建一个小型抽象语法树常量求值器来执行“视为 64 位”的步骤。我的猜测是我们可能不希望失去对整数上的 unsafe.Sizeof 的支持,我们可能可以放弃对 Alignof、Offsetof、len 和 cap 常量表达式的支持。
想法呢?
正确的解决方案是针对所有可能的 GOARCHes 运行检查,只有在所有情况下触发检查时才报告错误。听起来有点疯狂,但有什么理由我们不能这样做呢?
这实际上确实听起来像是一个有原则的答案。一个复杂的问题是 vet 假设只有一个一致类型检查的包,其他定义可能在不同的文件中。我们目前正在从 go/types 获取常量求值作为副作用。这在一组文件上一次有效。但我认为我们可以尝试为足够多的表达式模拟“所有 GOARCHes”。

8iwquhpp

8iwquhpp9#

对于位移检查的特定情况,在所有GOARCHes上运行相当于仅在32位和64位系统上触发检查,如果假设64位恰好具有相同的效果。
当然,只要移位量是整数字面量。我们放弃了移位量是构建标签控制的常量(某种程度上似乎出了问题)的情况。

相关问题