| 1 | //== UnconditionalVAArgChecker.cpp -----------------------------------------==// |
| 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 a checker to detect functions that unconditionally call |
| 10 | // va_arg() and would fail if they were called with zero variadic arguments. |
| 11 | // |
| 12 | // This checker is only partially path-sensitive: it relies on the symbolic |
| 13 | // execution of the analyzer engine to follow the execution path from the |
| 14 | // beginning of a function to a va_arg() call and determine whether there are |
| 15 | // any branching points on that path -- but it uses BasicBugReport reports |
| 16 | // because report path wouldn't contain any useful information. (As this |
| 17 | // checker diagnoses a property of a variadic function, the path before that |
| 18 | // function is irrelevant; then the unconditional part of path is trivial.) |
| 19 | // |
| 20 | // The AST matching framework of Clang Tidy is not powerful enough to express |
| 21 | // this "no branching on the execution path" relationship, at least not without |
| 22 | // reimplementing a crude and fragile form of symbolic execution. |
| 23 | // |
| 24 | //===----------------------------------------------------------------------===// |
| 25 | |
| 26 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
| 27 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
| 28 | #include "clang/StaticAnalyzer/Core/Checker.h" |
| 29 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
| 30 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" |
| 31 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
| 32 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
| 33 | #include "llvm/Support/FormatVariadic.h" |
| 34 | |
| 35 | using namespace clang; |
| 36 | using namespace ento; |
| 37 | using llvm::formatv; |
| 38 | |
| 39 | /// Either nullptr or a function under symbolic execution; a non-null value |
| 40 | /// means that the analyzer didn't see any branching points from the beginning |
| 41 | /// of that function until the current location. |
| 42 | REGISTER_TRAIT_WITH_PROGRAMSTATE(HasUnconditionalPath, const FunctionDecl *) |
| 43 | |
| 44 | namespace { |
| 45 | class UnconditionalVAArgChecker |
| 46 | : public Checker<check::BeginFunction, check::EndFunction, |
| 47 | check::BranchCondition, check::PreStmt<VAArgExpr>> { |
| 48 | const BugType BT{this, "Unconditional use of va_arg()" , |
| 49 | categories::MemoryError}; |
| 50 | |
| 51 | static const FunctionDecl *getCurrentFunction(CheckerContext &C); |
| 52 | |
| 53 | public: |
| 54 | void checkBeginFunction(CheckerContext &C) const; |
| 55 | void checkEndFunction(const ReturnStmt *, CheckerContext &C) const; |
| 56 | void checkBranchCondition(const Stmt *Condition, CheckerContext &C) const; |
| 57 | void checkPreStmt(const VAArgExpr *VAA, CheckerContext &C) const; |
| 58 | }; |
| 59 | } // end anonymous namespace |
| 60 | |
| 61 | const FunctionDecl * |
| 62 | UnconditionalVAArgChecker::getCurrentFunction(CheckerContext &C) { |
| 63 | const Decl *FD = C.getLocationContext()->getDecl(); |
| 64 | return dyn_cast_if_present<FunctionDecl>(Val: FD); |
| 65 | } |
| 66 | |
| 67 | void UnconditionalVAArgChecker::checkBeginFunction(CheckerContext &C) const { |
| 68 | // We only look for unconditional va_arg() use in variadic functions. |
| 69 | // Functions that take a va_list argument are just parts of the argument |
| 70 | // handling, it is more natural for them to have preconditions. |
| 71 | const FunctionDecl *FD = getCurrentFunction(C); |
| 72 | if (FD && FD->isVariadic()) { |
| 73 | // If a variadic function is inlined in the body of another variadic |
| 74 | // function, this overwrites the path tracking for the outer function. As |
| 75 | // this situation is fairly rare and it is very unlikely that the "big" |
| 76 | // outer function still has an unconditional path, there is no need to |
| 77 | // write more complex logic that handles this. |
| 78 | // NOTE: Despite this, the checker can sometimes still report the |
| 79 | // unconditional va_arg() use in the outer function (probably because there |
| 80 | // is an alternative execution path that doesn't enter the inner call). |
| 81 | C.addTransition(State: C.getState()->set<HasUnconditionalPath>(FD)); |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | void UnconditionalVAArgChecker::checkEndFunction(const ReturnStmt *, |
| 86 | CheckerContext &C) const { |
| 87 | // This callback is just for the sake of cleanliness, to remove data from the |
| 88 | // State after it becomes irrelevant. This checker would function perfectly |
| 89 | // correctly without this callback, and the impact on other checkers is also |
| 90 | // extremely limited (presence of extra metadata might prevent the |
| 91 | // unification of execution paths in some very rare situations). |
| 92 | ProgramStateRef State = C.getState(); |
| 93 | const FunctionDecl *FD = getCurrentFunction(C); |
| 94 | if (FD && FD == State->get<HasUnconditionalPath>()) { |
| 95 | State = State->set<HasUnconditionalPath>(nullptr); |
| 96 | C.addTransition(State); |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | void UnconditionalVAArgChecker::checkBranchCondition(const Stmt *Condition, |
| 101 | CheckerContext &C) const { |
| 102 | // After evaluating a branch condition, the analyzer (which examines |
| 103 | // execution paths individually) won't be able to find a va_arg() expression |
| 104 | // that is _unconditionally_ reached -- so this callback resets the state |
| 105 | // trait HasUnconditionalPath. |
| 106 | // NOTES: |
| 107 | // 1. This is the right thing to do even if the analyzer sees that _in the |
| 108 | // current state_ the execution can only continue in one direction. For |
| 109 | // example, if the variadic function isn't the entrypont, then the parameters |
| 110 | // recieved from the caller may guarantee that va_arg() is used -- but this |
| 111 | // does not mean that the function _unconditionally_ uses va_arg(). |
| 112 | // 2. After other kinds of state splits (e.g. EagerlyAssueme, callbacks of |
| 113 | // StdLibraryFunctions separating different cases for the behavior of a |
| 114 | // library function etc.) the different execution paths will follow the same |
| 115 | // code (until they hit a branch condition), so it is reasonable (although |
| 116 | // not always correct) to assume that a va_arg() reached after those state |
| 117 | // splits is still _unconditionally_ reached if there were no branching |
| 118 | // statements. |
| 119 | // 3. This checker activates _after_ the evaluation of the branch condition, |
| 120 | // so va_arg() in the branch condition can be unconditionally reached. |
| 121 | C.addTransition(State: C.getState()->set<HasUnconditionalPath>(nullptr)); |
| 122 | } |
| 123 | |
| 124 | void UnconditionalVAArgChecker::checkPreStmt(const VAArgExpr *VAA, |
| 125 | CheckerContext &C) const { |
| 126 | ProgramStateRef State = C.getState(); |
| 127 | const FunctionDecl *PathFrom = State->get<HasUnconditionalPath>(); |
| 128 | if (!PathFrom) |
| 129 | return; |
| 130 | |
| 131 | // Reset this trait in the state to ensure that multiple consecutive |
| 132 | // va_arg() calls don't produce repeated warnings. |
| 133 | C.addTransition(State: State->set<HasUnconditionalPath>(nullptr)); |
| 134 | |
| 135 | IdentifierInfo *II = PathFrom->getIdentifier(); |
| 136 | if (!II) |
| 137 | return; |
| 138 | StringRef FN = II->getName(); |
| 139 | |
| 140 | std::string FullMsg = formatv( |
| 141 | Fmt: "Calls to '{0}' always reach this va_arg() expression, so calling " |
| 142 | "'{0}' with no variadic arguments would be undefined behavior" , |
| 143 | Vals&: FN); |
| 144 | SourceRange SR = VAA->getSourceRange(); |
| 145 | PathDiagnosticLocation PDL(SR.getBegin(), |
| 146 | C.getASTContext().getSourceManager()); |
| 147 | // We create a non-path-sensitive report because the path wouldn't contain |
| 148 | // any useful information: the path leading to the variadic function is |
| 149 | // actively ignored by the checker and the unconditional path from the |
| 150 | // start of the variadic function is trivial. |
| 151 | auto R = |
| 152 | std::make_unique<BasicBugReport>(args: BT, args: BT.getDescription(), args&: FullMsg, args&: PDL); |
| 153 | |
| 154 | if (getCurrentFunction(C) != PathFrom) { |
| 155 | // Highlight the definition of the variadic function in the rare case |
| 156 | // when the reached va_arg() expression is in another function. |
| 157 | SourceRange DefSR = PathFrom->getSourceRange(); |
| 158 | PathDiagnosticLocation DefPDL(DefSR.getBegin(), |
| 159 | C.getASTContext().getSourceManager()); |
| 160 | std::string NoteMsg = |
| 161 | formatv(Fmt: "Variadic function '{0}' is defined here" , Vals&: FN); |
| 162 | R->addNote(Msg: NoteMsg, Pos: DefPDL, Ranges: DefSR); |
| 163 | } |
| 164 | R->addRange(R: SR); |
| 165 | R->setDeclWithIssue(PathFrom); |
| 166 | C.emitReport(R: std::move(R)); |
| 167 | } |
| 168 | |
| 169 | void ento::registerUnconditionalVAArgChecker(CheckerManager &Mgr) { |
| 170 | Mgr.registerChecker<UnconditionalVAArgChecker>(); |
| 171 | } |
| 172 | |
| 173 | bool ento::shouldRegisterUnconditionalVAArgChecker(const CheckerManager &) { |
| 174 | return true; |
| 175 | } |
| 176 | |