xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/GTestChecker.cpp (revision e32fecd0c2c3ee37c47ee100f169e7eb0282a873)
1 //==- GTestChecker.cpp - Model gtest API --*- 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 checker models the behavior of un-inlined APIs from the gtest
10 // unit-testing library to avoid false positives when using assertions from
11 // that library.
12 //
13 //===----------------------------------------------------------------------===//
14 
15 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
16 #include "clang/AST/Expr.h"
17 #include "clang/Basic/LangOptions.h"
18 #include "clang/StaticAnalyzer/Core/Checker.h"
19 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
20 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
21 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
22 #include "llvm/Support/raw_ostream.h"
23 
24 using namespace clang;
25 using namespace ento;
26 
27 // Modeling of un-inlined AssertionResult constructors
28 //
29 // The gtest unit testing API provides macros for assertions that expand
30 // into an if statement that calls a series of constructors and returns
31 // when the "assertion" is false.
32 //
33 // For example,
34 //
35 //   ASSERT_TRUE(a == b)
36 //
37 // expands into:
38 //
39 //   switch (0)
40 //   case 0:
41 //   default:
42 //     if (const ::testing::AssertionResult gtest_ar_ =
43 //             ::testing::AssertionResult((a == b)))
44 //       ;
45 //     else
46 //       return ::testing::internal::AssertHelper(
47 //                  ::testing::TestPartResult::kFatalFailure,
48 //                  "<path to project>",
49 //                  <line number>,
50 //                  ::testing::internal::GetBoolAssertionFailureMessage(
51 //                      gtest_ar_, "a == b", "false", "true")
52 //                      .c_str()) = ::testing::Message();
53 //
54 // where AssertionResult is defined similarly to
55 //
56 //   class AssertionResult {
57 //   public:
58 //     AssertionResult(const AssertionResult& other);
59 //     explicit AssertionResult(bool success) : success_(success) {}
60 //     operator bool() const { return success_; }
61 //     ...
62 //     private:
63 //     bool success_;
64 //   };
65 //
66 // In order for the analyzer to correctly handle this assertion, it needs to
67 // know that the boolean value of the expression "a == b" is stored the
68 // 'success_' field of the original AssertionResult temporary and propagated
69 // (via the copy constructor) into the 'success_' field of the object stored
70 // in 'gtest_ar_'.  That boolean value will then be returned from the bool
71 // conversion method in the if statement. This guarantees that the assertion
72 // holds when the return path is not taken.
73 //
74 // If the success value is not properly propagated, then the eager case split
75 // on evaluating the expression can cause pernicious false positives
76 // on the non-return path:
77 //
78 //   ASSERT(ptr != NULL)
79 //   *ptr = 7; // False positive null pointer dereference here
80 //
81 // Unfortunately, the bool constructor cannot be inlined (because its
82 // implementation is not present in the headers) and the copy constructor is
83 // not inlined (because it is constructed into a temporary and the analyzer
84 // does not inline these since it does not yet reliably call temporary
85 // destructors).
86 //
87 // This checker compensates for the missing inlining by propagating the
88 // _success value across the bool and copy constructors so the assertion behaves
89 // as expected.
90 
91 namespace {
92 class GTestChecker : public Checker<check::PostCall> {
93 
94   mutable IdentifierInfo *AssertionResultII;
95   mutable IdentifierInfo *SuccessII;
96 
97 public:
98   GTestChecker();
99 
100   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
101 
102 private:
103   void modelAssertionResultBoolConstructor(const CXXConstructorCall *Call,
104                                            bool IsRef, CheckerContext &C) const;
105 
106   void modelAssertionResultCopyConstructor(const CXXConstructorCall *Call,
107                                            CheckerContext &C) const;
108 
109   void initIdentifierInfo(ASTContext &Ctx) const;
110 
111   SVal
112   getAssertionResultSuccessFieldValue(const CXXRecordDecl *AssertionResultDecl,
113                                       SVal Instance,
114                                       ProgramStateRef State) const;
115 
116   static ProgramStateRef assumeValuesEqual(SVal Val1, SVal Val2,
117                                            ProgramStateRef State,
118                                            CheckerContext &C);
119 };
120 } // End anonymous namespace.
121 
122 GTestChecker::GTestChecker() : AssertionResultII(nullptr), SuccessII(nullptr) {}
123 
124 /// Model a call to an un-inlined AssertionResult(bool) or
125 /// AssertionResult(bool &, ...).
126 /// To do so, constrain the value of the newly-constructed instance's 'success_'
127 /// field to be equal to the passed-in boolean value.
128 ///
129 /// \param IsRef Whether the boolean parameter is a reference or not.
130 void GTestChecker::modelAssertionResultBoolConstructor(
131     const CXXConstructorCall *Call, bool IsRef, CheckerContext &C) const {
132   assert(Call->getNumArgs() >= 1 && Call->getNumArgs() <= 2);
133 
134   ProgramStateRef State = C.getState();
135   SVal BooleanArgVal = Call->getArgSVal(0);
136   if (IsRef) {
137     // The argument is a reference, so load from it to get the boolean value.
138     if (!isa<Loc>(BooleanArgVal))
139       return;
140     BooleanArgVal = C.getState()->getSVal(BooleanArgVal.castAs<Loc>());
141   }
142 
143   SVal ThisVal = Call->getCXXThisVal();
144 
145   SVal ThisSuccess = getAssertionResultSuccessFieldValue(
146       Call->getDecl()->getParent(), ThisVal, State);
147 
148   State = assumeValuesEqual(ThisSuccess, BooleanArgVal, State, C);
149   C.addTransition(State);
150 }
151 
152 /// Model a call to an un-inlined AssertionResult copy constructor:
153 ///
154 ///   AssertionResult(const &AssertionResult other)
155 ///
156 /// To do so, constrain the value of the newly-constructed instance's
157 /// 'success_' field to be equal to the value of the pass-in instance's
158 /// 'success_' field.
159 void GTestChecker::modelAssertionResultCopyConstructor(
160     const CXXConstructorCall *Call, CheckerContext &C) const {
161   assert(Call->getNumArgs() == 1);
162 
163   // The first parameter of the copy constructor must be the other
164   // instance to initialize this instances fields from.
165   SVal OtherVal = Call->getArgSVal(0);
166   SVal ThisVal = Call->getCXXThisVal();
167 
168   const CXXRecordDecl *AssertResultClassDecl = Call->getDecl()->getParent();
169   ProgramStateRef State = C.getState();
170 
171   SVal ThisSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl,
172                                                          ThisVal, State);
173   SVal OtherSuccess = getAssertionResultSuccessFieldValue(AssertResultClassDecl,
174                                                           OtherVal, State);
175 
176   State = assumeValuesEqual(ThisSuccess, OtherSuccess, State, C);
177   C.addTransition(State);
178 }
179 
180 /// Model calls to AssertionResult constructors that are not inlined.
181 void GTestChecker::checkPostCall(const CallEvent &Call,
182                                  CheckerContext &C) const {
183   /// If the constructor was inlined, there is no need model it.
184   if (C.wasInlined)
185     return;
186 
187   initIdentifierInfo(C.getASTContext());
188 
189   auto *CtorCall = dyn_cast<CXXConstructorCall>(&Call);
190   if (!CtorCall)
191     return;
192 
193   const CXXConstructorDecl *CtorDecl = CtorCall->getDecl();
194   const CXXRecordDecl *CtorParent = CtorDecl->getParent();
195   if (CtorParent->getIdentifier() != AssertionResultII)
196     return;
197 
198   unsigned ParamCount = CtorDecl->getNumParams();
199 
200   // Call the appropriate modeling method based the parameters and their
201   // types.
202 
203   // We have AssertionResult(const &AssertionResult)
204   if (CtorDecl->isCopyConstructor() && ParamCount == 1) {
205     modelAssertionResultCopyConstructor(CtorCall, C);
206     return;
207   }
208 
209   // There are two possible boolean constructors, depending on which
210   // version of gtest is being used:
211   //
212   // v1.7 and earlier:
213   //      AssertionResult(bool success)
214   //
215   // v1.8 and greater:
216   //      template <typename T>
217   //      AssertionResult(const T& success,
218   //                      typename internal::EnableIf<
219   //                          !internal::ImplicitlyConvertible<T,
220   //                              AssertionResult>::value>::type*)
221   //
222   CanQualType BoolTy = C.getASTContext().BoolTy;
223   if (ParamCount == 1 && CtorDecl->getParamDecl(0)->getType() == BoolTy) {
224     // We have AssertionResult(bool)
225     modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/false, C);
226     return;
227   }
228   if (ParamCount == 2){
229     auto *RefTy = CtorDecl->getParamDecl(0)->getType()->getAs<ReferenceType>();
230     if (RefTy &&
231         RefTy->getPointeeType()->getCanonicalTypeUnqualified() == BoolTy) {
232       // We have AssertionResult(bool &, ...)
233       modelAssertionResultBoolConstructor(CtorCall, /*IsRef=*/true, C);
234       return;
235     }
236   }
237 }
238 
239 void GTestChecker::initIdentifierInfo(ASTContext &Ctx) const {
240   if (AssertionResultII)
241     return;
242 
243   AssertionResultII = &Ctx.Idents.get("AssertionResult");
244   SuccessII = &Ctx.Idents.get("success_");
245 }
246 
247 /// Returns the value stored in the 'success_' field of the passed-in
248 /// AssertionResult instance.
249 SVal GTestChecker::getAssertionResultSuccessFieldValue(
250     const CXXRecordDecl *AssertionResultDecl, SVal Instance,
251     ProgramStateRef State) const {
252 
253   DeclContext::lookup_result Result = AssertionResultDecl->lookup(SuccessII);
254   if (Result.empty())
255     return UnknownVal();
256 
257   auto *SuccessField = dyn_cast<FieldDecl>(Result.front());
258   if (!SuccessField)
259     return UnknownVal();
260 
261   Optional<Loc> FieldLoc =
262       State->getLValue(SuccessField, Instance).getAs<Loc>();
263   if (!FieldLoc)
264     return UnknownVal();
265 
266   return State->getSVal(*FieldLoc);
267 }
268 
269 /// Constrain the passed-in state to assume two values are equal.
270 ProgramStateRef GTestChecker::assumeValuesEqual(SVal Val1, SVal Val2,
271                                                 ProgramStateRef State,
272                                                 CheckerContext &C) {
273   auto DVal1 = Val1.getAs<DefinedOrUnknownSVal>();
274   auto DVal2 = Val2.getAs<DefinedOrUnknownSVal>();
275   if (!DVal1 || !DVal2)
276     return State;
277 
278   auto ValuesEqual =
279       C.getSValBuilder().evalEQ(State, *DVal1, *DVal2).getAs<DefinedSVal>();
280   if (!ValuesEqual)
281     return State;
282 
283   State = C.getConstraintManager().assume(State, *ValuesEqual, true);
284   return State;
285 }
286 
287 void ento::registerGTestChecker(CheckerManager &Mgr) {
288   Mgr.registerChecker<GTestChecker>();
289 }
290 
291 bool ento::shouldRegisterGTestChecker(const CheckerManager &mgr) {
292   // gtest is a C++ API so there is no sense running the checker
293   // if not compiling for C++.
294   const LangOptions &LO = mgr.getLangOpts();
295   return LO.CPlusPlus;
296 }
297