//== 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 using namespace clang; using namespace ento; using llvm::formatv; namespace { enum class OperandSide { Left, Right }; using BugReportPtr = std::unique_ptr; 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 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()) { 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 = "", LowerBoundStr = ""; if (auto ConcreteRight = Right.getAs()) RightOpStr = formatv(" '{0}'", ConcreteRight->getValue()); else { SValBuilder &SVB = Ctx.getSValBuilder(); if (const llvm::APSInt *MinRight = SVB.getMinValue(FoldedState, Right)) { LowerBoundStr = formatv(" >= {0},", MinRight->getExtValue()); } } 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{2} not smaller than {3}, the capacity of '{4}'", shiftDir(), RightOpStr, LowerBoundStr, 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(); 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(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()) { // 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 BitwiseShiftValidator::createBugReport(StringRef ShortMsg, StringRef Msg) const { ProgramStateRef State = Ctx.getState(); if (ExplodedNode *ErrNode = Ctx.generateErrorNode(State)) { auto BR = std::make_unique(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> { BugType BT{this, "Bitwise shift", "Suspicious operation"}; public: void checkPreStmt(const BinaryOperator *B, CheckerContext &Ctx) const { BinaryOperator::Opcode Op = B->getOpcode(); if (Op != BO_Shl && Op != BO_Shr) return; BitwiseShiftValidator(B, Ctx, BT, Pedantic).run(); } bool Pedantic = false; }; void ento::registerBitwiseShiftChecker(CheckerManager &Mgr) { auto *Chk = Mgr.registerChecker(); const AnalyzerOptions &Opts = Mgr.getAnalyzerOptions(); Chk->Pedantic = Opts.getCheckerBooleanOption(Chk, "Pedantic"); } bool ento::shouldRegisterBitwiseShiftChecker(const CheckerManager &mgr) { return true; }