// 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.

/*
This file contains the code to check for suspicious shifts.
*/

package main

import (
	"go/ast"
	"go/constant"
	"go/token"
	"go/types"
)

func init() {
	register("shift",
		"check for useless shifts",
		checkShift,
		binaryExpr, assignStmt)
}

func checkShift(f *File, node ast.Node) {
	switch node := node.(type) {
	case *ast.BinaryExpr:
		if node.Op == token.SHL || node.Op == token.SHR {
			checkLongShift(f, 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(f, node, node.Lhs[0], node.Rhs[0])
		}
	}
}

// checkLongShift checks if shift or shift-assign operations shift by more than
// the length of the underlying variable.
func checkLongShift(f *File, node ast.Node, x, y ast.Expr) {
	if f.pkg.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 := f.pkg.types[y].Value
	if v == nil {
		return
	}
	amt, ok := constant.Int64Val(v)
	if !ok {
		return
	}
	t := f.pkg.types[x].Type
	if t == nil {
		return
	}
	b, ok := t.Underlying().(*types.Basic)
	if !ok {
		return
	}
	var size int64
	var msg string
	switch b.Kind() {
	case types.Uint8, types.Int8:
		size = 8
	case types.Uint16, types.Int16:
		size = 16
	case types.Uint32, types.Int32:
		size = 32
	case types.Uint64, types.Int64:
		size = 64
	case types.Int, types.Uint, types.Uintptr:
		// These types may be as small as 32 bits, but no smaller.
		size = 32
		msg = "might be "
	default:
		return
	}
	if amt >= size {
		ident := f.gofmt(x)
		f.Badf(node.Pos(), "%s %stoo small for shift of %d", ident, msg, amt)
	}
}
