1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
|
//== 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);
MinRight && *MinRight >= LHSBitWidth) {
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 int64_t RHS = ConcreteRight->getValue()->getExtValue();
assert(RHS > MaximalAllowedShift);
const int64_t 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;
}
|