llvm-project/clang/lib/StaticAnalyzer/Checkers/BitwiseShiftChecker.cpp
Donát Nagy 25b9696b61 [analyzer] Upstream BitwiseShiftChecker
This commit releases a checker that was developed to a stable level in
the Ericsson-internal fork of Clang Static Analyzer.

Note that the functionality of this checker overlaps with
core.UndefinedBinaryOperatorResult ("UBOR"), but there are several
differences between them:
(1) UBOR is only triggered when the constant folding performed by the
Clang Static Analyzer engine determines that the value of a binary
operator expression is undefined; this checker can report issues where
the operands are not constants.
(2) UBOR has unrelated checks for handling other binary operators, this
checker only examines bitwise shifts.
(3) This checker has a Pedantic flag and by default does not report
expressions (e.g. -2 << 2) that're undefined by the standard but
consistently supported in practice.
(4) UBOR exhibits buggy behavior in code that involves cast expressions,
e.g.
    void foo(unsigned short s) {
      if (s == 2) {
        (void) ((unsigned int) s) << 16;
      }
    }

Later it would be good to eliminate this overlap (perhaps by deprecating
and then eliminating the bitwise shift handling in UBOR), but in my
opinion that belongs to separate commits.

Differential Revision: https://reviews.llvm.org/D156312

Co-authored-by: Endre Fulop <endre.fulop@sigmatechnology.se>
2023-08-18 10:47:05 +02:00

369 lines
14 KiB
C++

