1 | //== BitwiseShiftChecker.cpp ------------------------------------*- C++ -*--==// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This file defines BitwiseShiftChecker, which is a path-sensitive checker |
10 | // that looks for undefined behavior when the operands of the bitwise shift |
11 | // operators '<<' and '>>' are invalid (negative or too large). |
12 | // |
13 | //===----------------------------------------------------------------------===// |
14 | |
15 | #include "clang/AST/ASTContext.h" |
16 | #include "clang/AST/CharUnits.h" |
17 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
18 | #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" |
19 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
20 | #include "clang/StaticAnalyzer/Core/Checker.h" |
21 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
22 | #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" |
23 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
24 | #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" |
25 | #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" |
26 | #include "llvm/Support/FormatVariadic.h" |
27 | #include <memory> |
28 | |
29 | using namespace clang; |
30 | using namespace ento; |
31 | using llvm::formatv; |
32 | |
33 | namespace { |
34 | enum class OperandSide { Left, Right }; |
35 | |
36 | using BugReportPtr = std::unique_ptr<PathSensitiveBugReport>; |
37 | |
38 | struct NoteTagTemplate { |
39 | llvm::StringLiteral SignInfo; |
40 | llvm::StringLiteral UpperBoundIntro; |
41 | }; |
42 | |
43 | constexpr NoteTagTemplate NoteTagTemplates[] = { |
44 | {.SignInfo: "" , .UpperBoundIntro: "right operand of bit shift is less than " }, |
45 | {.SignInfo: "left operand of bit shift is non-negative" , .UpperBoundIntro: " and right operand is less than " }, |
46 | {.SignInfo: "right operand of bit shift is non-negative" , .UpperBoundIntro: " but less than " }, |
47 | {.SignInfo: "both operands of bit shift are non-negative" , .UpperBoundIntro: " and right operand is less than " } |
48 | }; |
49 | |
50 | /// An implementation detail class which is introduced to split the checker |
51 | /// logic into several methods while maintaining a consistently updated state |
52 | /// and access to other contextual data. |
53 | class BitwiseShiftValidator { |
54 | CheckerContext &Ctx; |
55 | ProgramStateRef FoldedState; |
56 | const BinaryOperator *const Op; |
57 | const BugType &BT; |
58 | const bool PedanticFlag; |
59 | |
60 | // The following data members are only used for note tag creation: |
61 | enum { NonNegLeft = 1, NonNegRight = 2 }; |
62 | unsigned NonNegOperands = 0; |
63 | |
64 | std::optional<unsigned> UpperBoundBitCount = std::nullopt; |
65 | |
66 | public: |
67 | BitwiseShiftValidator(const BinaryOperator *O, CheckerContext &C, |
68 | const BugType &B, bool P) |
69 | : Ctx(C), FoldedState(C.getState()), Op(O), BT(B), PedanticFlag(P) {} |
70 | void run(); |
71 | |
72 | private: |
73 | const Expr *operandExpr(OperandSide Side) const { |
74 | return Side == OperandSide::Left ? Op->getLHS() : Op->getRHS(); |
75 | } |
76 | |
77 | bool shouldPerformPedanticChecks() const { |
78 | // The pedantic flag has no effect under C++20 because the affected issues |
79 | // are no longer undefined under that version of the standard. |
80 | return PedanticFlag && !Ctx.getASTContext().getLangOpts().CPlusPlus20; |
81 | } |
82 | |
83 | bool assumeRequirement(OperandSide Side, BinaryOperator::Opcode Cmp, unsigned Limit); |
84 | |
85 | void recordAssumption(OperandSide Side, BinaryOperator::Opcode Cmp, unsigned Limit); |
86 | const NoteTag *createNoteTag() const; |
87 | |
88 | BugReportPtr createBugReport(StringRef ShortMsg, StringRef Msg) const; |
89 | |
90 | BugReportPtr checkOvershift(); |
91 | BugReportPtr checkOperandNegative(OperandSide Side); |
92 | BugReportPtr checkLeftShiftOverflow(); |
93 | |
94 | bool isLeftShift() const { return Op->getOpcode() == BO_Shl; } |
95 | StringRef shiftDir() const { return isLeftShift() ? "left" : "right" ; } |
96 | static StringRef pluralSuffix(unsigned n) { return n <= 1 ? "" : "s" ; } |
97 | static StringRef verbSuffix(unsigned n) { return n <= 1 ? "s" : "" ; } |
98 | }; |
99 | |
100 | void BitwiseShiftValidator::run() { |
101 | // Report a bug if the right operand is >= the bit width of the type of the |
102 | // left operand: |
103 | if (BugReportPtr BR = checkOvershift()) { |
104 | Ctx.emitReport(R: std::move(BR)); |
105 | return; |
106 | } |
107 | |
108 | // Report a bug if the right operand is negative: |
109 | if (BugReportPtr BR = checkOperandNegative(Side: OperandSide::Right)) { |
110 | Ctx.emitReport(R: std::move(BR)); |
111 | return; |
112 | } |
113 | |
114 | if (shouldPerformPedanticChecks()) { |
115 | // Report a bug if the left operand is negative: |
116 | if (BugReportPtr BR = checkOperandNegative(Side: OperandSide::Left)) { |
117 | Ctx.emitReport(R: std::move(BR)); |
118 | return; |
119 | } |
120 | |
121 | // Report a bug when left shift of a concrete signed value overflows: |
122 | if (BugReportPtr BR = checkLeftShiftOverflow()) { |
123 | Ctx.emitReport(R: std::move(BR)); |
124 | return; |
125 | } |
126 | } |
127 | |
128 | // No bugs detected, update the state and add a single note tag which |
129 | // summarizes the new assumptions. |
130 | Ctx.addTransition(State: FoldedState, Tag: createNoteTag()); |
131 | } |
132 | |
133 | /// This method checks a requirement that must be satisfied by the value on the |
134 | /// given Side of a bitwise shift operator in well-defined code. If the |
135 | /// requirement is incompatible with prior knowledge, this method reports |
136 | /// failure by returning false. |
137 | bool BitwiseShiftValidator::assumeRequirement(OperandSide Side, |
138 | BinaryOperator::Opcode Comparison, |
139 | unsigned Limit) { |
140 | SValBuilder &SVB = Ctx.getSValBuilder(); |
141 | |
142 | const SVal OperandVal = Ctx.getSVal(S: operandExpr(Side)); |
143 | const auto LimitVal = SVB.makeIntVal(integer: Limit, type: Ctx.getASTContext().IntTy); |
144 | // Note that the type of `LimitVal` must be a signed, because otherwise a |
145 | // negative `Val` could be converted to a large positive value. |
146 | |
147 | auto ResultVal = SVB.evalBinOp(state: FoldedState, op: Comparison, lhs: OperandVal, rhs: LimitVal, |
148 | type: SVB.getConditionType()); |
149 | if (auto DURes = ResultVal.getAs<DefinedOrUnknownSVal>()) { |
150 | auto [StTrue, StFalse] = FoldedState->assume(Cond: DURes.value()); |
151 | if (!StTrue) { |
152 | // We detected undefined behavior (the caller will report it). |
153 | FoldedState = StFalse; |
154 | return false; |
155 | } |
156 | // The code may be valid, so let's assume that it's valid: |
157 | FoldedState = StTrue; |
158 | if (StFalse) { |
159 | // Record note tag data for the assumption that we made |
160 | recordAssumption(Side, Cmp: Comparison, Limit); |
161 | } |
162 | } |
163 | return true; |
164 | } |
165 | |
166 | BugReportPtr BitwiseShiftValidator::checkOvershift() { |
167 | const QualType LHSTy = Op->getLHS()->getType(); |
168 | const unsigned LHSBitWidth = Ctx.getASTContext().getIntWidth(T: LHSTy); |
169 | |
170 | if (assumeRequirement(Side: OperandSide::Right, Comparison: BO_LT, Limit: LHSBitWidth)) |
171 | return nullptr; |
172 | |
173 | const SVal Right = Ctx.getSVal(S: operandExpr(Side: OperandSide::Right)); |
174 | |
175 | std::string RightOpStr = "" , LowerBoundStr = "" ; |
176 | if (auto ConcreteRight = Right.getAs<nonloc::ConcreteInt>()) |
177 | RightOpStr = formatv(Fmt: " '{0}'" , Vals: ConcreteRight->getValue()); |
178 | else { |
179 | SValBuilder &SVB = Ctx.getSValBuilder(); |
180 | if (const llvm::APSInt *MinRight = SVB.getMinValue(state: FoldedState, val: Right)) { |
181 | LowerBoundStr = formatv(Fmt: " >= {0}," , Vals: MinRight->getExtValue()); |
182 | } |
183 | } |
184 | |
185 | std::string ShortMsg = formatv( |
186 | Fmt: "{0} shift{1}{2} overflows the capacity of '{3}'" , |
187 | Vals: isLeftShift() ? "Left" : "Right" , Vals: RightOpStr.empty() ? "" : " by" , |
188 | Vals&: RightOpStr, Vals: LHSTy.getAsString()); |
189 | std::string Msg = formatv( |
190 | Fmt: "The result of {0} shift is undefined because the right " |
191 | "operand{1} is{2} not smaller than {3}, the capacity of '{4}'" , |
192 | Vals: shiftDir(), Vals&: RightOpStr, Vals&: LowerBoundStr, Vals: LHSBitWidth, Vals: LHSTy.getAsString()); |
193 | return createBugReport(ShortMsg, Msg); |
194 | } |
195 | |
196 | // Before C++20, at 5.8 [expr.shift] (N4296, 2014-11-19) the standard says |
197 | // 1. "... The behaviour is undefined if the right operand is negative..." |
198 | // 2. "The value of E1 << E2 ... |
199 | // if E1 has a signed type and non-negative value ... |
200 | // otherwise, the behavior is undefined." |
201 | // 3. "The value of E1 >> E2 ... |
202 | // If E1 has a signed type and a negative value, |
203 | // the resulting value is implementation-defined." |
204 | // However, negative left arguments work in practice and the C++20 standard |
205 | // eliminates conditions 2 and 3. |
206 | BugReportPtr BitwiseShiftValidator::checkOperandNegative(OperandSide Side) { |
207 | // If the type is unsigned, it cannot be negative |
208 | if (!operandExpr(Side)->getType()->isSignedIntegerType()) |
209 | return nullptr; |
210 | |
211 | // Main check: determine whether the operand is constrained to be negative |
212 | if (assumeRequirement(Side, Comparison: BO_GE, Limit: 0)) |
213 | return nullptr; |
214 | |
215 | std::string ShortMsg = formatv(Fmt: "{0} operand is negative in {1} shift" , |
216 | Vals: Side == OperandSide::Left ? "Left" : "Right" , |
217 | Vals: shiftDir()) |
218 | .str(); |
219 | std::string Msg = formatv(Fmt: "The result of {0} shift is undefined " |
220 | "because the {1} operand is negative" , |
221 | Vals: shiftDir(), |
222 | Vals: Side == OperandSide::Left ? "left" : "right" ) |
223 | .str(); |
224 | |
225 | return createBugReport(ShortMsg, Msg); |
226 | } |
227 | |
228 | BugReportPtr BitwiseShiftValidator::checkLeftShiftOverflow() { |
229 | // A right shift cannot be an overflowing left shift... |
230 | if (!isLeftShift()) |
231 | return nullptr; |
232 | |
233 | // In C++ it's well-defined to shift to the sign bit. In C however, it's UB. |
234 | // 5.8.2 [expr.shift] (N4296, 2014-11-19) |
235 | const bool ShouldPreserveSignBit = !Ctx.getLangOpts().CPlusPlus; |
236 | |
237 | const Expr *LHS = operandExpr(Side: OperandSide::Left); |
238 | const QualType LHSTy = LHS->getType(); |
239 | const unsigned LeftBitWidth = Ctx.getASTContext().getIntWidth(T: LHSTy); |
240 | assert(LeftBitWidth > 0); |
241 | |
242 | // Quote "For unsigned lhs, the value of LHS << RHS is the value of LHS * |
243 | // 2^RHS, reduced modulo maximum value of the return type plus 1." |
244 | if (LHSTy->isUnsignedIntegerType()) |
245 | return nullptr; |
246 | |
247 | // We only support concrete integers as left operand. |
248 | const auto Left = Ctx.getSVal(S: LHS).getAs<nonloc::ConcreteInt>(); |
249 | if (!Left.has_value()) |
250 | return nullptr; |
251 | |
252 | // We should have already reported a bug if the left operand of the shift was |
253 | // negative, so it cannot be negative here. |
254 | assert(Left->getValue().isNonNegative()); |
255 | |
256 | const unsigned LeftAvailableBitWidth = |
257 | LeftBitWidth - static_cast<unsigned>(ShouldPreserveSignBit); |
258 | const unsigned UsedBitsInLeftOperand = Left->getValue().getActiveBits(); |
259 | assert(LeftBitWidth >= UsedBitsInLeftOperand); |
260 | const unsigned MaximalAllowedShift = |
261 | LeftAvailableBitWidth - UsedBitsInLeftOperand; |
262 | |
263 | if (assumeRequirement(Side: OperandSide::Right, Comparison: BO_LT, Limit: MaximalAllowedShift + 1)) |
264 | return nullptr; |
265 | |
266 | const std::string CapacityMsg = |
267 | formatv(Fmt: "because '{0}' can hold only {1} bits ({2} the sign bit)" , |
268 | Vals: LHSTy.getAsString(), Vals: LeftAvailableBitWidth, |
269 | Vals: ShouldPreserveSignBit ? "excluding" : "including" ); |
270 | |
271 | const SVal Right = Ctx.getSVal(S: Op->getRHS()); |
272 | |
273 | std::string ShortMsg, Msg; |
274 | if (const auto ConcreteRight = Right.getAs<nonloc::ConcreteInt>()) { |
275 | // Here ConcreteRight must contain a small non-negative integer, because |
276 | // otherwise one of the earlier checks should've reported a bug. |
277 | const unsigned RHS = ConcreteRight->getValue().getExtValue(); |
278 | assert(RHS > MaximalAllowedShift); |
279 | const unsigned OverflownBits = RHS - MaximalAllowedShift; |
280 | ShortMsg = formatv( |
281 | Fmt: "The shift '{0} << {1}' overflows the capacity of '{2}'" , |
282 | Vals: Left->getValue(), Vals: ConcreteRight->getValue(), Vals: LHSTy.getAsString()); |
283 | Msg = formatv( |
284 | Fmt: "The shift '{0} << {1}' is undefined {2}, so {3} bit{4} overflow{5}" , |
285 | Vals: Left->getValue(), Vals: ConcreteRight->getValue(), Vals: CapacityMsg, Vals: OverflownBits, |
286 | Vals: pluralSuffix(n: OverflownBits), Vals: verbSuffix(n: OverflownBits)); |
287 | } else { |
288 | ShortMsg = formatv(Fmt: "Left shift of '{0}' overflows the capacity of '{1}'" , |
289 | Vals: Left->getValue(), Vals: LHSTy.getAsString()); |
290 | Msg = formatv( |
291 | Fmt: "Left shift of '{0}' is undefined {1}, so some bits overflow" , |
292 | Vals: Left->getValue(), Vals: CapacityMsg); |
293 | } |
294 | |
295 | return createBugReport(ShortMsg, Msg); |
296 | } |
297 | |
298 | void BitwiseShiftValidator::recordAssumption(OperandSide Side, |
299 | BinaryOperator::Opcode Comparison, |
300 | unsigned Limit) { |
301 | switch (Comparison) { |
302 | case BO_GE: |
303 | assert(Limit == 0); |
304 | NonNegOperands |= (Side == OperandSide::Left ? NonNegLeft : NonNegRight); |
305 | break; |
306 | case BO_LT: |
307 | assert(Side == OperandSide::Right); |
308 | if (!UpperBoundBitCount || Limit < UpperBoundBitCount.value()) |
309 | UpperBoundBitCount = Limit; |
310 | break; |
311 | default: |
312 | llvm_unreachable("this checker does not use other comparison operators" ); |
313 | } |
314 | } |
315 | |
316 | const NoteTag *BitwiseShiftValidator::createNoteTag() const { |
317 | if (!NonNegOperands && !UpperBoundBitCount) |
318 | return nullptr; |
319 | |
320 | SmallString<128> Buf; |
321 | llvm::raw_svector_ostream Out(Buf); |
322 | Out << "Assuming " ; |
323 | NoteTagTemplate Templ = NoteTagTemplates[NonNegOperands]; |
324 | Out << Templ.SignInfo; |
325 | if (UpperBoundBitCount) |
326 | Out << Templ.UpperBoundIntro << UpperBoundBitCount.value(); |
327 | const std::string Msg(Out.str()); |
328 | |
329 | return Ctx.getNoteTag(Note: Msg, /*isPrunable=*/IsPrunable: true); |
330 | } |
331 | |
332 | std::unique_ptr<PathSensitiveBugReport> |
333 | BitwiseShiftValidator::createBugReport(StringRef ShortMsg, StringRef Msg) const { |
334 | ProgramStateRef State = Ctx.getState(); |
335 | if (ExplodedNode *ErrNode = Ctx.generateErrorNode(State)) { |
336 | auto BR = |
337 | std::make_unique<PathSensitiveBugReport>(args: BT, args&: ShortMsg, args&: Msg, args&: ErrNode); |
338 | bugreporter::trackExpressionValue(N: ErrNode, E: Op->getLHS(), R&: *BR); |
339 | bugreporter::trackExpressionValue(N: ErrNode, E: Op->getRHS(), R&: *BR); |
340 | return BR; |
341 | } |
342 | return nullptr; |
343 | } |
344 | } // anonymous namespace |
345 | |
346 | class BitwiseShiftChecker : public Checker<check::PreStmt<BinaryOperator>> { |
347 | BugType BT{this, "Bitwise shift" , "Suspicious operation" }; |
348 | |
349 | public: |
350 | void checkPreStmt(const BinaryOperator *B, CheckerContext &Ctx) const { |
351 | BinaryOperator::Opcode Op = B->getOpcode(); |
352 | |
353 | if (Op != BO_Shl && Op != BO_Shr) |
354 | return; |
355 | |
356 | BitwiseShiftValidator(B, Ctx, BT, Pedantic).run(); |
357 | } |
358 | |
359 | bool Pedantic = false; |
360 | }; |
361 | |
362 | void ento::registerBitwiseShiftChecker(CheckerManager &Mgr) { |
363 | auto *Chk = Mgr.registerChecker<BitwiseShiftChecker>(); |
364 | const AnalyzerOptions &Opts = Mgr.getAnalyzerOptions(); |
365 | Chk->Pedantic = Opts.getCheckerBooleanOption(C: Chk, OptionName: "Pedantic" ); |
366 | } |
367 | |
368 | bool ento::shouldRegisterBitwiseShiftChecker(const CheckerManager &mgr) { |
369 | return true; |
370 | } |
371 | |