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-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
10#include "clang/StaticAnalyzer/Checkers/SValExplainer.h"
11#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
12#include "clang/StaticAnalyzer/Core/Checker.h"
13#include "clang/StaticAnalyzer/Core/IssueHash.h"
14#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
15#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
16#include "llvm/ADT/StringSwitch.h"
17#include "llvm/Support/ScopedPrinter.h"
18
19using namespace clang;
20using namespace ento;
21
22namespace {
23class ExprInspectionChecker : public Checker<eval::Call, check::DeadSymbols,
24                                             check::EndAnalysis> {
25  mutable std::unique_ptr<BugType> BT;
26
27  // These stats are per-analysis, not per-branch, hence they shouldn't
28  // stay inside the program state.
29  struct ReachedStat {
30    ExplodedNode *ExampleNode;
31    unsigned NumTimesReached;
32  };
33  mutable llvm::DenseMap<const CallExpr *, ReachedStat> ReachedStats;
34
35  void analyzerEval(const CallExpr *CE, CheckerContext &C) const;
36  void analyzerCheckInlined(const CallExpr *CE, CheckerContext &C) const;
37  void analyzerWarnIfReached(const CallExpr *CE, CheckerContext &C) const;
38  void analyzerNumTimesReached(const CallExpr *CE, CheckerContext &C) const;
39  void analyzerCrash(const CallExpr *CE, CheckerContext &C) const;
40  void analyzerWarnOnDeadSymbol(const CallExpr *CE, CheckerContext &C) const;
41  void analyzerDump(const CallExpr *CE, CheckerContext &C) const;
42  void analyzerExplain(const CallExpr *CE, CheckerContext &C) const;
43  void analyzerPrintState(const CallExpr *CE, CheckerContext &C) const;
44  void analyzerGetExtent(const CallExpr *CE, CheckerContext &C) const;
45  void analyzerHashDump(const CallExpr *CE, CheckerContext &C) const;
46  void analyzerDenote(const CallExpr *CE, CheckerContext &C) const;
47  void analyzerExpress(const CallExpr *CE, CheckerContext &C) const;
48
49  typedef void (ExprInspectionChecker::*FnCheck)(const CallExpr *,
50                                                 CheckerContext &C) const;
51
52  ExplodedNode *reportBug(llvm::StringRef Msg, CheckerContext &C) const;
53  ExplodedNode *reportBug(llvm::StringRef Msg, BugReporter &BR,
54                          ExplodedNode *N) const;
55
56public:
57  bool evalCall(const CallEvent &Call, CheckerContext &C) const;
58  void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
59  void checkEndAnalysis(ExplodedGraph &G, BugReporter &BR,
60                        ExprEngine &Eng) const;
61};
62}
63
64REGISTER_SET_WITH_PROGRAMSTATE(MarkedSymbols, SymbolRef)
65REGISTER_MAP_WITH_PROGRAMSTATE(DenotedSymbols, SymbolRef, const StringLiteral *)
66
67bool ExprInspectionChecker::evalCall(const CallEvent &Call,
68                                     CheckerContext &C) const {
69  const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
70  if (!CE)
71    return false;
72
73  // These checks should have no effect on the surrounding environment
74  // (globals should not be invalidated, etc), hence the use of evalCall.
75  FnCheck Handler = llvm::StringSwitch<FnCheck>(C.getCalleeName(CE))
76    .Case("clang_analyzer_eval", &ExprInspectionChecker::analyzerEval)
77    .Case("clang_analyzer_checkInlined",
78          &ExprInspectionChecker::analyzerCheckInlined)
79    .Case("clang_analyzer_crash", &ExprInspectionChecker::analyzerCrash)
80    .Case("clang_analyzer_warnIfReached",
81          &ExprInspectionChecker::analyzerWarnIfReached)
82    .Case("clang_analyzer_warnOnDeadSymbol",
83          &ExprInspectionChecker::analyzerWarnOnDeadSymbol)
84    .StartsWith("clang_analyzer_explain", &ExprInspectionChecker::analyzerExplain)
85    .StartsWith("clang_analyzer_dump", &ExprInspectionChecker::analyzerDump)
86    .Case("clang_analyzer_getExtent", &ExprInspectionChecker::analyzerGetExtent)
87    .Case("clang_analyzer_printState",
88          &ExprInspectionChecker::analyzerPrintState)
89    .Case("clang_analyzer_numTimesReached",
90          &ExprInspectionChecker::analyzerNumTimesReached)
91    .Case("clang_analyzer_hashDump", &ExprInspectionChecker::analyzerHashDump)
92    .Case("clang_analyzer_denote", &ExprInspectionChecker::analyzerDenote)
93    .Case("clang_analyzer_express", &ExprInspectionChecker::analyzerExpress)
94    .Default(nullptr);
95
96  if (!Handler)
97    return false;
98
99  (this->*Handler)(CE, C);
100  return true;
101}
102
103static const char *getArgumentValueString(const CallExpr *CE,
104                                          CheckerContext &C) {
105  if (CE->getNumArgs() == 0)
106    return "Missing assertion argument";
107
108  ExplodedNode *N = C.getPredecessor();
109  const LocationContext *LC = N->getLocationContext();
110  ProgramStateRef State = N->getState();
111
112  const Expr *Assertion = CE->getArg(0);
113  SVal AssertionVal = State->getSVal(Assertion, LC);
114
115  if (AssertionVal.isUndef())
116    return "UNDEFINED";
117
118  ProgramStateRef StTrue, StFalse;
119  std::tie(StTrue, StFalse) =
120    State->assume(AssertionVal.castAs<DefinedOrUnknownSVal>());
121
122  if (StTrue) {
123    if (StFalse)
124      return "UNKNOWN";
125    else
126      return "TRUE";
127  } else {
128    if (StFalse)
129      return "FALSE";
130    else
131      llvm_unreachable("Invalid constraint; neither true or false.");
132  }
133}
134
135ExplodedNode *ExprInspectionChecker::reportBug(llvm::StringRef Msg,
136                                               CheckerContext &C) const {
137  ExplodedNode *N = C.generateNonFatalErrorNode();
138  reportBug(Msg, C.getBugReporter(), N);
139  return N;
140}
141
142ExplodedNode *ExprInspectionChecker::reportBug(llvm::StringRef Msg,
143                                               BugReporter &BR,
144                                               ExplodedNode *N) const {
145  if (!N)
146    return nullptr;
147
148  if (!BT)
149    BT.reset(new BugType(this, "Checking analyzer assumptions", "debug"));
150
151  BR.emitReport(std::make_unique<PathSensitiveBugReport>(*BT, Msg, N));
152  return N;
153}
154
155void ExprInspectionChecker::analyzerEval(const CallExpr *CE,
156                                         CheckerContext &C) const {
157  const LocationContext *LC = C.getPredecessor()->getLocationContext();
158
159  // A specific instantiation of an inlined function may have more constrained
160  // values than can generally be assumed. Skip the check.
161  if (LC->getStackFrame()->getParent() != nullptr)
162    return;
163
164  reportBug(getArgumentValueString(CE, C), C);
165}
166
167void ExprInspectionChecker::analyzerWarnIfReached(const CallExpr *CE,
168                                                  CheckerContext &C) const {
169  reportBug("REACHABLE", C);
170}
171
172void ExprInspectionChecker::analyzerNumTimesReached(const CallExpr *CE,
173                                                    CheckerContext &C) const {
174  ++ReachedStats[CE].NumTimesReached;
175  if (!ReachedStats[CE].ExampleNode) {
176    // Later, in checkEndAnalysis, we'd throw a report against it.
177    ReachedStats[CE].ExampleNode = C.generateNonFatalErrorNode();
178  }
179}
180
181void ExprInspectionChecker::analyzerCheckInlined(const CallExpr *CE,
182                                                 CheckerContext &C) const {
183  const LocationContext *LC = C.getPredecessor()->getLocationContext();
184
185  // An inlined function could conceivably also be analyzed as a top-level
186  // function. We ignore this case and only emit a message (TRUE or FALSE)
187  // when we are analyzing it as an inlined function. This means that
188  // clang_analyzer_checkInlined(true) should always print TRUE, but
189  // clang_analyzer_checkInlined(false) should never actually print anything.
190  if (LC->getStackFrame()->getParent() == nullptr)
191    return;
192
193  reportBug(getArgumentValueString(CE, C), C);
194}
195
196void ExprInspectionChecker::analyzerExplain(const CallExpr *CE,
197                                            CheckerContext &C) const {
198  if (CE->getNumArgs() == 0) {
199    reportBug("Missing argument for explaining", C);
200    return;
201  }
202
203  SVal V = C.getSVal(CE->getArg(0));
204  SValExplainer Ex(C.getASTContext());
205  reportBug(Ex.Visit(V), C);
206}
207
208void ExprInspectionChecker::analyzerDump(const CallExpr *CE,
209                                         CheckerContext &C) const {
210  if (CE->getNumArgs() == 0) {
211    reportBug("Missing argument for dumping", C);
212    return;
213  }
214
215  SVal V = C.getSVal(CE->getArg(0));
216
217  llvm::SmallString<32> Str;
218  llvm::raw_svector_ostream OS(Str);
219  V.dumpToStream(OS);
220  reportBug(OS.str(), C);
221}
222
223void ExprInspectionChecker::analyzerGetExtent(const CallExpr *CE,
224                                              CheckerContext &C) const {
225  if (CE->getNumArgs() == 0) {
226    reportBug("Missing region for obtaining extent", C);
227    return;
228  }
229
230  auto MR = dyn_cast_or_null<SubRegion>(C.getSVal(CE->getArg(0)).getAsRegion());
231  if (!MR) {
232    reportBug("Obtaining extent of a non-region", C);
233    return;
234  }
235
236  ProgramStateRef State = C.getState();
237  State = State->BindExpr(CE, C.getLocationContext(),
238                          MR->getExtent(C.getSValBuilder()));
239  C.addTransition(State);
240}
241
242void ExprInspectionChecker::analyzerPrintState(const CallExpr *CE,
243                                               CheckerContext &C) const {
244  C.getState()->dump();
245}
246
247void ExprInspectionChecker::analyzerWarnOnDeadSymbol(const CallExpr *CE,
248                                                     CheckerContext &C) const {
249  if (CE->getNumArgs() == 0)
250    return;
251  SVal Val = C.getSVal(CE->getArg(0));
252  SymbolRef Sym = Val.getAsSymbol();
253  if (!Sym)
254    return;
255
256  ProgramStateRef State = C.getState();
257  State = State->add<MarkedSymbols>(Sym);
258  C.addTransition(State);
259}
260
261void ExprInspectionChecker::checkDeadSymbols(SymbolReaper &SymReaper,
262                                             CheckerContext &C) const {
263  ProgramStateRef State = C.getState();
264  const MarkedSymbolsTy &Syms = State->get<MarkedSymbols>();
265  ExplodedNode *N = C.getPredecessor();
266  for (auto I = Syms.begin(), E = Syms.end(); I != E; ++I) {
267    SymbolRef Sym = *I;
268    if (!SymReaper.isDead(Sym))
269      continue;
270
271    // The non-fatal error node should be the same for all reports.
272    if (ExplodedNode *BugNode = reportBug("SYMBOL DEAD", C))
273      N = BugNode;
274    State = State->remove<MarkedSymbols>(Sym);
275  }
276
277  for (auto I : State->get<DenotedSymbols>()) {
278    SymbolRef Sym = I.first;
279    if (!SymReaper.isLive(Sym))
280      State = State->remove<DenotedSymbols>(Sym);
281  }
282
283  C.addTransition(State, N);
284}
285
286void ExprInspectionChecker::checkEndAnalysis(ExplodedGraph &G, BugReporter &BR,
287                                             ExprEngine &Eng) const {
288  for (auto Item: ReachedStats) {
289    unsigned NumTimesReached = Item.second.NumTimesReached;
290    ExplodedNode *N = Item.second.ExampleNode;
291
292    reportBug(llvm::to_string(NumTimesReached), BR, N);
293  }
294  ReachedStats.clear();
295}
296
297void ExprInspectionChecker::analyzerCrash(const CallExpr *CE,
298                                          CheckerContext &C) const {
299  LLVM_BUILTIN_TRAP;
300}
301
302void ExprInspectionChecker::analyzerHashDump(const CallExpr *CE,
303                                             CheckerContext &C) const {
304  const LangOptions &Opts = C.getLangOpts();
305  const SourceManager &SM = C.getSourceManager();
306  FullSourceLoc FL(CE->getArg(0)->getBeginLoc(), SM);
307  std::string HashContent =
308      GetIssueString(SM, FL, getCheckerName().getName(), "Category",
309                     C.getLocationContext()->getDecl(), Opts);
310
311  reportBug(HashContent, C);
312}
313
314void ExprInspectionChecker::analyzerDenote(const CallExpr *CE,
315                                           CheckerContext &C) const {
316  if (CE->getNumArgs() < 2) {
317    reportBug("clang_analyzer_denote() requires a symbol and a string literal",
318              C);
319    return;
320  }
321
322  SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol();
323  if (!Sym) {
324    reportBug("Not a symbol", C);
325    return;
326  }
327
328  const auto *E = dyn_cast<StringLiteral>(CE->getArg(1)->IgnoreParenCasts());
329  if (!E) {
330    reportBug("Not a string literal", C);
331    return;
332  }
333
334  ProgramStateRef State = C.getState();
335
336  C.addTransition(C.getState()->set<DenotedSymbols>(Sym, E));
337}
338
339namespace {
340class SymbolExpressor
341    : public SymExprVisitor<SymbolExpressor, Optional<std::string>> {
342  ProgramStateRef State;
343
344public:
345  SymbolExpressor(ProgramStateRef State) : State(State) {}
346
347  Optional<std::string> lookup(const SymExpr *S) {
348    if (const StringLiteral *const *SLPtr = State->get<DenotedSymbols>(S)) {
349      const StringLiteral *SL = *SLPtr;
350      return std::string(SL->getBytes());
351    }
352    return None;
353  }
354
355  Optional<std::string> VisitSymExpr(const SymExpr *S) {
356    return lookup(S);
357  }
358
359  Optional<std::string> VisitSymIntExpr(const SymIntExpr *S) {
360    if (Optional<std::string> Str = lookup(S))
361      return Str;
362    if (Optional<std::string> Str = Visit(S->getLHS()))
363      return (*Str + " " + BinaryOperator::getOpcodeStr(S->getOpcode()) + " " +
364              std::to_string(S->getRHS().getLimitedValue()) +
365              (S->getRHS().isUnsigned() ? "U" : ""))
366          .str();
367    return None;
368  }
369
370  Optional<std::string> VisitSymSymExpr(const SymSymExpr *S) {
371    if (Optional<std::string> Str = lookup(S))
372      return Str;
373    if (Optional<std::string> Str1 = Visit(S->getLHS()))
374      if (Optional<std::string> Str2 = Visit(S->getRHS()))
375        return (*Str1 + " " + BinaryOperator::getOpcodeStr(S->getOpcode()) +
376                " " + *Str2).str();
377    return None;
378  }
379
380  Optional<std::string> VisitSymbolCast(const SymbolCast *S) {
381    if (Optional<std::string> Str = lookup(S))
382      return Str;
383    if (Optional<std::string> Str = Visit(S->getOperand()))
384      return (Twine("(") + S->getType().getAsString() + ")" + *Str).str();
385    return None;
386  }
387};
388} // namespace
389
390void ExprInspectionChecker::analyzerExpress(const CallExpr *CE,
391                                            CheckerContext &C) const {
392  if (CE->getNumArgs() == 0) {
393    reportBug("clang_analyzer_express() requires a symbol", C);
394    return;
395  }
396
397  SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol();
398  if (!Sym) {
399    reportBug("Not a symbol", C);
400    return;
401  }
402
403  SymbolExpressor V(C.getState());
404  auto Str = V.Visit(Sym);
405  if (!Str) {
406    reportBug("Unable to express", C);
407    return;
408  }
409
410  reportBug(*Str, C);
411}
412
413void ento::registerExprInspectionChecker(CheckerManager &Mgr) {
414  Mgr.registerChecker<ExprInspectionChecker>();
415}
416
417bool ento::shouldRegisterExprInspectionChecker(const LangOptions &LO) {
418  return true;
419}
420