golang.org/x/tools@v0.21.0/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/aliases" 25 "golang.org/x/tools/internal/typeparams" 26 ) 27 28 const Doc = "check for shifts that equal or exceed the width of the integer" 29 30 var Analyzer = &analysis.Analyzer{ 31 Name: "shift", 32 Doc: Doc, 33 URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/shift", 34 Requires: []*analysis.Analyzer{inspect.Analyzer}, 35 Run: run, 36 } 37 38 func run(pass *analysis.Pass) (interface{}, error) { 39 inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 40 41 // Do a complete pass to compute dead nodes. 42 dead := make(map[ast.Node]bool) 43 nodeFilter := []ast.Node{ 44 (*ast.IfStmt)(nil), 45 (*ast.SwitchStmt)(nil), 46 } 47 inspect.Preorder(nodeFilter, func(n ast.Node) { 48 // TODO(adonovan): move updateDead into this file. 49 updateDead(pass.TypesInfo, dead, n) 50 }) 51 52 nodeFilter = []ast.Node{ 53 (*ast.AssignStmt)(nil), 54 (*ast.BinaryExpr)(nil), 55 } 56 inspect.Preorder(nodeFilter, func(node ast.Node) { 57 if dead[node] { 58 // Skip shift checks on unreachable nodes. 59 return 60 } 61 62 switch node := node.(type) { 63 case *ast.BinaryExpr: 64 if node.Op == token.SHL || node.Op == token.SHR { 65 checkLongShift(pass, node, node.X, node.Y) 66 } 67 case *ast.AssignStmt: 68 if len(node.Lhs) != 1 || len(node.Rhs) != 1 { 69 return 70 } 71 if node.Tok == token.SHL_ASSIGN || node.Tok == token.SHR_ASSIGN { 72 checkLongShift(pass, node, node.Lhs[0], node.Rhs[0]) 73 } 74 } 75 }) 76 return nil, nil 77 } 78 79 // checkLongShift checks if shift or shift-assign operations shift by more than 80 // the length of the underlying variable. 81 func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) { 82 if pass.TypesInfo.Types[x].Value != nil { 83 // Ignore shifts of constants. 84 // These are frequently used for bit-twiddling tricks 85 // like ^uint(0) >> 63 for 32/64 bit detection and compatibility. 86 return 87 } 88 89 v := pass.TypesInfo.Types[y].Value 90 if v == nil { 91 return 92 } 93 u := constant.ToInt(v) // either an Int or Unknown 94 amt, ok := constant.Int64Val(u) 95 if !ok { 96 return 97 } 98 t := pass.TypesInfo.Types[x].Type 99 if t == nil { 100 return 101 } 102 var structuralTypes []types.Type 103 switch t := aliases.Unalias(t).(type) { 104 case *types.TypeParam: 105 terms, err := typeparams.StructuralTerms(t) 106 if err != nil { 107 return // invalid type 108 } 109 for _, term := range terms { 110 structuralTypes = append(structuralTypes, term.Type()) 111 } 112 default: 113 structuralTypes = append(structuralTypes, t) 114 } 115 sizes := make(map[int64]struct{}) 116 for _, t := range structuralTypes { 117 size := 8 * pass.TypesSizes.Sizeof(t) 118 sizes[size] = struct{}{} 119 } 120 minSize := int64(math.MaxInt64) 121 for size := range sizes { 122 if size < minSize { 123 minSize = size 124 } 125 } 126 if amt >= minSize { 127 ident := analysisutil.Format(pass.Fset, x) 128 qualifier := "" 129 if len(sizes) > 1 { 130 qualifier = "may be " 131 } 132 pass.ReportRangef(node, "%s (%s%d bits) too small for shift of %d", ident, qualifier, minSize, amt) 133 } 134 }