//== BitwiseShiftChecker.cpp ------------------------------------*- C++ -*--==//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines BitwiseShiftChecker, which is a path-sensitive checker
// that looks for undefined behavior when the operands of the bitwise shift
// operators '<<' and '>>' are invalid (negative or too large).
//
//===----------------------------------------------------------------------===//
#include "clang/AST/ASTContext.h"
#include "clang/AST/CharUnits.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
#include "llvm/Support/FormatVariadic.h"
#include <memory>
using namespace clang;
using namespace ento;
using llvm::formatv;
namespace {
enum class OperandSide { Left, Right };
using BugReportPtr = std::unique_ptr<PathSensitiveBugReport>;
struct NoteTagTemplate {
llvm::StringLiteral SignInfo;
llvm::StringLiteral UpperBoundIntro;
};
constexpr NoteTagTemplate NoteTagTemplates[] = {
{"", "right operand of bit shift is less than "},
{"left operand of bit shift is non-negative", " and right operand is less than "},
{"right operand of bit shift is non-negative", " but less than "},
{"both operands of bit shift are non-negative", " and right operand is less than "}
};
/// An implementation detail class which is introduced to split the checker
/// logic into several methods while maintaining a consistently updated state
/// and access to other contextual data.
class BitwiseShiftValidator {
CheckerContext &Ctx;
ProgramStateRef FoldedState;
const BinaryOperator *const Op;
const BugType &BT;
const bool PedanticFlag;
// The following data members are only used for note tag creation:
enum { NonNegLeft = 1, NonNegRight = 2 };
unsigned NonNegOperands = 0;
std::optional<unsigned> UpperBoundBitCount = std::nullopt;
public:
BitwiseShiftValidator(const BinaryOperator *O, CheckerContext &C,
const BugType &B, bool P)
: Ctx(C), FoldedState(C.getState()), Op(O), BT(B), PedanticFlag(P) {}
void run();
private:
const Expr *operandExpr(OperandSide Side) const {
return Side == OperandSide::Left ? Op->getLHS() : Op->getRHS();
}
bool shouldPerformPedanticChecks() const {
// The pedantic flag has no effect under C++20 because the affected issues
// are no longer undefined under that version of the standard.
return PedanticFlag && !Ctx.getASTContext().getLangOpts().CPlusPlus20;
}
bool assumeRequirement(OperandSide Side, BinaryOperator::Opcode Cmp, unsigned Limit);
void recordAssumption(OperandSide Side, BinaryOperator::Opcode Cmp, unsigned Limit);
const NoteTag *createNoteTag() const;
BugReportPtr createBugReport(StringRef ShortMsg, StringRef Msg) const;
BugReportPtr checkOvershift();
BugReportPtr checkOperandNegative(OperandSide Side);
BugReportPtr checkLeftShiftOverflow();
bool isLeftShift() const { return Op->getOpcode() == BO_Shl; }
StringRef shiftDir() const { return isLeftShift() ? "left" : "right"; }
static StringRef pluralSuffix(unsigned n) { return n <= 1 ? "" : "s"; }
static StringRef verbSuffix(unsigned n) { return n <= 1 ? "s" : ""; }
};
void BitwiseShiftValidator::run() {
// Report a bug if the right operand is >= the bit width of the type of the
// left operand:
if (BugReportPtr BR = checkOvershift()) {
Ctx.emitReport(std::move(BR));
return;
}
// Report a bug if the right operand is negative:
if (BugReportPtr BR = checkOperandNegative(OperandSide::Right)) {
Ctx.emitReport(std::move(BR));
return;
}
if (shouldPerformPedanticChecks()) {
// Report a bug if the left operand is negative:
if (BugReportPtr BR = checkOperandNegative(OperandSide::Left)) {
Ctx.emitReport(std::move(BR));
return;
}
// Report a bug when left shift of a concrete signed value overflows:
if (BugReportPtr BR = checkLeftShiftOverflow()) {
Ctx.emitReport(std::move(BR));
return;
}
}
// No bugs detected, update the state and add a single note tag which
// summarizes the new assumptions.
Ctx.addTransition(FoldedState, createNoteTag());
}
/// This method checks a requirement that must be satisfied by the value on the
/// given Side of a bitwise shift operator in well-defined code. If the
/// requirement is incompatible with prior knowledge, this method reports
/// failure by returning false.
bool BitwiseShiftValidator::assumeRequirement(OperandSide Side,
BinaryOperator::Opcode Comparison,
unsigned Limit) {
SValBuilder &SVB = Ctx.getSValBuilder();
const SVal OperandVal = Ctx.getSVal(operandExpr(Side));
const auto LimitVal = SVB.makeIntVal(Limit, Ctx.getASTContext().IntTy);
// Note that the type of `LimitVal` must be a signed, because otherwise a
// negative `Val` could be converted to a large positive value.
auto ResultVal = SVB.evalBinOp(FoldedState, Comparison, OperandVal, LimitVal,
SVB.getConditionType());
if (auto DURes = ResultVal.getAs<DefinedOrUnknownSVal>()) {
auto [StTrue, StFalse] = FoldedState->assume(DURes.value());
if (!StTrue) {
// We detected undefined behavior (the caller will report it).
FoldedState = StFalse;
return false;
}
// The code may be valid, so let's assume that it's valid:
FoldedState = StTrue;
if (StFalse) {
// Record note tag data for the assumption that we made
recordAssumption(Side, Comparison, Limit);
}
}
return true;
}
BugReportPtr BitwiseShiftValidator::checkOvershift() {
const QualType LHSTy = Op->getLHS()->getType();
const unsigned LHSBitWidth = Ctx.getASTContext().getIntWidth(LHSTy);
if (assumeRequirement(OperandSide::Right, BO_LT, LHSBitWidth))
return nullptr;
const SVal Right = Ctx.getSVal(operandExpr(OperandSide::Right));
std::string RightOpStr = "";
if (auto ConcreteRight = Right.getAs<nonloc::ConcreteInt>())
RightOpStr = formatv(" '{0}'", ConcreteRight->getValue());
std::string ShortMsg = formatv(
"{0} shift{1}{2} overflows the capacity of '{3}'",
isLeftShift() ? "Left" : "Right", RightOpStr.empty() ? "" : " by",
RightOpStr, LHSTy.getAsString());
std::string Msg =
formatv("The result of {0} shift is undefined because the right "
"operand{1} is not smaller than {2}, the capacity of '{3}'",
shiftDir(), RightOpStr, LHSBitWidth, LHSTy.getAsString());
return createBugReport(ShortMsg, Msg);
}
// Before C++20, at 5.8 [expr.shift] (N4296, 2014-11-19) the standard says
// 1. "... The behaviour is undefined if the right operand is negative..."
// 2. "The value of E1 << E2 ...
// if E1 has a signed type and non-negative value ...
// otherwise, the behavior is undefined."
// 3. "The value of E1 >> E2 ...
// If E1 has a signed type and a negative value,
// the resulting value is implementation-defined."
// However, negative left arguments work in practice and the C++20 standard
// eliminates conditions 2 and 3.
BugReportPtr BitwiseShiftValidator::checkOperandNegative(OperandSide Side) {
// If the type is unsigned, it cannot be negative
if (!operandExpr(Side)->getType()->isSignedIntegerType())
return nullptr;
// Main check: determine whether the operand is constrained to be negative
if (assumeRequirement(Side, BO_GE, 0))
return nullptr;
std::string ShortMsg = formatv("{0} operand is negative in {1} shift",
Side == OperandSide::Left ? "Left" : "Right",
shiftDir())
.str();
std::string Msg = formatv("The result of {0} shift is undefined "
"because the {1} operand is negative",
shiftDir(),
Side == OperandSide::Left ? "left" : "right")
.str();
return createBugReport(ShortMsg, Msg);
}
BugReportPtr BitwiseShiftValidator::checkLeftShiftOverflow() {
// A right shift cannot be an overflowing left shift...
if (!isLeftShift())
return nullptr;
// In C++ it's well-defined to shift to the sign bit. In C however, it's UB.
// 5.8.2 [expr.shift] (N4296, 2014-11-19)
const bool ShouldPreserveSignBit = !Ctx.getLangOpts().CPlusPlus;
const Expr *LHS = operandExpr(OperandSide::Left);
const QualType LHSTy = LHS->getType();
const unsigned LeftBitWidth = Ctx.getASTContext().getIntWidth(LHSTy);
assert(LeftBitWidth > 0);
// Quote "For unsigned lhs, the value of LHS << RHS is the value of LHS *
// 2^RHS, reduced modulo maximum value of the return type plus 1."
if (LHSTy->isUnsignedIntegerType())
return nullptr;
// We only support concrete integers as left operand.
const auto Left = Ctx.getSVal(LHS).getAs<nonloc::ConcreteInt>();
if (!Left.has_value())
return nullptr;
// We should have already reported a bug if the left operand of the shift was
// negative, so it cannot be negative here.
assert(Left->getValue().isNonNegative());
const unsigned LeftAvailableBitWidth =
LeftBitWidth - static_cast<unsigned>(ShouldPreserveSignBit);
const unsigned UsedBitsInLeftOperand = Left->getValue().getActiveBits();
assert(LeftBitWidth >= UsedBitsInLeftOperand);
const unsigned MaximalAllowedShift =
LeftAvailableBitWidth - UsedBitsInLeftOperand;
if (assumeRequirement(OperandSide::Right, BO_LT, MaximalAllowedShift + 1))
return nullptr;
const std::string CapacityMsg =
formatv("because '{0}' can hold only {1} bits ({2} the sign bit)",
LHSTy.getAsString(), LeftAvailableBitWidth,
ShouldPreserveSignBit ? "excluding" : "including");
const SVal Right = Ctx.getSVal(Op->getRHS());
std::string ShortMsg, Msg;
if (const auto ConcreteRight = Right.getAs<nonloc::ConcreteInt>()) {
// Here ConcreteRight must contain a small non-negative integer, because
// otherwise one of the earlier checks should've reported a bug.
const unsigned RHS = ConcreteRight->getValue().getExtValue();
assert(RHS > MaximalAllowedShift);
const unsigned OverflownBits = RHS - MaximalAllowedShift;
ShortMsg = formatv(
"The shift '{0} << {1}' overflows the capacity of '{2}'",
Left->getValue(), ConcreteRight->getValue(), LHSTy.getAsString());
Msg = formatv(
"The shift '{0} << {1}' is undefined {2}, so {3} bit{4} overflow{5}",
Left->getValue(), ConcreteRight->getValue(), CapacityMsg, OverflownBits,
pluralSuffix(OverflownBits), verbSuffix(OverflownBits));
} else {
ShortMsg = formatv("Left shift of '{0}' overflows the capacity of '{1}'",
Left->getValue(), LHSTy.getAsString());
Msg = formatv(
"Left shift of '{0}' is undefined {1}, so some bits overflow",
Left->getValue(), CapacityMsg);
}
return createBugReport(ShortMsg, Msg);
}
void BitwiseShiftValidator::recordAssumption(OperandSide Side,
BinaryOperator::Opcode Comparison,
unsigned Limit) {
switch (Comparison) {
case BO_GE:
assert(Limit == 0);
NonNegOperands |= (Side == OperandSide::Left ? NonNegLeft : NonNegRight);
break;
case BO_LT:
assert(Side == OperandSide::Right);
if (!UpperBoundBitCount || Limit < UpperBoundBitCount.value())
UpperBoundBitCount = Limit;
break;
default:
llvm_unreachable("this checker does not use other comparison operators");
}
}
const NoteTag *BitwiseShiftValidator::createNoteTag() const {
if (!NonNegOperands && !UpperBoundBitCount)
return nullptr;
SmallString<128> Buf;
llvm::raw_svector_ostream Out(Buf);
Out << "Assuming ";
NoteTagTemplate Templ = NoteTagTemplates[NonNegOperands];
Out << Templ.SignInfo;
if (UpperBoundBitCount)
Out << Templ.UpperBoundIntro << UpperBoundBitCount.value();
const std::string Msg(Out.str());
return Ctx.getNoteTag(Msg, /*isPrunable=*/true);
}
std::unique_ptr<PathSensitiveBugReport>
BitwiseShiftValidator::createBugReport(StringRef ShortMsg, StringRef Msg) const {
ProgramStateRef State = Ctx.getState();
if (ExplodedNode *ErrNode = Ctx.generateErrorNode(State)) {
auto BR =
std::make_unique<PathSensitiveBugReport>(BT, ShortMsg, Msg, ErrNode);
bugreporter::trackExpressionValue(ErrNode, Op->getLHS(), *BR);
bugreporter::trackExpressionValue(ErrNode, Op->getRHS(), *BR);
return BR;
}
return nullptr;
}
} // anonymous namespace
class BitwiseShiftChecker : public Checker<check::PreStmt<BinaryOperator>> {
mutable std::unique_ptr<BugType> BTPtr;
public:
void checkPreStmt(const BinaryOperator *B, CheckerContext &Ctx) const {
BinaryOperator::Opcode Op = B->getOpcode();
if (Op != BO_Shl && Op != BO_Shr)
return;
if (!BTPtr)
BTPtr = std::make_unique<BugType>(this, "Bitwise shift",
"Suspicious operation");
BitwiseShiftValidator(B, Ctx, *BTPtr, Pedantic).run();
}
bool Pedantic = false;
};
void ento::registerBitwiseShiftChecker(CheckerManager &Mgr) {
auto *Chk = Mgr.registerChecker<BitwiseShiftChecker>();
const AnalyzerOptions &Opts = Mgr.getAnalyzerOptions();
Chk->Pedantic = Opts.getCheckerBooleanOption(Chk, "Pedantic");
}
bool ento::shouldRegisterBitwiseShiftChecker(const CheckerManager &mgr) {
return true;
}