github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/analysis/passes/shift/shift.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package shift defines an Analyzer that checks for shifts that exceed 6 // the width of an integer. 7 package shift 8 9 // TODO(adonovan): integrate with ctrflow (CFG-based) dead code analysis. May 10 // have impedance mismatch due to its (non-)treatment of constant 11 // expressions (such as runtime.GOARCH=="386"). 12 13 import ( 14 "go/ast" 15 "go/constant" 16 "go/token" 17 "go/types" 18 "math" 19 20 "golang.org/x/tools/go/analysis" 21 "golang.org/x/tools/go/analysis/passes/inspect" 22 "golang.org/x/tools/go/analysis/passes/internal/analysisutil" 23 "golang.org/x/tools/go/ast/inspector" 24 "golang.org/x/tools/internal/typeparams" 25 ) 26 27 const Doc = "check for shifts that equal or exceed the width of the integer" 28 29 var Analyzer = &analysis.Analyzer{ 30 Name: "shift", 31 Doc: Doc, 32 Requires: []*analysis.Analyzer{inspect.Analyzer}, 33 Run: run, 34 } 35 36 func run(pass *analysis.Pass) (interface{}, error) { 37 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 38 39 // Do a complete pass to compute dead nodes. 40 dead := make(map[ast.Node]bool) 41 nodeFilter := []ast.Node{ 42 (*ast.IfStmt)(nil), 43 (*ast.SwitchStmt)(nil), 44 } 45 inspect.Preorder(nodeFilter, func(n ast.Node) { 46 // TODO(adonovan): move updateDead into this file. 47 updateDead(pass.TypesInfo, dead, n) 48 }) 49 50 nodeFilter = []ast.Node{ 51 (*ast.AssignStmt)(nil), 52 (*ast.BinaryExpr)(nil), 53 } 54 inspect.Preorder(nodeFilter, func(node ast.Node) { 55 if dead[node] { 56 // Skip shift checks on unreachable nodes. 57 return 58 } 59 60 switch node := node.(type) { 61 case *ast.BinaryExpr: 62 if node.Op == token.SHL || node.Op == token.SHR { 63 checkLongShift(pass, node, node.X, node.Y) 64 } 65 case *ast.AssignStmt: 66 if len(node.Lhs) != 1 || len(node.Rhs) != 1 { 67 return 68 } 69 if node.Tok == token.SHL_ASSIGN || node.Tok == token.SHR_ASSIGN { 70 checkLongShift(pass, node, node.Lhs[0], node.Rhs[0]) 71 } 72 } 73 }) 74 return nil, nil 75 } 76 77 // checkLongShift checks if shift or shift-assign operations shift by more than 78 // the length of the underlying variable. 79 func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) { 80 if pass.TypesInfo.Types[x].Value != nil { 81 // Ignore shifts of constants. 82 // These are frequently used for bit-twiddling tricks 83 // like ^uint(0) >> 63 for 32/64 bit detection and compatibility. 84 return 85 } 86 87 v := pass.TypesInfo.Types[y].Value 88 if v == nil { 89 return 90 } 91 amt, ok := constant.Int64Val(v) 92 if !ok { 93 return 94 } 95 t := pass.TypesInfo.Types[x].Type 96 if t == nil { 97 return 98 } 99 var structuralTypes []types.Type 100 switch t := t.(type) { 101 case *typeparams.TypeParam: 102 terms, err := typeparams.StructuralTerms(t) 103 if err != nil { 104 return // invalid type 105 } 106 for _, term := range terms { 107 structuralTypes = append(structuralTypes, term.Type()) 108 } 109 default: 110 structuralTypes = append(structuralTypes, t) 111 } 112 sizes := make(map[int64]struct{}) 113 for _, t := range structuralTypes { 114 size := 8 * pass.TypesSizes.Sizeof(t) 115 sizes[size] = struct{}{} 116 } 117 minSize := int64(math.MaxInt64) 118 for size := range sizes { 119 if size < minSize { 120 minSize = size 121 } 122 } 123 if amt >= minSize { 124 ident := analysisutil.Format(pass.Fset, x) 125 qualifier := "" 126 if len(sizes) > 1 { 127 qualifier = "may be " 128 } 129 pass.ReportRangef(node, "%s (%s%d bits) too small for shift of %d", ident, qualifier, minSize, amt) 130 } 131 }