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(Name: &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 | Val: lookup("S" , *AST.context().getTranslationUnitDecl())); |
83 | SType = AST.context().getRecordType(SDecl); |
84 | CallVal = selectFirst<CallExpr>( |
85 | BoundTo: "call" , |
86 | Results: match(Matcher: cxxMemberCallExpr(callee(InnerMatcher: functionDecl(hasName(Name: "valProperty" )))) |
87 | .bind(ID: "call" ), |
88 | Context&: AST.context())); |
89 | assert(CallVal != nullptr); |
90 | |
91 | CallRef = selectFirst<CallExpr>( |
92 | BoundTo: "call" , |
93 | Results: match(Matcher: cxxMemberCallExpr(callee(InnerMatcher: functionDecl(hasName(Name: "refProperty" )))) |
94 | .bind(ID: "call" ), |
95 | Context&: 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(RecordLoc: Loc, CE, Env); |
114 | Value *Val2 = Lattice.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
115 | |
116 | EXPECT_EQ(Val1, Val2); |
117 | |
118 | Lattice.clearConstMethodReturnValues(RecordLoc: Loc); |
119 | Value *Val3 = Lattice.getOrCreateConstMethodReturnValue(RecordLoc: 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 | const FunctionDecl *Callee = CE->getDirectCallee(); |
134 | ASSERT_NE(Callee, nullptr); |
135 | StorageLocation &Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation( |
136 | RecordLoc: Loc, Callee, Env, Initialize: NopInit); |
137 | auto NotCalled = [](StorageLocation &) { |
138 | ASSERT_TRUE(false) << "Not reached" ; |
139 | }; |
140 | StorageLocation &Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation( |
141 | RecordLoc: Loc, Callee, Env, Initialize: NotCalled); |
142 | |
143 | EXPECT_EQ(&Loc1, &Loc2); |
144 | |
145 | Lattice.clearConstMethodReturnStorageLocations(RecordLoc: Loc); |
146 | StorageLocation &Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation( |
147 | RecordLoc: Loc, Callee, Env, Initialize: NopInit); |
148 | |
149 | EXPECT_NE(&Loc3, &Loc1); |
150 | EXPECT_NE(&Loc3, &Loc2); |
151 | } |
152 | |
153 | TEST_F(CachedConstAccessorsLatticeTest, |
154 | SameStructValBeforeClearOrDiffAfterClear) { |
155 | TestAST AST(R"cpp( |
156 | struct S { |
157 | S structValProperty() const; |
158 | }; |
159 | void target() { |
160 | S s; |
161 | s.structValProperty(); |
162 | } |
163 | )cpp" ); |
164 | auto *SDecl = |
165 | cast<CXXRecordDecl>(Val: lookup("S" , *AST.context().getTranslationUnitDecl())); |
166 | QualType SType = AST.context().getRecordType(Decl: SDecl); |
167 | const CallExpr *CE = selectFirst<CallExpr>( |
168 | BoundTo: "call" , Results: match(Matcher: cxxMemberCallExpr( |
169 | callee(InnerMatcher: functionDecl(hasName(Name: "structValProperty" )))) |
170 | .bind(ID: "call" ), |
171 | Context&: AST.context())); |
172 | ASSERT_NE(CE, nullptr); |
173 | |
174 | RecordStorageLocation Loc(SType, RecordStorageLocation::FieldToLoc(), {}); |
175 | |
176 | LatticeT Lattice; |
177 | // Accessors that return a record by value are modeled by a record storage |
178 | // location (instead of a Value). |
179 | auto NopInit = [](StorageLocation &) {}; |
180 | const FunctionDecl *Callee = CE->getDirectCallee(); |
181 | ASSERT_NE(Callee, nullptr); |
182 | StorageLocation &Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation( |
183 | RecordLoc: Loc, Callee, Env, Initialize: NopInit); |
184 | auto NotCalled = [](StorageLocation &) { |
185 | ASSERT_TRUE(false) << "Not reached" ; |
186 | }; |
187 | StorageLocation &Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation( |
188 | RecordLoc: Loc, Callee, Env, Initialize: NotCalled); |
189 | |
190 | EXPECT_EQ(&Loc1, &Loc2); |
191 | |
192 | Lattice.clearConstMethodReturnStorageLocations(RecordLoc: Loc); |
193 | StorageLocation &Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation( |
194 | RecordLoc: Loc, Callee, Env, Initialize: NopInit); |
195 | |
196 | EXPECT_NE(&Loc3, &Loc1); |
197 | EXPECT_NE(&Loc3, &Loc1); |
198 | } |
199 | |
200 | TEST_F(CachedConstAccessorsLatticeTest, ClearDifferentLocs) { |
201 | CommonTestInputs Inputs; |
202 | auto *CE = Inputs.CallRef; |
203 | RecordStorageLocation LocS1(Inputs.SType, RecordStorageLocation::FieldToLoc(), |
204 | {}); |
205 | RecordStorageLocation LocS2(Inputs.SType, RecordStorageLocation::FieldToLoc(), |
206 | {}); |
207 | |
208 | LatticeT Lattice; |
209 | auto NopInit = [](StorageLocation &) {}; |
210 | const FunctionDecl *Callee = CE->getDirectCallee(); |
211 | ASSERT_NE(Callee, nullptr); |
212 | StorageLocation &RetLoc1 = |
213 | Lattice.getOrCreateConstMethodReturnStorageLocation(RecordLoc: LocS1, Callee, Env, |
214 | Initialize: NopInit); |
215 | Lattice.clearConstMethodReturnStorageLocations(RecordLoc: LocS2); |
216 | auto NotCalled = [](StorageLocation &) { |
217 | ASSERT_TRUE(false) << "Not reached" ; |
218 | }; |
219 | StorageLocation &RetLoc2 = |
220 | Lattice.getOrCreateConstMethodReturnStorageLocation(RecordLoc: LocS1, Callee, Env, |
221 | Initialize: NotCalled); |
222 | |
223 | EXPECT_EQ(&RetLoc1, &RetLoc2); |
224 | } |
225 | |
226 | TEST_F(CachedConstAccessorsLatticeTest, DifferentValsFromDifferentLocs) { |
227 | TestAST AST(R"cpp( |
228 | struct S { |
229 | int *valProperty() const; |
230 | }; |
231 | void target() { |
232 | S s1; |
233 | s1.valProperty(); |
234 | S s2; |
235 | s2.valProperty(); |
236 | } |
237 | )cpp" ); |
238 | auto *SDecl = |
239 | cast<CXXRecordDecl>(Val: lookup("S" , *AST.context().getTranslationUnitDecl())); |
240 | QualType SType = AST.context().getRecordType(Decl: SDecl); |
241 | SmallVector<BoundNodes, 1> valPropertyCalls = |
242 | match(Matcher: cxxMemberCallExpr(callee(InnerMatcher: functionDecl(hasName(Name: "valProperty" )))) |
243 | .bind(ID: "call" ), |
244 | Context&: AST.context()); |
245 | ASSERT_THAT(valPropertyCalls, SizeIs(2)); |
246 | |
247 | const CallExpr *CE1 = selectFirst<CallExpr>(BoundTo: "call" , Results: valPropertyCalls); |
248 | ASSERT_NE(CE1, nullptr); |
249 | |
250 | valPropertyCalls.erase(CI: valPropertyCalls.begin()); |
251 | const CallExpr *CE2 = selectFirst<CallExpr>(BoundTo: "call" , Results: valPropertyCalls); |
252 | ASSERT_NE(CE2, nullptr); |
253 | ASSERT_NE(CE1, CE2); |
254 | |
255 | RecordStorageLocation LocS1(SType, RecordStorageLocation::FieldToLoc(), {}); |
256 | RecordStorageLocation LocS2(SType, RecordStorageLocation::FieldToLoc(), {}); |
257 | |
258 | LatticeT Lattice; |
259 | Value *Val1 = Lattice.getOrCreateConstMethodReturnValue(RecordLoc: LocS1, CE: CE1, Env); |
260 | Value *Val2 = Lattice.getOrCreateConstMethodReturnValue(RecordLoc: LocS2, CE: CE2, Env); |
261 | |
262 | EXPECT_NE(Val1, Val2); |
263 | } |
264 | |
265 | TEST_F(CachedConstAccessorsLatticeTest, JoinSameNoop) { |
266 | CommonTestInputs Inputs; |
267 | auto *CE = Inputs.CallVal; |
268 | RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), |
269 | {}); |
270 | |
271 | LatticeT EmptyLattice; |
272 | LatticeT EmptyLattice2; |
273 | EXPECT_EQ(EmptyLattice.join(EmptyLattice2), LatticeJoinEffect::Unchanged); |
274 | |
275 | LatticeT Lattice1; |
276 | Lattice1.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
277 | EXPECT_EQ(Lattice1.join(Lattice1), LatticeJoinEffect::Unchanged); |
278 | } |
279 | |
280 | TEST_F(CachedConstAccessorsLatticeTest, ProducesNewValueAfterJoinDistinct) { |
281 | CommonTestInputs Inputs; |
282 | auto *CE = Inputs.CallVal; |
283 | RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), |
284 | {}); |
285 | |
286 | // L1 w/ v vs L2 empty |
287 | LatticeT Lattice1; |
288 | Value *Val1 = Lattice1.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
289 | |
290 | LatticeT EmptyLattice; |
291 | |
292 | EXPECT_EQ(Lattice1.join(EmptyLattice), LatticeJoinEffect::Changed); |
293 | Value *ValAfterJoin = |
294 | Lattice1.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
295 | |
296 | EXPECT_NE(ValAfterJoin, Val1); |
297 | |
298 | // L1 w/ v1 vs L3 w/ v2 |
299 | LatticeT Lattice3; |
300 | Value *Val3 = Lattice3.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
301 | |
302 | EXPECT_EQ(Lattice1.join(Lattice3), LatticeJoinEffect::Changed); |
303 | Value *ValAfterJoin2 = |
304 | Lattice1.getOrCreateConstMethodReturnValue(RecordLoc: Loc, CE, Env); |
305 | |
306 | EXPECT_NE(ValAfterJoin2, ValAfterJoin); |
307 | EXPECT_NE(ValAfterJoin2, Val3); |
308 | } |
309 | |
310 | } // namespace |
311 | } // namespace clang::dataflow |
312 | |