1//===- ReturnValueChecker - Applies guaranteed return values ----*- 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 ReturnValueChecker, which checks for calls with guaranteed
10// boolean return value. It ensures the return value of each function call.
11//
12//===----------------------------------------------------------------------===//
13
14#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
15#include "clang/StaticAnalyzer/Core/Checker.h"
16#include "clang/StaticAnalyzer/Core/CheckerManager.h"
17#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
18#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
19#include "llvm/ADT/Optional.h"
20#include "llvm/ADT/SmallVector.h"
21
22using namespace clang;
23using namespace ento;
24
25namespace {
26class ReturnValueChecker : public Checker<check::PostCall, check::EndFunction> {
27public:
28  // It sets the predefined invariant ('CDM') if the current call not break it.
29  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
30
31  // It reports whether a predefined invariant ('CDM') is broken.
32  void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
33
34private:
35  // The pairs are in the following form: {{{class, call}}, return value}
36  const CallDescriptionMap<bool> CDM = {
37      // These are known in the LLVM project: 'Error()'
38      {{{"ARMAsmParser", "Error"}}, true},
39      {{{"HexagonAsmParser", "Error"}}, true},
40      {{{"LLLexer", "Error"}}, true},
41      {{{"LLParser", "Error"}}, true},
42      {{{"MCAsmParser", "Error"}}, true},
43      {{{"MCAsmParserExtension", "Error"}}, true},
44      {{{"TGParser", "Error"}}, true},
45      {{{"X86AsmParser", "Error"}}, true},
46      // 'TokError()'
47      {{{"LLParser", "TokError"}}, true},
48      {{{"MCAsmParser", "TokError"}}, true},
49      {{{"MCAsmParserExtension", "TokError"}}, true},
50      {{{"TGParser", "TokError"}}, true},
51      // 'error()'
52      {{{"MIParser", "error"}}, true},
53      {{{"WasmAsmParser", "error"}}, true},
54      {{{"WebAssemblyAsmParser", "error"}}, true},
55      // Other
56      {{{"AsmParser", "printError"}}, true}};
57};
58} // namespace
59
60static std::string getName(const CallEvent &Call) {
61  std::string Name = "";
62  if (const auto *MD = dyn_cast<CXXMethodDecl>(Call.getDecl()))
63    if (const CXXRecordDecl *RD = MD->getParent())
64      Name += RD->getNameAsString() + "::";
65
66  Name += Call.getCalleeIdentifier()->getName();
67  return Name;
68}
69
70// The predefinitions ('CDM') could break due to the ever growing code base.
71// Check for the expected invariants and see whether they apply.
72static Optional<bool> isInvariantBreak(bool ExpectedValue, SVal ReturnV,
73                                       CheckerContext &C) {
74  auto ReturnDV = ReturnV.getAs<DefinedOrUnknownSVal>();
75  if (!ReturnDV)
76    return None;
77
78  if (ExpectedValue)
79    return C.getState()->isNull(*ReturnDV).isConstrainedTrue();
80
81  return C.getState()->isNull(*ReturnDV).isConstrainedFalse();
82}
83
84void ReturnValueChecker::checkPostCall(const CallEvent &Call,
85                                       CheckerContext &C) const {
86  const bool *RawExpectedValue = CDM.lookup(Call);
87  if (!RawExpectedValue)
88    return;
89
90  SVal ReturnV = Call.getReturnValue();
91  bool ExpectedValue = *RawExpectedValue;
92  Optional<bool> IsInvariantBreak = isInvariantBreak(ExpectedValue, ReturnV, C);
93  if (!IsInvariantBreak)
94    return;
95
96  // If the invariant is broken it is reported by 'checkEndFunction()'.
97  if (*IsInvariantBreak)
98    return;
99
100  std::string Name = getName(Call);
101  const NoteTag *CallTag = C.getNoteTag(
102      [Name, ExpectedValue](BugReport &) -> std::string {
103        SmallString<128> Msg;
104        llvm::raw_svector_ostream Out(Msg);
105
106        Out << '\'' << Name << "' returns "
107            << (ExpectedValue ? "true" : "false");
108        return Out.str();
109      },
110      /*IsPrunable=*/true);
111
112  ProgramStateRef State = C.getState();
113  State = State->assume(ReturnV.castAs<DefinedOrUnknownSVal>(), ExpectedValue);
114  C.addTransition(State, CallTag);
115}
116
117void ReturnValueChecker::checkEndFunction(const ReturnStmt *RS,
118                                          CheckerContext &C) const {
119  if (!RS || !RS->getRetValue())
120    return;
121
122  // We cannot get the caller in the top-frame.
123  const StackFrameContext *SFC = C.getStackFrame();
124  if (C.getStackFrame()->inTopFrame())
125    return;
126
127  ProgramStateRef State = C.getState();
128  CallEventManager &CMgr = C.getStateManager().getCallEventManager();
129  CallEventRef<> Call = CMgr.getCaller(SFC, State);
130  if (!Call)
131    return;
132
133  const bool *RawExpectedValue = CDM.lookup(*Call);
134  if (!RawExpectedValue)
135    return;
136
137  SVal ReturnV = State->getSVal(RS->getRetValue(), C.getLocationContext());
138  bool ExpectedValue = *RawExpectedValue;
139  Optional<bool> IsInvariantBreak = isInvariantBreak(ExpectedValue, ReturnV, C);
140  if (!IsInvariantBreak)
141    return;
142
143  // If the invariant is appropriate it is reported by 'checkPostCall()'.
144  if (!*IsInvariantBreak)
145    return;
146
147  std::string Name = getName(*Call);
148  const NoteTag *CallTag = C.getNoteTag(
149      [Name, ExpectedValue](BugReport &BR) -> std::string {
150        SmallString<128> Msg;
151        llvm::raw_svector_ostream Out(Msg);
152
153        // The following is swapped because the invariant is broken.
154        Out << '\'' << Name << "' returns "
155            << (ExpectedValue ? "false" : "true");
156
157        return Out.str();
158      },
159      /*IsPrunable=*/false);
160
161  C.addTransition(State, CallTag);
162}
163
164void ento::registerReturnValueChecker(CheckerManager &Mgr) {
165  Mgr.registerChecker<ReturnValueChecker>();
166}
167
168bool ento::shouldRegisterReturnValueChecker(const LangOptions &LO) {
169  return true;
170}
171