565 lines · cpp
1//==- ExprInspectionChecker.cpp - Used for regression tests ------*- 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-exception6//7//===----------------------------------------------------------------------===//8 9#include "clang/Analysis/IssueHash.h"10#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"11#include "clang/StaticAnalyzer/Checkers/SValExplainer.h"12#include "clang/StaticAnalyzer/Checkers/Taint.h"13#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"14#include "clang/StaticAnalyzer/Core/Checker.h"15#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"16#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"17#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"18#include "llvm/ADT/StringSwitch.h"19#include "llvm/Support/ScopedPrinter.h"20#include <optional>21 22using namespace clang;23using namespace ento;24 25namespace {26class ExprInspectionChecker27 : public Checker<eval::Call, check::DeadSymbols, check::EndAnalysis> {28 const BugType BT{this, "Checking analyzer assumptions", "debug"};29 30 // These stats are per-analysis, not per-branch, hence they shouldn't31 // stay inside the program state.32 struct ReachedStat {33 ExplodedNode *ExampleNode;34 unsigned NumTimesReached;35 };36 mutable llvm::DenseMap<const CallExpr *, ReachedStat> ReachedStats;37 38 void analyzerEval(const CallExpr *CE, CheckerContext &C) const;39 void analyzerCheckInlined(const CallExpr *CE, CheckerContext &C) const;40 void analyzerWarnIfReached(const CallExpr *CE, CheckerContext &C) const;41 void analyzerNumTimesReached(const CallExpr *CE, CheckerContext &C) const;42 void analyzerCrash(const CallExpr *CE, CheckerContext &C) const;43 void analyzerWarnOnDeadSymbol(const CallExpr *CE, CheckerContext &C) const;44 void analyzerValue(const CallExpr *CE, CheckerContext &C) const;45 void analyzerDumpSValType(const CallExpr *CE, CheckerContext &C) const;46 void analyzerDump(const CallExpr *CE, CheckerContext &C) const;47 void analyzerExplain(const CallExpr *CE, CheckerContext &C) const;48 void analyzerPrintState(const CallExpr *CE, CheckerContext &C) const;49 void analyzerGetExtent(const CallExpr *CE, CheckerContext &C) const;50 void analyzerDumpExtent(const CallExpr *CE, CheckerContext &C) const;51 void analyzerDumpElementCount(const CallExpr *CE, CheckerContext &C) const;52 void analyzerHashDump(const CallExpr *CE, CheckerContext &C) const;53 void analyzerDenote(const CallExpr *CE, CheckerContext &C) const;54 void analyzerExpress(const CallExpr *CE, CheckerContext &C) const;55 void analyzerIsTainted(const CallExpr *CE, CheckerContext &C) const;56 57 typedef void (ExprInspectionChecker::*FnCheck)(const CallExpr *,58 CheckerContext &C) const;59 60 // Optional parameter `ExprVal` for expression value to be marked interesting.61 ExplodedNode *reportBug(llvm::StringRef Msg, CheckerContext &C,62 std::optional<SVal> ExprVal = std::nullopt) const;63 ExplodedNode *reportBug(llvm::StringRef Msg, BugReporter &BR, ExplodedNode *N,64 std::optional<SVal> ExprVal = std::nullopt) const;65 template <typename T> void printAndReport(CheckerContext &C, T What) const;66 67 const Expr *getArgExpr(const CallExpr *CE, CheckerContext &C) const;68 const MemRegion *getArgRegion(const CallExpr *CE, CheckerContext &C) const;69 70public:71 bool evalCall(const CallEvent &Call, CheckerContext &C) const;72 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;73 void checkEndAnalysis(ExplodedGraph &G, BugReporter &BR,74 ExprEngine &Eng) const;75};76} // namespace77 78REGISTER_SET_WITH_PROGRAMSTATE(MarkedSymbols, SymbolRef)79REGISTER_MAP_WITH_PROGRAMSTATE(DenotedSymbols, SymbolRef, const StringLiteral *)80 81bool ExprInspectionChecker::evalCall(const CallEvent &Call,82 CheckerContext &C) const {83 const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());84 if (!CE)85 return false;86 87 // These checks should have no effect on the surrounding environment88 // (globals should not be invalidated, etc), hence the use of evalCall.89 FnCheck Handler =90 llvm::StringSwitch<FnCheck>(C.getCalleeName(CE))91 .Case("clang_analyzer_eval", &ExprInspectionChecker::analyzerEval)92 .Case("clang_analyzer_checkInlined",93 &ExprInspectionChecker::analyzerCheckInlined)94 .Case("clang_analyzer_crash", &ExprInspectionChecker::analyzerCrash)95 .Case("clang_analyzer_warnIfReached",96 &ExprInspectionChecker::analyzerWarnIfReached)97 .Case("clang_analyzer_warnOnDeadSymbol",98 &ExprInspectionChecker::analyzerWarnOnDeadSymbol)99 .StartsWith("clang_analyzer_explain",100 &ExprInspectionChecker::analyzerExplain)101 .Case("clang_analyzer_dumpExtent",102 &ExprInspectionChecker::analyzerDumpExtent)103 .Case("clang_analyzer_dumpElementCount",104 &ExprInspectionChecker::analyzerDumpElementCount)105 .Case("clang_analyzer_value", &ExprInspectionChecker::analyzerValue)106 .StartsWith("clang_analyzer_dumpSvalType",107 &ExprInspectionChecker::analyzerDumpSValType)108 .StartsWith("clang_analyzer_dump",109 &ExprInspectionChecker::analyzerDump)110 .Case("clang_analyzer_getExtent",111 &ExprInspectionChecker::analyzerGetExtent)112 .Case("clang_analyzer_printState",113 &ExprInspectionChecker::analyzerPrintState)114 .Case("clang_analyzer_numTimesReached",115 &ExprInspectionChecker::analyzerNumTimesReached)116 .Case("clang_analyzer_hashDump",117 &ExprInspectionChecker::analyzerHashDump)118 .Case("clang_analyzer_denote", &ExprInspectionChecker::analyzerDenote)119 .Case("clang_analyzer_express", // This also marks the argument as120 // interesting.121 &ExprInspectionChecker::analyzerExpress)122 .StartsWith("clang_analyzer_isTainted",123 &ExprInspectionChecker::analyzerIsTainted)124 .Default(nullptr);125 126 if (!Handler)127 return false;128 129 (this->*Handler)(CE, C);130 return true;131}132 133static const char *getArgumentValueString(const CallExpr *CE,134 CheckerContext &C) {135 if (CE->getNumArgs() == 0)136 return "Missing assertion argument";137 138 ExplodedNode *N = C.getPredecessor();139 const LocationContext *LC = N->getLocationContext();140 ProgramStateRef State = N->getState();141 142 const Expr *Assertion = CE->getArg(0);143 SVal AssertionVal = State->getSVal(Assertion, LC);144 145 if (AssertionVal.isUndef())146 return "UNDEFINED";147 148 ProgramStateRef StTrue, StFalse;149 std::tie(StTrue, StFalse) =150 State->assume(AssertionVal.castAs<DefinedOrUnknownSVal>());151 152 if (StTrue) {153 if (StFalse)154 return "UNKNOWN";155 else156 return "TRUE";157 } else {158 if (StFalse)159 return "FALSE";160 else161 llvm_unreachable("Invalid constraint; neither true or false.");162 }163}164 165ExplodedNode *166ExprInspectionChecker::reportBug(llvm::StringRef Msg, CheckerContext &C,167 std::optional<SVal> ExprVal) const {168 ExplodedNode *N = C.generateNonFatalErrorNode();169 reportBug(Msg, C.getBugReporter(), N, ExprVal);170 return N;171}172 173ExplodedNode *174ExprInspectionChecker::reportBug(llvm::StringRef Msg, BugReporter &BR,175 ExplodedNode *N,176 std::optional<SVal> ExprVal) const {177 if (!N)178 return nullptr;179 auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);180 if (ExprVal) {181 R->markInteresting(*ExprVal);182 }183 BR.emitReport(std::move(R));184 return N;185}186 187const Expr *ExprInspectionChecker::getArgExpr(const CallExpr *CE,188 CheckerContext &C) const {189 if (CE->getNumArgs() == 0) {190 reportBug("Missing argument", C);191 return nullptr;192 }193 return CE->getArg(0);194}195 196const MemRegion *ExprInspectionChecker::getArgRegion(const CallExpr *CE,197 CheckerContext &C) const {198 const Expr *Arg = getArgExpr(CE, C);199 if (!Arg)200 return nullptr;201 202 const MemRegion *MR = C.getSVal(Arg).getAsRegion();203 if (!MR) {204 reportBug("Cannot obtain the region", C);205 return nullptr;206 }207 208 return MR;209}210 211void ExprInspectionChecker::analyzerEval(const CallExpr *CE,212 CheckerContext &C) const {213 const LocationContext *LC = C.getPredecessor()->getLocationContext();214 215 // A specific instantiation of an inlined function may have more constrained216 // values than can generally be assumed. Skip the check.217 if (LC->getStackFrame()->getParent() != nullptr)218 return;219 220 reportBug(getArgumentValueString(CE, C), C);221}222 223void ExprInspectionChecker::analyzerWarnIfReached(const CallExpr *CE,224 CheckerContext &C) const {225 reportBug("REACHABLE", C);226}227 228void ExprInspectionChecker::analyzerNumTimesReached(const CallExpr *CE,229 CheckerContext &C) const {230 ReachedStat &Stat = ReachedStats[CE];231 ++Stat.NumTimesReached;232 if (!Stat.ExampleNode) {233 // Later, in checkEndAnalysis, we'd throw a report against it.234 Stat.ExampleNode = C.generateNonFatalErrorNode();235 }236}237 238void ExprInspectionChecker::analyzerCheckInlined(const CallExpr *CE,239 CheckerContext &C) const {240 const LocationContext *LC = C.getPredecessor()->getLocationContext();241 242 // An inlined function could conceivably also be analyzed as a top-level243 // function. We ignore this case and only emit a message (TRUE or FALSE)244 // when we are analyzing it as an inlined function. This means that245 // clang_analyzer_checkInlined(true) should always print TRUE, but246 // clang_analyzer_checkInlined(false) should never actually print anything.247 if (LC->getStackFrame()->getParent() == nullptr)248 return;249 250 reportBug(getArgumentValueString(CE, C), C);251}252 253void ExprInspectionChecker::analyzerExplain(const CallExpr *CE,254 CheckerContext &C) const {255 const Expr *Arg = getArgExpr(CE, C);256 if (!Arg)257 return;258 259 SVal V = C.getSVal(Arg);260 SValExplainer Ex(C.getASTContext(), C.getState());261 reportBug(Ex.Visit(V), C);262}263 264static void printHelper(llvm::raw_svector_ostream &Out, CheckerContext &C,265 const llvm::APSInt &I) {266 Out << I.getBitWidth() << (I.isUnsigned() ? "u:" : "s:");267 Out << I;268}269 270static void printHelper(llvm::raw_svector_ostream &Out, CheckerContext &C,271 SymbolRef Sym) {272 C.getConstraintManager().printValue(Out, C.getState(), Sym);273}274 275static void printHelper(llvm::raw_svector_ostream &Out, CheckerContext &C,276 SVal V) {277 Out << V;278}279 280template <typename T>281void ExprInspectionChecker::printAndReport(CheckerContext &C, T What) const {282 llvm::SmallString<64> Str;283 llvm::raw_svector_ostream OS(Str);284 printHelper(OS, C, What);285 reportBug(OS.str(), C);286}287 288void ExprInspectionChecker::analyzerValue(const CallExpr *CE,289 CheckerContext &C) const {290 const Expr *Arg = getArgExpr(CE, C);291 if (!Arg)292 return;293 294 SVal V = C.getSVal(Arg);295 if (const SymbolRef Sym = V.getAsSymbol())296 printAndReport(C, Sym);297 else if (const llvm::APSInt *I = V.getAsInteger())298 printAndReport(C, *I);299 else300 reportBug("n/a", C);301}302 303void ExprInspectionChecker::analyzerDumpSValType(const CallExpr *CE,304 CheckerContext &C) const {305 const Expr *Arg = getArgExpr(CE, C);306 if (!Arg)307 return;308 309 QualType Ty = C.getSVal(Arg).getType(C.getASTContext());310 reportBug(Ty.getAsString(), C);311}312 313void ExprInspectionChecker::analyzerDump(const CallExpr *CE,314 CheckerContext &C) const {315 const Expr *Arg = getArgExpr(CE, C);316 if (!Arg)317 return;318 319 SVal V = C.getSVal(Arg);320 printAndReport(C, V);321}322 323void ExprInspectionChecker::analyzerGetExtent(const CallExpr *CE,324 CheckerContext &C) const {325 const Expr *Arg = getArgExpr(CE, C);326 if (!Arg)327 return;328 329 ProgramStateRef State = C.getState();330 SVal Size = getDynamicExtentWithOffset(State, C.getSVal(Arg));331 332 State = State->BindExpr(CE, C.getLocationContext(), Size);333 C.addTransition(State);334}335 336void ExprInspectionChecker::analyzerDumpExtent(const CallExpr *CE,337 CheckerContext &C) const {338 const Expr *Arg = getArgExpr(CE, C);339 if (!Arg)340 return;341 342 ProgramStateRef State = C.getState();343 SVal Size = getDynamicExtentWithOffset(State, C.getSVal(Arg));344 printAndReport(C, Size);345}346 347void ExprInspectionChecker::analyzerDumpElementCount(const CallExpr *CE,348 CheckerContext &C) const {349 const MemRegion *MR = getArgRegion(CE, C);350 if (!MR)351 return;352 353 QualType ElementTy;354 if (const auto *TVR = MR->getAs<TypedValueRegion>()) {355 ElementTy = TVR->getValueType();356 } else {357 ElementTy = MR->castAs<SymbolicRegion>()->getPointeeStaticType();358 }359 360 assert(!ElementTy->isPointerType());361 362 DefinedOrUnknownSVal ElementCount = getDynamicElementCountWithOffset(363 C.getState(), C.getSVal(getArgExpr(CE, C)), ElementTy);364 printAndReport(C, ElementCount);365}366 367void ExprInspectionChecker::analyzerPrintState(const CallExpr *CE,368 CheckerContext &C) const {369 C.getState()->dump();370}371 372void ExprInspectionChecker::analyzerWarnOnDeadSymbol(const CallExpr *CE,373 CheckerContext &C) const {374 const Expr *Arg = getArgExpr(CE, C);375 if (!Arg)376 return;377 378 SVal Val = C.getSVal(Arg);379 SymbolRef Sym = Val.getAsSymbol();380 if (!Sym)381 return;382 383 ProgramStateRef State = C.getState();384 State = State->add<MarkedSymbols>(Sym);385 C.addTransition(State);386}387 388void ExprInspectionChecker::checkDeadSymbols(SymbolReaper &SymReaper,389 CheckerContext &C) const {390 ProgramStateRef State = C.getState();391 const MarkedSymbolsTy &Syms = State->get<MarkedSymbols>();392 ExplodedNode *N = C.getPredecessor();393 for (SymbolRef Sym : Syms) {394 if (!SymReaper.isDead(Sym))395 continue;396 397 // The non-fatal error node should be the same for all reports.398 if (ExplodedNode *BugNode = reportBug("SYMBOL DEAD", C))399 N = BugNode;400 State = State->remove<MarkedSymbols>(Sym);401 }402 403 for (auto I : State->get<DenotedSymbols>()) {404 SymbolRef Sym = I.first;405 if (!SymReaper.isLive(Sym))406 State = State->remove<DenotedSymbols>(Sym);407 }408 409 C.addTransition(State, N);410}411 412void ExprInspectionChecker::checkEndAnalysis(ExplodedGraph &G, BugReporter &BR,413 ExprEngine &Eng) const {414 for (auto Item : ReachedStats) {415 unsigned NumTimesReached = Item.second.NumTimesReached;416 ExplodedNode *N = Item.second.ExampleNode;417 418 reportBug(llvm::to_string(NumTimesReached), BR, N);419 }420 ReachedStats.clear();421}422 423void ExprInspectionChecker::analyzerCrash(const CallExpr *CE,424 CheckerContext &C) const {425 LLVM_BUILTIN_TRAP;426}427 428void ExprInspectionChecker::analyzerHashDump(const CallExpr *CE,429 CheckerContext &C) const {430 const LangOptions &Opts = C.getLangOpts();431 const SourceManager &SM = C.getSourceManager();432 FullSourceLoc FL(CE->getArg(0)->getBeginLoc(), SM);433 std::string HashContent = getIssueString(434 FL, getName(), "Category", C.getLocationContext()->getDecl(), Opts);435 436 reportBug(HashContent, C);437}438 439void ExprInspectionChecker::analyzerDenote(const CallExpr *CE,440 CheckerContext &C) const {441 if (CE->getNumArgs() < 2) {442 reportBug("clang_analyzer_denote() requires a symbol and a string literal",443 C);444 return;445 }446 447 SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol();448 if (!Sym) {449 reportBug("Not a symbol", C);450 return;451 }452 453 const auto *E = dyn_cast<StringLiteral>(CE->getArg(1)->IgnoreParenCasts());454 if (!E) {455 reportBug("Not a string literal", C);456 return;457 }458 459 ProgramStateRef State = C.getState();460 461 C.addTransition(C.getState()->set<DenotedSymbols>(Sym, E));462}463 464namespace {465class SymbolExpressor466 : public SymExprVisitor<SymbolExpressor, std::optional<std::string>> {467 ProgramStateRef State;468 469public:470 SymbolExpressor(ProgramStateRef State) : State(State) {}471 472 std::optional<std::string> lookup(const SymExpr *S) {473 if (const StringLiteral *const *SLPtr = State->get<DenotedSymbols>(S)) {474 const StringLiteral *SL = *SLPtr;475 return std::string(SL->getBytes());476 }477 return std::nullopt;478 }479 480 std::optional<std::string> VisitSymExpr(const SymExpr *S) {481 return lookup(S);482 }483 484 std::optional<std::string> VisitSymIntExpr(const SymIntExpr *S) {485 if (std::optional<std::string> Str = lookup(S))486 return Str;487 if (std::optional<std::string> Str = Visit(S->getLHS()))488 return (*Str + " " + BinaryOperator::getOpcodeStr(S->getOpcode()) + " " +489 std::to_string(S->getRHS()->getLimitedValue()) +490 (S->getRHS()->isUnsigned() ? "U" : ""))491 .str();492 return std::nullopt;493 }494 495 std::optional<std::string> VisitSymSymExpr(const SymSymExpr *S) {496 if (std::optional<std::string> Str = lookup(S))497 return Str;498 if (std::optional<std::string> Str1 = Visit(S->getLHS()))499 if (std::optional<std::string> Str2 = Visit(S->getRHS()))500 return (*Str1 + " " + BinaryOperator::getOpcodeStr(S->getOpcode()) +501 " " + *Str2)502 .str();503 return std::nullopt;504 }505 506 std::optional<std::string> VisitUnarySymExpr(const UnarySymExpr *S) {507 if (std::optional<std::string> Str = lookup(S))508 return Str;509 if (std::optional<std::string> Str = Visit(S->getOperand()))510 return (UnaryOperator::getOpcodeStr(S->getOpcode()) + *Str).str();511 return std::nullopt;512 }513 514 std::optional<std::string> VisitSymbolCast(const SymbolCast *S) {515 if (std::optional<std::string> Str = lookup(S))516 return Str;517 if (std::optional<std::string> Str = Visit(S->getOperand()))518 return (Twine("(") + S->getType().getAsString() + ")" + *Str).str();519 return std::nullopt;520 }521};522} // namespace523 524void ExprInspectionChecker::analyzerExpress(const CallExpr *CE,525 CheckerContext &C) const {526 const Expr *Arg = getArgExpr(CE, C);527 if (!Arg)528 return;529 530 SVal ArgVal = C.getSVal(CE->getArg(0));531 SymbolRef Sym = ArgVal.getAsSymbol();532 if (!Sym) {533 reportBug("Not a symbol", C, ArgVal);534 return;535 }536 537 SymbolExpressor V(C.getState());538 auto Str = V.Visit(Sym);539 if (!Str) {540 reportBug("Unable to express", C, ArgVal);541 return;542 }543 544 reportBug(*Str, C, ArgVal);545}546 547void ExprInspectionChecker::analyzerIsTainted(const CallExpr *CE,548 CheckerContext &C) const {549 if (CE->getNumArgs() != 1) {550 reportBug("clang_analyzer_isTainted() requires exactly one argument", C);551 return;552 }553 const bool IsTainted =554 taint::isTainted(C.getState(), CE->getArg(0), C.getLocationContext());555 reportBug(IsTainted ? "YES" : "NO", C);556}557 558void ento::registerExprInspectionChecker(CheckerManager &Mgr) {559 Mgr.registerChecker<ExprInspectionChecker>();560}561 562bool ento::shouldRegisterExprInspectionChecker(const CheckerManager &mgr) {563 return true;564}565