| //== 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 = "", LowerBoundStr = ""; |
| if (auto ConcreteRight = Right.getAs<nonloc::ConcreteInt>()) |
| 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<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>> { |
| 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<BitwiseShiftChecker>(); |
| const AnalyzerOptions &Opts = Mgr.getAnalyzerOptions(); |
| Chk->Pedantic = Opts.getCheckerBooleanOption(Chk, "Pedantic"); |
| } |
| |
| bool ento::shouldRegisterBitwiseShiftChecker(const CheckerManager &mgr) { |
| return true; |
| } |