101 lines
2.6 KiB
Go
101 lines
2.6 KiB
Go
|
|
package sa9006
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"fmt"
|
|||
|
|
"go/ast"
|
|||
|
|
"go/types"
|
|||
|
|
|
|||
|
|
"honnef.co/go/tools/analysis/code"
|
|||
|
|
"honnef.co/go/tools/analysis/lint"
|
|||
|
|
"honnef.co/go/tools/analysis/report"
|
|||
|
|
"honnef.co/go/tools/pattern"
|
|||
|
|
|
|||
|
|
"golang.org/x/tools/go/analysis"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
|||
|
|
Analyzer: &analysis.Analyzer{
|
|||
|
|
Name: "SA9006",
|
|||
|
|
Run: run,
|
|||
|
|
Requires: code.RequiredAnalyzers,
|
|||
|
|
},
|
|||
|
|
Doc: &lint.RawDocumentation{
|
|||
|
|
Title: `Dubious bit shifting of a fixed size integer value`,
|
|||
|
|
Text: `Bit shifting a value past its size will always clear the value.
|
|||
|
|
|
|||
|
|
For instance:
|
|||
|
|
|
|||
|
|
v := int8(42)
|
|||
|
|
v >>= 8
|
|||
|
|
|
|||
|
|
will always result in 0.
|
|||
|
|
|
|||
|
|
This check flags bit shifting operations on fixed size integer values only.
|
|||
|
|
That is, int, uint and uintptr are never flagged to avoid potential false
|
|||
|
|
positives in somewhat exotic but valid bit twiddling tricks:
|
|||
|
|
|
|||
|
|
// Clear any value above 32 bits if integers are more than 32 bits.
|
|||
|
|
func f(i int) int {
|
|||
|
|
v := i >> 32
|
|||
|
|
v = v << 32
|
|||
|
|
return i-v
|
|||
|
|
}`,
|
|||
|
|
Since: "2020.2",
|
|||
|
|
Severity: lint.SeverityWarning,
|
|||
|
|
// Technically this should be MergeIfAll, because the type of
|
|||
|
|
// v might be different for different build tags. Practically,
|
|||
|
|
// don't write code that depends on that.
|
|||
|
|
MergeIf: lint.MergeIfAny,
|
|||
|
|
},
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
var Analyzer = SCAnalyzer.Analyzer
|
|||
|
|
var (
|
|||
|
|
checkFixedLengthTypeShiftQ = pattern.MustParse(`
|
|||
|
|
(Or
|
|||
|
|
(AssignStmt _ (Or ">>=" "<<=") _)
|
|||
|
|
(BinaryExpr _ (Or ">>" "<<") _))
|
|||
|
|
`)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func run(pass *analysis.Pass) (any, error) {
|
|||
|
|
isDubiousShift := func(x, y ast.Expr) (int64, int64, bool) {
|
|||
|
|
typ, ok := pass.TypesInfo.TypeOf(x).Underlying().(*types.Basic)
|
|||
|
|
if !ok {
|
|||
|
|
return 0, 0, false
|
|||
|
|
}
|
|||
|
|
switch typ.Kind() {
|
|||
|
|
case types.Int8, types.Int16, types.Int32, types.Int64,
|
|||
|
|
types.Uint8, types.Uint16, types.Uint32, types.Uint64:
|
|||
|
|
// We're only interested in fixed–size types.
|
|||
|
|
default:
|
|||
|
|
return 0, 0, false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const bitsInByte = 8
|
|||
|
|
typeBits := pass.TypesSizes.Sizeof(typ) * bitsInByte
|
|||
|
|
|
|||
|
|
shiftLength, ok := code.ExprToInt(pass, y)
|
|||
|
|
if !ok {
|
|||
|
|
return 0, 0, false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return typeBits, shiftLength, shiftLength >= typeBits
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for node := range code.Matches(pass, checkFixedLengthTypeShiftQ) {
|
|||
|
|
switch e := node.(type) {
|
|||
|
|
case *ast.AssignStmt:
|
|||
|
|
if size, shift, yes := isDubiousShift(e.Lhs[0], e.Rhs[0]); yes {
|
|||
|
|
report.Report(pass, e, fmt.Sprintf("shifting %d-bit value by %d bits will always clear it", size, shift))
|
|||
|
|
}
|
|||
|
|
case *ast.BinaryExpr:
|
|||
|
|
if size, shift, yes := isDubiousShift(e.X, e.Y); yes {
|
|||
|
|
report.Report(pass, e, fmt.Sprintf("shifting %d-bit value by %d bits will always clear it", size, shift))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return nil, nil
|
|||
|
|
}
|