| 1 | //=== UndefResultChecker.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 defines UndefResultChecker, a builtin check in ExprEngine that |
| 10 | // performs checks for undefined results of non-assignment binary operators. |
| 11 | // |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
| 15 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
| 16 | #include "clang/StaticAnalyzer/Core/Checker.h" |
| 17 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
| 18 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
| 19 | #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" |
| 20 | #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" |
| 21 | #include "llvm/Support/raw_ostream.h" |
| 22 | |
| 23 | using namespace clang; |
| 24 | using namespace ento; |
| 25 | |
| 26 | namespace { |
| 27 | class UndefResultChecker |
| 28 | : public Checker< check::PostStmt<BinaryOperator> > { |
| 29 | |
| 30 | const BugType BT{this, "Result of operation is garbage or undefined" }; |
| 31 | |
| 32 | public: |
| 33 | void checkPostStmt(const BinaryOperator *B, CheckerContext &C) const; |
| 34 | }; |
| 35 | } // end anonymous namespace |
| 36 | |
| 37 | static bool isArrayIndexOutOfBounds(CheckerContext &C, const Expr *Ex) { |
| 38 | ProgramStateRef state = C.getState(); |
| 39 | |
| 40 | if (!isa<ArraySubscriptExpr>(Val: Ex)) |
| 41 | return false; |
| 42 | |
| 43 | SVal Loc = C.getSVal(S: Ex); |
| 44 | if (!Loc.isValid()) |
| 45 | return false; |
| 46 | |
| 47 | const MemRegion *MR = Loc.castAs<loc::MemRegionVal>().getRegion(); |
| 48 | const ElementRegion *ER = dyn_cast<ElementRegion>(Val: MR); |
| 49 | if (!ER) |
| 50 | return false; |
| 51 | |
| 52 | DefinedOrUnknownSVal Idx = ER->getIndex().castAs<DefinedOrUnknownSVal>(); |
| 53 | DefinedOrUnknownSVal ElementCount = getDynamicElementCount( |
| 54 | State: state, MR: ER->getSuperRegion(), SVB&: C.getSValBuilder(), Ty: ER->getValueType()); |
| 55 | ProgramStateRef StInBound, StOutBound; |
| 56 | std::tie(args&: StInBound, args&: StOutBound) = state->assumeInBoundDual(idx: Idx, upperBound: ElementCount); |
| 57 | return StOutBound && !StInBound; |
| 58 | } |
| 59 | |
| 60 | void UndefResultChecker::checkPostStmt(const BinaryOperator *B, |
| 61 | CheckerContext &C) const { |
| 62 | if (C.getSVal(S: B).isUndef()) { |
| 63 | |
| 64 | // Do not report assignments of uninitialized values inside swap functions. |
| 65 | // This should allow to swap partially uninitialized structs |
| 66 | if (const FunctionDecl *EnclosingFunctionDecl = |
| 67 | dyn_cast<FunctionDecl>(Val: C.getStackFrame()->getDecl())) |
| 68 | if (C.getCalleeName(FunDecl: EnclosingFunctionDecl) == "swap" ) |
| 69 | return; |
| 70 | |
| 71 | // Generate an error node. |
| 72 | ExplodedNode *N = C.generateErrorNode(); |
| 73 | if (!N) |
| 74 | return; |
| 75 | |
| 76 | SmallString<256> sbuf; |
| 77 | llvm::raw_svector_ostream OS(sbuf); |
| 78 | const Expr *Ex = nullptr; |
| 79 | bool isLeft = true; |
| 80 | |
| 81 | if (C.getSVal(S: B->getLHS()).isUndef()) { |
| 82 | Ex = B->getLHS()->IgnoreParenCasts(); |
| 83 | isLeft = true; |
| 84 | } |
| 85 | else if (C.getSVal(S: B->getRHS()).isUndef()) { |
| 86 | Ex = B->getRHS()->IgnoreParenCasts(); |
| 87 | isLeft = false; |
| 88 | } |
| 89 | |
| 90 | if (Ex) { |
| 91 | OS << "The " << (isLeft ? "left" : "right" ) << " operand of '" |
| 92 | << BinaryOperator::getOpcodeStr(Op: B->getOpcode()) |
| 93 | << "' is a garbage value" ; |
| 94 | if (isArrayIndexOutOfBounds(C, Ex)) |
| 95 | OS << " due to array index out of bounds" ; |
| 96 | } else { |
| 97 | // Neither operand was undefined, but the result is undefined. |
| 98 | OS << "The result of the '" |
| 99 | << BinaryOperator::getOpcodeStr(Op: B->getOpcode()) |
| 100 | << "' expression is undefined" ; |
| 101 | } |
| 102 | auto report = std::make_unique<PathSensitiveBugReport>(args: BT, args: OS.str(), args&: N); |
| 103 | if (Ex) { |
| 104 | report->addRange(R: Ex->getSourceRange()); |
| 105 | bugreporter::trackExpressionValue(N, E: Ex, R&: *report); |
| 106 | } |
| 107 | else |
| 108 | bugreporter::trackExpressionValue(N, E: B, R&: *report); |
| 109 | |
| 110 | C.emitReport(R: std::move(report)); |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | void ento::registerUndefResultChecker(CheckerManager &mgr) { |
| 115 | mgr.registerChecker<UndefResultChecker>(); |
| 116 | } |
| 117 | |
| 118 | bool ento::shouldRegisterUndefResultChecker(const CheckerManager &mgr) { |
| 119 | return true; |
| 120 | } |
| 121 | |