Path: blob/main/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BitwiseShiftChecker.cpp
35266 views
//== BitwiseShiftChecker.cpp ------------------------------------*- C++ -*--==//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This file defines BitwiseShiftChecker, which is a path-sensitive checker9// that looks for undefined behavior when the operands of the bitwise shift10// operators '<<' and '>>' are invalid (negative or too large).11//12//===----------------------------------------------------------------------===//1314#include "clang/AST/ASTContext.h"15#include "clang/AST/CharUnits.h"16#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"17#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"18#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"19#include "clang/StaticAnalyzer/Core/Checker.h"20#include "clang/StaticAnalyzer/Core/CheckerManager.h"21#include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h"22#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"23#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"24#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"25#include "llvm/Support/FormatVariadic.h"26#include <memory>2728using namespace clang;29using namespace ento;30using llvm::formatv;3132namespace {33enum class OperandSide { Left, Right };3435using BugReportPtr = std::unique_ptr<PathSensitiveBugReport>;3637struct NoteTagTemplate {38llvm::StringLiteral SignInfo;39llvm::StringLiteral UpperBoundIntro;40};4142constexpr NoteTagTemplate NoteTagTemplates[] = {43{"", "right operand of bit shift is less than "},44{"left operand of bit shift is non-negative", " and right operand is less than "},45{"right operand of bit shift is non-negative", " but less than "},46{"both operands of bit shift are non-negative", " and right operand is less than "}47};4849/// An implementation detail class which is introduced to split the checker50/// logic into several methods while maintaining a consistently updated state51/// and access to other contextual data.52class BitwiseShiftValidator {53CheckerContext &Ctx;54ProgramStateRef FoldedState;55const BinaryOperator *const Op;56const BugType &BT;57const bool PedanticFlag;5859// The following data members are only used for note tag creation:60enum { NonNegLeft = 1, NonNegRight = 2 };61unsigned NonNegOperands = 0;6263std::optional<unsigned> UpperBoundBitCount = std::nullopt;6465public:66BitwiseShiftValidator(const BinaryOperator *O, CheckerContext &C,67const BugType &B, bool P)68: Ctx(C), FoldedState(C.getState()), Op(O), BT(B), PedanticFlag(P) {}69void run();7071private:72const Expr *operandExpr(OperandSide Side) const {73return Side == OperandSide::Left ? Op->getLHS() : Op->getRHS();74}7576bool shouldPerformPedanticChecks() const {77// The pedantic flag has no effect under C++20 because the affected issues78// are no longer undefined under that version of the standard.79return PedanticFlag && !Ctx.getASTContext().getLangOpts().CPlusPlus20;80}8182bool assumeRequirement(OperandSide Side, BinaryOperator::Opcode Cmp, unsigned Limit);8384void recordAssumption(OperandSide Side, BinaryOperator::Opcode Cmp, unsigned Limit);85const NoteTag *createNoteTag() const;8687BugReportPtr createBugReport(StringRef ShortMsg, StringRef Msg) const;8889BugReportPtr checkOvershift();90BugReportPtr checkOperandNegative(OperandSide Side);91BugReportPtr checkLeftShiftOverflow();9293bool isLeftShift() const { return Op->getOpcode() == BO_Shl; }94StringRef shiftDir() const { return isLeftShift() ? "left" : "right"; }95static StringRef pluralSuffix(unsigned n) { return n <= 1 ? "" : "s"; }96static StringRef verbSuffix(unsigned n) { return n <= 1 ? "s" : ""; }97};9899void BitwiseShiftValidator::run() {100// Report a bug if the right operand is >= the bit width of the type of the101// left operand:102if (BugReportPtr BR = checkOvershift()) {103Ctx.emitReport(std::move(BR));104return;105}106107// Report a bug if the right operand is negative:108if (BugReportPtr BR = checkOperandNegative(OperandSide::Right)) {109Ctx.emitReport(std::move(BR));110return;111}112113if (shouldPerformPedanticChecks()) {114// Report a bug if the left operand is negative:115if (BugReportPtr BR = checkOperandNegative(OperandSide::Left)) {116Ctx.emitReport(std::move(BR));117return;118}119120// Report a bug when left shift of a concrete signed value overflows:121if (BugReportPtr BR = checkLeftShiftOverflow()) {122Ctx.emitReport(std::move(BR));123return;124}125}126127// No bugs detected, update the state and add a single note tag which128// summarizes the new assumptions.129Ctx.addTransition(FoldedState, createNoteTag());130}131132/// This method checks a requirement that must be satisfied by the value on the133/// given Side of a bitwise shift operator in well-defined code. If the134/// requirement is incompatible with prior knowledge, this method reports135/// failure by returning false.136bool BitwiseShiftValidator::assumeRequirement(OperandSide Side,137BinaryOperator::Opcode Comparison,138unsigned Limit) {139SValBuilder &SVB = Ctx.getSValBuilder();140141const SVal OperandVal = Ctx.getSVal(operandExpr(Side));142const auto LimitVal = SVB.makeIntVal(Limit, Ctx.getASTContext().IntTy);143// Note that the type of `LimitVal` must be a signed, because otherwise a144// negative `Val` could be converted to a large positive value.145146auto ResultVal = SVB.evalBinOp(FoldedState, Comparison, OperandVal, LimitVal,147SVB.getConditionType());148if (auto DURes = ResultVal.getAs<DefinedOrUnknownSVal>()) {149auto [StTrue, StFalse] = FoldedState->assume(DURes.value());150if (!StTrue) {151// We detected undefined behavior (the caller will report it).152FoldedState = StFalse;153return false;154}155// The code may be valid, so let's assume that it's valid:156FoldedState = StTrue;157if (StFalse) {158// Record note tag data for the assumption that we made159recordAssumption(Side, Comparison, Limit);160}161}162return true;163}164165BugReportPtr BitwiseShiftValidator::checkOvershift() {166const QualType LHSTy = Op->getLHS()->getType();167const unsigned LHSBitWidth = Ctx.getASTContext().getIntWidth(LHSTy);168169if (assumeRequirement(OperandSide::Right, BO_LT, LHSBitWidth))170return nullptr;171172const SVal Right = Ctx.getSVal(operandExpr(OperandSide::Right));173174std::string RightOpStr = "", LowerBoundStr = "";175if (auto ConcreteRight = Right.getAs<nonloc::ConcreteInt>())176RightOpStr = formatv(" '{0}'", ConcreteRight->getValue());177else {178SValBuilder &SVB = Ctx.getSValBuilder();179if (const llvm::APSInt *MinRight = SVB.getMinValue(FoldedState, Right)) {180LowerBoundStr = formatv(" >= {0},", MinRight->getExtValue());181}182}183184std::string ShortMsg = formatv(185"{0} shift{1}{2} overflows the capacity of '{3}'",186isLeftShift() ? "Left" : "Right", RightOpStr.empty() ? "" : " by",187RightOpStr, LHSTy.getAsString());188std::string Msg = formatv(189"The result of {0} shift is undefined because the right "190"operand{1} is{2} not smaller than {3}, the capacity of '{4}'",191shiftDir(), RightOpStr, LowerBoundStr, LHSBitWidth, LHSTy.getAsString());192return createBugReport(ShortMsg, Msg);193}194195// Before C++20, at 5.8 [expr.shift] (N4296, 2014-11-19) the standard says196// 1. "... The behaviour is undefined if the right operand is negative..."197// 2. "The value of E1 << E2 ...198// if E1 has a signed type and non-negative value ...199// otherwise, the behavior is undefined."200// 3. "The value of E1 >> E2 ...201// If E1 has a signed type and a negative value,202// the resulting value is implementation-defined."203// However, negative left arguments work in practice and the C++20 standard204// eliminates conditions 2 and 3.205BugReportPtr BitwiseShiftValidator::checkOperandNegative(OperandSide Side) {206// If the type is unsigned, it cannot be negative207if (!operandExpr(Side)->getType()->isSignedIntegerType())208return nullptr;209210// Main check: determine whether the operand is constrained to be negative211if (assumeRequirement(Side, BO_GE, 0))212return nullptr;213214std::string ShortMsg = formatv("{0} operand is negative in {1} shift",215Side == OperandSide::Left ? "Left" : "Right",216shiftDir())217.str();218std::string Msg = formatv("The result of {0} shift is undefined "219"because the {1} operand is negative",220shiftDir(),221Side == OperandSide::Left ? "left" : "right")222.str();223224return createBugReport(ShortMsg, Msg);225}226227BugReportPtr BitwiseShiftValidator::checkLeftShiftOverflow() {228// A right shift cannot be an overflowing left shift...229if (!isLeftShift())230return nullptr;231232// In C++ it's well-defined to shift to the sign bit. In C however, it's UB.233// 5.8.2 [expr.shift] (N4296, 2014-11-19)234const bool ShouldPreserveSignBit = !Ctx.getLangOpts().CPlusPlus;235236const Expr *LHS = operandExpr(OperandSide::Left);237const QualType LHSTy = LHS->getType();238const unsigned LeftBitWidth = Ctx.getASTContext().getIntWidth(LHSTy);239assert(LeftBitWidth > 0);240241// Quote "For unsigned lhs, the value of LHS << RHS is the value of LHS *242// 2^RHS, reduced modulo maximum value of the return type plus 1."243if (LHSTy->isUnsignedIntegerType())244return nullptr;245246// We only support concrete integers as left operand.247const auto Left = Ctx.getSVal(LHS).getAs<nonloc::ConcreteInt>();248if (!Left.has_value())249return nullptr;250251// We should have already reported a bug if the left operand of the shift was252// negative, so it cannot be negative here.253assert(Left->getValue().isNonNegative());254255const unsigned LeftAvailableBitWidth =256LeftBitWidth - static_cast<unsigned>(ShouldPreserveSignBit);257const unsigned UsedBitsInLeftOperand = Left->getValue().getActiveBits();258assert(LeftBitWidth >= UsedBitsInLeftOperand);259const unsigned MaximalAllowedShift =260LeftAvailableBitWidth - UsedBitsInLeftOperand;261262if (assumeRequirement(OperandSide::Right, BO_LT, MaximalAllowedShift + 1))263return nullptr;264265const std::string CapacityMsg =266formatv("because '{0}' can hold only {1} bits ({2} the sign bit)",267LHSTy.getAsString(), LeftAvailableBitWidth,268ShouldPreserveSignBit ? "excluding" : "including");269270const SVal Right = Ctx.getSVal(Op->getRHS());271272std::string ShortMsg, Msg;273if (const auto ConcreteRight = Right.getAs<nonloc::ConcreteInt>()) {274// Here ConcreteRight must contain a small non-negative integer, because275// otherwise one of the earlier checks should've reported a bug.276const unsigned RHS = ConcreteRight->getValue().getExtValue();277assert(RHS > MaximalAllowedShift);278const unsigned OverflownBits = RHS - MaximalAllowedShift;279ShortMsg = formatv(280"The shift '{0} << {1}' overflows the capacity of '{2}'",281Left->getValue(), ConcreteRight->getValue(), LHSTy.getAsString());282Msg = formatv(283"The shift '{0} << {1}' is undefined {2}, so {3} bit{4} overflow{5}",284Left->getValue(), ConcreteRight->getValue(), CapacityMsg, OverflownBits,285pluralSuffix(OverflownBits), verbSuffix(OverflownBits));286} else {287ShortMsg = formatv("Left shift of '{0}' overflows the capacity of '{1}'",288Left->getValue(), LHSTy.getAsString());289Msg = formatv(290"Left shift of '{0}' is undefined {1}, so some bits overflow",291Left->getValue(), CapacityMsg);292}293294return createBugReport(ShortMsg, Msg);295}296297void BitwiseShiftValidator::recordAssumption(OperandSide Side,298BinaryOperator::Opcode Comparison,299unsigned Limit) {300switch (Comparison) {301case BO_GE:302assert(Limit == 0);303NonNegOperands |= (Side == OperandSide::Left ? NonNegLeft : NonNegRight);304break;305case BO_LT:306assert(Side == OperandSide::Right);307if (!UpperBoundBitCount || Limit < UpperBoundBitCount.value())308UpperBoundBitCount = Limit;309break;310default:311llvm_unreachable("this checker does not use other comparison operators");312}313}314315const NoteTag *BitwiseShiftValidator::createNoteTag() const {316if (!NonNegOperands && !UpperBoundBitCount)317return nullptr;318319SmallString<128> Buf;320llvm::raw_svector_ostream Out(Buf);321Out << "Assuming ";322NoteTagTemplate Templ = NoteTagTemplates[NonNegOperands];323Out << Templ.SignInfo;324if (UpperBoundBitCount)325Out << Templ.UpperBoundIntro << UpperBoundBitCount.value();326const std::string Msg(Out.str());327328return Ctx.getNoteTag(Msg, /*isPrunable=*/true);329}330331std::unique_ptr<PathSensitiveBugReport>332BitwiseShiftValidator::createBugReport(StringRef ShortMsg, StringRef Msg) const {333ProgramStateRef State = Ctx.getState();334if (ExplodedNode *ErrNode = Ctx.generateErrorNode(State)) {335auto BR =336std::make_unique<PathSensitiveBugReport>(BT, ShortMsg, Msg, ErrNode);337bugreporter::trackExpressionValue(ErrNode, Op->getLHS(), *BR);338bugreporter::trackExpressionValue(ErrNode, Op->getRHS(), *BR);339return BR;340}341return nullptr;342}343} // anonymous namespace344345class BitwiseShiftChecker : public Checker<check::PreStmt<BinaryOperator>> {346BugType BT{this, "Bitwise shift", "Suspicious operation"};347348public:349void checkPreStmt(const BinaryOperator *B, CheckerContext &Ctx) const {350BinaryOperator::Opcode Op = B->getOpcode();351352if (Op != BO_Shl && Op != BO_Shr)353return;354355BitwiseShiftValidator(B, Ctx, BT, Pedantic).run();356}357358bool Pedantic = false;359};360361void ento::registerBitwiseShiftChecker(CheckerManager &Mgr) {362auto *Chk = Mgr.registerChecker<BitwiseShiftChecker>();363const AnalyzerOptions &Opts = Mgr.getAnalyzerOptions();364Chk->Pedantic = Opts.getCheckerBooleanOption(Chk, "Pedantic");365}366367bool ento::shouldRegisterBitwiseShiftChecker(const CheckerManager &mgr) {368return true;369}370371372