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
32namespace clang::dataflow {
33namespace {
34
35using ast_matchers::BoundNodes;
36using ast_matchers::callee;
37using ast_matchers::cxxMemberCallExpr;
38using ast_matchers::functionDecl;
39using ast_matchers::hasName;
40using ast_matchers::match;
41using ast_matchers::selectFirst;
42
43using dataflow::DataflowAnalysisContext;
44using dataflow::Environment;
45using dataflow::LatticeJoinEffect;
46using dataflow::RecordStorageLocation;
47using dataflow::Value;
48using dataflow::WatchedLiteralsSolver;
49
50using testing::SizeIs;
51
52NamedDecl *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
58class CachedConstAccessorsLatticeTest : public ::testing::Test {
59protected:
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).
67struct 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
105TEST_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
125TEST_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
153TEST_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
200TEST_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
226TEST_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
265TEST_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
280TEST_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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of clang/unittests/Analysis/FlowSensitive/CachedConstAccessorsLatticeTest.cpp