| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package shift defines an Analyzer that checks for shifts that exceed |
| // the width of an integer. |
| package shift |
| |
| // TODO(adonovan): integrate with ctrflow (CFG-based) dead code analysis. May |
| // have impedance mismatch due to its (non-)treatment of constant |
| // expressions (such as runtime.GOARCH=="386"). |
| |
| import ( |
| "go/ast" |
| "go/constant" |
| "go/token" |
| |
| "golang.org/x/tools/go/analysis" |
| "golang.org/x/tools/go/analysis/passes/inspect" |
| "golang.org/x/tools/go/analysis/passes/internal/analysisutil" |
| "golang.org/x/tools/go/ast/inspector" |
| ) |
| |
| const Doc = "check for shifts that equal or exceed the width of the integer" |
| |
| var Analyzer = &analysis.Analyzer{ |
| Name: "shift", |
| Doc: Doc, |
| Requires: []*analysis.Analyzer{inspect.Analyzer}, |
| Run: run, |
| } |
| |
| func run(pass *analysis.Pass) (interface{}, error) { |
| inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) |
| |
| // Do a complete pass to compute dead nodes. |
| dead := make(map[ast.Node]bool) |
| nodeFilter := []ast.Node{ |
| (*ast.IfStmt)(nil), |
| (*ast.SwitchStmt)(nil), |
| } |
| inspect.Preorder(nodeFilter, func(n ast.Node) { |
| // TODO(adonovan): move updateDead into this file. |
| updateDead(pass.TypesInfo, dead, n) |
| }) |
| |
| nodeFilter = []ast.Node{ |
| (*ast.AssignStmt)(nil), |
| (*ast.BinaryExpr)(nil), |
| } |
| inspect.Preorder(nodeFilter, func(node ast.Node) { |
| if dead[node] { |
| // Skip shift checks on unreachable nodes. |
| return |
| } |
| |
| switch node := node.(type) { |
| case *ast.BinaryExpr: |
| if node.Op == token.SHL || node.Op == token.SHR { |
| checkLongShift(pass, node, node.X, node.Y) |
| } |
| case *ast.AssignStmt: |
| if len(node.Lhs) != 1 || len(node.Rhs) != 1 { |
| return |
| } |
| if node.Tok == token.SHL_ASSIGN || node.Tok == token.SHR_ASSIGN { |
| checkLongShift(pass, node, node.Lhs[0], node.Rhs[0]) |
| } |
| } |
| }) |
| return nil, nil |
| } |
| |
| // checkLongShift checks if shift or shift-assign operations shift by more than |
| // the length of the underlying variable. |
| func checkLongShift(pass *analysis.Pass, node ast.Node, x, y ast.Expr) { |
| if pass.TypesInfo.Types[x].Value != nil { |
| // Ignore shifts of constants. |
| // These are frequently used for bit-twiddling tricks |
| // like ^uint(0) >> 63 for 32/64 bit detection and compatibility. |
| return |
| } |
| |
| v := pass.TypesInfo.Types[y].Value |
| if v == nil { |
| return |
| } |
| amt, ok := constant.Int64Val(v) |
| if !ok { |
| return |
| } |
| t := pass.TypesInfo.Types[x].Type |
| if t == nil { |
| return |
| } |
| size := 8 * pass.TypesSizes.Sizeof(t) |
| if amt >= size { |
| ident := analysisutil.Format(pass.Fset, x) |
| pass.ReportRangef(node, "%s (%d bits) too small for shift of %d", ident, size, amt) |
| } |
| } |