xref: /llvm-project/clang/unittests/Analysis/FlowSensitive/CachedConstAccessorsLatticeTest.cpp (revision 72a28a3bf0b539bcdfd8f41905675ce6a890c0ac)
1 //===- unittests/Analysis/FlowSensitive/CachedConstAccessorsLatticeTest.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 #include "clang/Analysis/FlowSensitive/CachedConstAccessorsLattice.h"
10 
11 #include <cassert>
12 #include <memory>
13 
14 #include "clang/AST/Decl.h"
15 #include "clang/AST/DeclBase.h"
16 #include "clang/AST/DeclCXX.h"
17 #include "clang/AST/Expr.h"
18 #include "clang/AST/Type.h"
19 #include "clang/ASTMatchers/ASTMatchFinder.h"
20 #include "clang/ASTMatchers/ASTMatchers.h"
21 #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
22 #include "clang/Analysis/FlowSensitive/DataflowLattice.h"
23 #include "clang/Analysis/FlowSensitive/NoopLattice.h"
24 #include "clang/Analysis/FlowSensitive/StorageLocation.h"
25 #include "clang/Analysis/FlowSensitive/Value.h"
26 #include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
27 #include "clang/Basic/LLVM.h"
28 #include "clang/Testing/TestAST.h"
29 #include "gmock/gmock.h"
30 #include "gtest/gtest.h"
31 
32 namespace clang::dataflow {
33 namespace {
34 
35 using ast_matchers::BoundNodes;
36 using ast_matchers::callee;
37 using ast_matchers::cxxMemberCallExpr;
38 using ast_matchers::functionDecl;
39 using ast_matchers::hasName;
40 using ast_matchers::match;
41 using ast_matchers::selectFirst;
42 
43 using dataflow::DataflowAnalysisContext;
44 using dataflow::Environment;
45 using dataflow::LatticeJoinEffect;
46 using dataflow::RecordStorageLocation;
47 using dataflow::Value;
48 using dataflow::WatchedLiteralsSolver;
49 
50 using testing::SizeIs;
51 
52 NamedDecl *lookup(StringRef Name, const DeclContext &DC) {
53   auto Result = DC.lookup(&DC.getParentASTContext().Idents.get(Name));
54   EXPECT_TRUE(Result.isSingleResult()) << Name;
55   return Result.front();
56 }
57 
58 class CachedConstAccessorsLatticeTest : public ::testing::Test {
59 protected:
60   using LatticeT = CachedConstAccessorsLattice<NoopLattice>;
61 
62   DataflowAnalysisContext DACtx{std::make_unique<WatchedLiteralsSolver>()};
63   Environment Env{DACtx};
64 };
65 
66 // Basic test AST with two const methods (return a value, and return a ref).
67 struct CommonTestInputs {
68   CommonTestInputs()
69       : AST(R"cpp(
70     struct S {
71       int *valProperty() const;
72       int &refProperty() const;
73     };
74     void target() {
75       S s;
76       s.valProperty();
77       S s2;
78       s2.refProperty();
79     }
80   )cpp") {
81     auto *SDecl = cast<CXXRecordDecl>(
82         lookup("S", *AST.context().getTranslationUnitDecl()));
83     SType = AST.context().getRecordType(SDecl);
84     CallVal = selectFirst<CallExpr>(
85         "call",
86         match(cxxMemberCallExpr(callee(functionDecl(hasName("valProperty"))))
87                   .bind("call"),
88               AST.context()));
89     assert(CallVal != nullptr);
90 
91     CallRef = selectFirst<CallExpr>(
92         "call",
93         match(cxxMemberCallExpr(callee(functionDecl(hasName("refProperty"))))
94                   .bind("call"),
95               AST.context()));
96     assert(CallRef != nullptr);
97   }
98 
99   TestAST AST;
100   QualType SType;
101   const CallExpr *CallVal;
102   const CallExpr *CallRef;
103 };
104 
105 TEST_F(CachedConstAccessorsLatticeTest,
106        SamePrimitiveValBeforeClearOrDiffAfterClear) {
107   CommonTestInputs Inputs;
108   auto *CE = Inputs.CallVal;
109   RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(),
110                             {});
111 
112   LatticeT Lattice;
113   Value *Val1 = Lattice.getOrCreateConstMethodReturnValue(Loc, CE, Env);
114   Value *Val2 = Lattice.getOrCreateConstMethodReturnValue(Loc, CE, Env);
115 
116   EXPECT_EQ(Val1, Val2);
117 
118   Lattice.clearConstMethodReturnValues(Loc);
119   Value *Val3 = Lattice.getOrCreateConstMethodReturnValue(Loc, CE, Env);
120 
121   EXPECT_NE(Val3, Val1);
122   EXPECT_NE(Val3, Val2);
123 }
124 
125 TEST_F(CachedConstAccessorsLatticeTest, SameLocBeforeClearOrDiffAfterClear) {
126   CommonTestInputs Inputs;
127   auto *CE = Inputs.CallRef;
128   RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(),
129                             {});
130 
131   LatticeT Lattice;
132   auto NopInit = [](StorageLocation &) {};
133   StorageLocation *Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation(
134       Loc, CE, Env, NopInit);
135   auto NotCalled = [](StorageLocation &) {
136     ASSERT_TRUE(false) << "Not reached";
137   };
138   StorageLocation *Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation(
139       Loc, CE, Env, NotCalled);
140 
141   EXPECT_EQ(Loc1, Loc2);
142 
143   Lattice.clearConstMethodReturnStorageLocations(Loc);
144   StorageLocation *Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation(
145       Loc, CE, Env, NopInit);
146 
147   EXPECT_NE(Loc3, Loc1);
148   EXPECT_NE(Loc3, Loc2);
149 }
150 
151 TEST_F(CachedConstAccessorsLatticeTest,
152        SameLocBeforeClearOrDiffAfterClearWithCallee) {
153   CommonTestInputs Inputs;
154   auto *CE = Inputs.CallRef;
155   RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(),
156                             {});
157 
158   LatticeT Lattice;
159   auto NopInit = [](StorageLocation &) {};
160   const FunctionDecl *Callee = CE->getDirectCallee();
161   ASSERT_NE(Callee, nullptr);
162   StorageLocation &Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation(
163       Loc, Callee, Env, NopInit);
164   auto NotCalled = [](StorageLocation &) {
165     ASSERT_TRUE(false) << "Not reached";
166   };
167   StorageLocation &Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation(
168       Loc, Callee, Env, NotCalled);
169 
170   EXPECT_EQ(&Loc1, &Loc2);
171 
172   Lattice.clearConstMethodReturnStorageLocations(Loc);
173   StorageLocation &Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation(
174       Loc, Callee, Env, NopInit);
175 
176   EXPECT_NE(&Loc3, &Loc1);
177   EXPECT_NE(&Loc3, &Loc2);
178 }
179 
180 TEST_F(CachedConstAccessorsLatticeTest,
181        SameStructValBeforeClearOrDiffAfterClear) {
182   TestAST AST(R"cpp(
183     struct S {
184       S structValProperty() const;
185     };
186     void target() {
187       S s;
188       s.structValProperty();
189     }
190   )cpp");
191   auto *SDecl =
192       cast<CXXRecordDecl>(lookup("S", *AST.context().getTranslationUnitDecl()));
193   QualType SType = AST.context().getRecordType(SDecl);
194   const CallExpr *CE = selectFirst<CallExpr>(
195       "call", match(cxxMemberCallExpr(
196                         callee(functionDecl(hasName("structValProperty"))))
197                         .bind("call"),
198                     AST.context()));
199   ASSERT_NE(CE, nullptr);
200 
201   RecordStorageLocation Loc(SType, RecordStorageLocation::FieldToLoc(), {});
202 
203   LatticeT Lattice;
204   // Accessors that return a record by value are modeled by a record storage
205   // location (instead of a Value).
206   auto NopInit = [](StorageLocation &) {};
207   StorageLocation *Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation(
208       Loc, CE, Env, NopInit);
209   auto NotCalled = [](StorageLocation &) {
210     ASSERT_TRUE(false) << "Not reached";
211   };
212   StorageLocation *Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation(
213       Loc, CE, Env, NotCalled);
214 
215   EXPECT_EQ(Loc1, Loc2);
216 
217   Lattice.clearConstMethodReturnStorageLocations(Loc);
218   StorageLocation *Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation(
219       Loc, CE, Env, NopInit);
220 
221   EXPECT_NE(Loc3, Loc1);
222   EXPECT_NE(Loc3, Loc1);
223 }
224 
225 TEST_F(CachedConstAccessorsLatticeTest, ClearDifferentLocs) {
226   CommonTestInputs Inputs;
227   auto *CE = Inputs.CallRef;
228   RecordStorageLocation LocS1(Inputs.SType, RecordStorageLocation::FieldToLoc(),
229                               {});
230   RecordStorageLocation LocS2(Inputs.SType, RecordStorageLocation::FieldToLoc(),
231                               {});
232 
233   LatticeT Lattice;
234   auto NopInit = [](StorageLocation &) {};
235   StorageLocation *RetLoc1 =
236       Lattice.getOrCreateConstMethodReturnStorageLocation(LocS1, CE, Env,
237                                                           NopInit);
238   Lattice.clearConstMethodReturnStorageLocations(LocS2);
239   auto NotCalled = [](StorageLocation &) {
240     ASSERT_TRUE(false) << "Not reached";
241   };
242   StorageLocation *RetLoc2 =
243       Lattice.getOrCreateConstMethodReturnStorageLocation(LocS1, CE, Env,
244                                                           NotCalled);
245 
246   EXPECT_EQ(RetLoc1, RetLoc2);
247 }
248 
249 TEST_F(CachedConstAccessorsLatticeTest, DifferentValsFromDifferentLocs) {
250   TestAST AST(R"cpp(
251     struct S {
252       int *valProperty() const;
253     };
254     void target() {
255       S s1;
256       s1.valProperty();
257       S s2;
258       s2.valProperty();
259     }
260   )cpp");
261   auto *SDecl =
262       cast<CXXRecordDecl>(lookup("S", *AST.context().getTranslationUnitDecl()));
263   QualType SType = AST.context().getRecordType(SDecl);
264   SmallVector<BoundNodes, 1> valPropertyCalls =
265       match(cxxMemberCallExpr(callee(functionDecl(hasName("valProperty"))))
266                 .bind("call"),
267             AST.context());
268   ASSERT_THAT(valPropertyCalls, SizeIs(2));
269 
270   const CallExpr *CE1 = selectFirst<CallExpr>("call", valPropertyCalls);
271   ASSERT_NE(CE1, nullptr);
272 
273   valPropertyCalls.erase(valPropertyCalls.begin());
274   const CallExpr *CE2 = selectFirst<CallExpr>("call", valPropertyCalls);
275   ASSERT_NE(CE2, nullptr);
276   ASSERT_NE(CE1, CE2);
277 
278   RecordStorageLocation LocS1(SType, RecordStorageLocation::FieldToLoc(), {});
279   RecordStorageLocation LocS2(SType, RecordStorageLocation::FieldToLoc(), {});
280 
281   LatticeT Lattice;
282   Value *Val1 = Lattice.getOrCreateConstMethodReturnValue(LocS1, CE1, Env);
283   Value *Val2 = Lattice.getOrCreateConstMethodReturnValue(LocS2, CE2, Env);
284 
285   EXPECT_NE(Val1, Val2);
286 }
287 
288 TEST_F(CachedConstAccessorsLatticeTest, JoinSameNoop) {
289   CommonTestInputs Inputs;
290   auto *CE = Inputs.CallVal;
291   RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(),
292                             {});
293 
294   LatticeT EmptyLattice;
295   LatticeT EmptyLattice2;
296   EXPECT_EQ(EmptyLattice.join(EmptyLattice2), LatticeJoinEffect::Unchanged);
297 
298   LatticeT Lattice1;
299   Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env);
300   EXPECT_EQ(Lattice1.join(Lattice1), LatticeJoinEffect::Unchanged);
301 }
302 
303 TEST_F(CachedConstAccessorsLatticeTest, ProducesNewValueAfterJoinDistinct) {
304   CommonTestInputs Inputs;
305   auto *CE = Inputs.CallVal;
306   RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(),
307                             {});
308 
309   // L1 w/ v vs L2 empty
310   LatticeT Lattice1;
311   Value *Val1 = Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env);
312 
313   LatticeT EmptyLattice;
314 
315   EXPECT_EQ(Lattice1.join(EmptyLattice), LatticeJoinEffect::Changed);
316   Value *ValAfterJoin =
317       Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env);
318 
319   EXPECT_NE(ValAfterJoin, Val1);
320 
321   // L1 w/ v1 vs L3 w/ v2
322   LatticeT Lattice3;
323   Value *Val3 = Lattice3.getOrCreateConstMethodReturnValue(Loc, CE, Env);
324 
325   EXPECT_EQ(Lattice1.join(Lattice3), LatticeJoinEffect::Changed);
326   Value *ValAfterJoin2 =
327       Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env);
328 
329   EXPECT_NE(ValAfterJoin2, ValAfterJoin);
330   EXPECT_NE(ValAfterJoin2, Val3);
331 }
332 
333 } // namespace
334 } // namespace clang::dataflow
335