1 | //===- unittests/Analysis/FlowSensitive/RecordOpsTest.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/RecordOps.h" |
10 | #include "TestingSupport.h" |
11 | #include "llvm/Testing/Support/Error.h" |
12 | #include "gtest/gtest.h" |
13 | |
14 | namespace clang { |
15 | namespace dataflow { |
16 | namespace test { |
17 | namespace { |
18 | |
19 | void runDataflow( |
20 | llvm::StringRef Code, |
21 | std::function<llvm::StringMap<QualType>(QualType)> SyntheticFieldCallback, |
22 | std::function< |
23 | void(const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &, |
24 | ASTContext &)> |
25 | VerifyResults) { |
26 | ASSERT_THAT_ERROR(checkDataflowWithNoopAnalysis( |
27 | Code, ast_matchers::hasName("target" ), VerifyResults, |
28 | {BuiltinOptions()}, LangStandard::lang_cxx17, |
29 | SyntheticFieldCallback), |
30 | llvm::Succeeded()); |
31 | } |
32 | |
33 | const FieldDecl *getFieldNamed(RecordDecl *RD, llvm::StringRef Name) { |
34 | for (const FieldDecl *FD : RD->fields()) |
35 | if (FD->getName() == Name) |
36 | return FD; |
37 | assert(false); |
38 | return nullptr; |
39 | } |
40 | |
41 | TEST(RecordOpsTest, CopyRecord) { |
42 | std::string Code = R"( |
43 | struct S { |
44 | int outer_int; |
45 | int &ref; |
46 | struct { |
47 | int inner_int; |
48 | } inner; |
49 | }; |
50 | void target(S s1, S s2) { |
51 | (void)s1.outer_int; |
52 | (void)s1.ref; |
53 | (void)s1.inner.inner_int; |
54 | // [[p]] |
55 | } |
56 | )" ; |
57 | runDataflow( |
58 | Code, |
59 | SyntheticFieldCallback: [](QualType Ty) -> llvm::StringMap<QualType> { |
60 | if (Ty.getAsString() != "S" ) |
61 | return {}; |
62 | QualType IntTy = |
63 | getFieldNamed(RD: Ty->getAsRecordDecl(), Name: "outer_int" )->getType(); |
64 | return {{"synth_int" , IntTy}}; |
65 | }, |
66 | VerifyResults: [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
67 | ASTContext &ASTCtx) { |
68 | Environment Env = getEnvironmentAtAnnotation(AnnotationStates: Results, Annotation: "p" ).fork(); |
69 | |
70 | const ValueDecl *OuterIntDecl = findValueDecl(ASTCtx, Name: "outer_int" ); |
71 | const ValueDecl *RefDecl = findValueDecl(ASTCtx, Name: "ref" ); |
72 | const ValueDecl *InnerDecl = findValueDecl(ASTCtx, Name: "inner" ); |
73 | const ValueDecl *InnerIntDecl = findValueDecl(ASTCtx, Name: "inner_int" ); |
74 | |
75 | auto &S1 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, Name: "s1" ); |
76 | auto &S2 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, Name: "s2" ); |
77 | auto &Inner1 = *cast<RecordStorageLocation>(Val: S1.getChild(D: *InnerDecl)); |
78 | auto &Inner2 = *cast<RecordStorageLocation>(Val: S2.getChild(D: *InnerDecl)); |
79 | |
80 | EXPECT_NE(getFieldValue(&S1, *OuterIntDecl, Env), |
81 | getFieldValue(&S2, *OuterIntDecl, Env)); |
82 | EXPECT_NE(S1.getChild(*RefDecl), S2.getChild(*RefDecl)); |
83 | EXPECT_NE(getFieldValue(&Inner1, *InnerIntDecl, Env), |
84 | getFieldValue(&Inner2, *InnerIntDecl, Env)); |
85 | EXPECT_NE(Env.getValue(S1.getSyntheticField("synth_int" )), |
86 | Env.getValue(S2.getSyntheticField("synth_int" ))); |
87 | |
88 | copyRecord(Src&: S1, Dst&: S2, Env); |
89 | |
90 | EXPECT_EQ(getFieldValue(&S1, *OuterIntDecl, Env), |
91 | getFieldValue(&S2, *OuterIntDecl, Env)); |
92 | EXPECT_EQ(S1.getChild(*RefDecl), S2.getChild(*RefDecl)); |
93 | EXPECT_EQ(getFieldValue(&Inner1, *InnerIntDecl, Env), |
94 | getFieldValue(&Inner2, *InnerIntDecl, Env)); |
95 | EXPECT_EQ(Env.getValue(S1.getSyntheticField("synth_int" )), |
96 | Env.getValue(S2.getSyntheticField("synth_int" ))); |
97 | }); |
98 | } |
99 | |
100 | TEST(RecordOpsTest, RecordsEqual) { |
101 | std::string Code = R"( |
102 | struct S { |
103 | int outer_int; |
104 | int &ref; |
105 | struct { |
106 | int inner_int; |
107 | } inner; |
108 | }; |
109 | void target(S s1, S s2) { |
110 | (void)s1.outer_int; |
111 | (void)s1.ref; |
112 | (void)s1.inner.inner_int; |
113 | // [[p]] |
114 | } |
115 | )" ; |
116 | runDataflow( |
117 | Code, |
118 | SyntheticFieldCallback: [](QualType Ty) -> llvm::StringMap<QualType> { |
119 | if (Ty.getAsString() != "S" ) |
120 | return {}; |
121 | QualType IntTy = |
122 | getFieldNamed(RD: Ty->getAsRecordDecl(), Name: "outer_int" )->getType(); |
123 | return {{"synth_int" , IntTy}}; |
124 | }, |
125 | VerifyResults: [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
126 | ASTContext &ASTCtx) { |
127 | Environment Env = getEnvironmentAtAnnotation(AnnotationStates: Results, Annotation: "p" ).fork(); |
128 | |
129 | const ValueDecl *OuterIntDecl = findValueDecl(ASTCtx, Name: "outer_int" ); |
130 | const ValueDecl *RefDecl = findValueDecl(ASTCtx, Name: "ref" ); |
131 | const ValueDecl *InnerDecl = findValueDecl(ASTCtx, Name: "inner" ); |
132 | const ValueDecl *InnerIntDecl = findValueDecl(ASTCtx, Name: "inner_int" ); |
133 | |
134 | auto &S1 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, Name: "s1" ); |
135 | auto &S2 = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, Name: "s2" ); |
136 | auto &Inner2 = *cast<RecordStorageLocation>(Val: S2.getChild(D: *InnerDecl)); |
137 | |
138 | Env.setValue(Loc: S1.getSyntheticField(Name: "synth_int" ), |
139 | Val&: Env.create<IntegerValue>()); |
140 | |
141 | // Strategy: Create two equal records, then verify each of the various |
142 | // ways in which records can differ causes recordsEqual to return false. |
143 | // changes we can make to the record. |
144 | |
145 | // This test reuses the same objects for multiple checks, which isn't |
146 | // great, but seems better than duplicating the setup code for every |
147 | // check. |
148 | |
149 | copyRecord(Src&: S1, Dst&: S2, Env); |
150 | EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
151 | |
152 | // S2 has a different outer_int. |
153 | Env.setValue(Loc: *S2.getChild(D: *OuterIntDecl), Val&: Env.create<IntegerValue>()); |
154 | EXPECT_FALSE(recordsEqual(S1, S2, Env)); |
155 | copyRecord(Src&: S1, Dst&: S2, Env); |
156 | EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
157 | |
158 | // S2 doesn't have outer_int at all. |
159 | Env.clearValue(Loc: *S2.getChild(D: *OuterIntDecl)); |
160 | EXPECT_FALSE(recordsEqual(S1, S2, Env)); |
161 | copyRecord(Src&: S1, Dst&: S2, Env); |
162 | EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
163 | |
164 | // S2 has a different ref. |
165 | S2.setChild(D: *RefDecl, Loc: &Env.createStorageLocation( |
166 | Type: RefDecl->getType().getNonReferenceType())); |
167 | EXPECT_FALSE(recordsEqual(S1, S2, Env)); |
168 | copyRecord(Src&: S1, Dst&: S2, Env); |
169 | EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
170 | |
171 | // S2 as a different inner_int. |
172 | Env.setValue(Loc: *Inner2.getChild(D: *InnerIntDecl), |
173 | Val&: Env.create<IntegerValue>()); |
174 | EXPECT_FALSE(recordsEqual(S1, S2, Env)); |
175 | copyRecord(Src&: S1, Dst&: S2, Env); |
176 | EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
177 | |
178 | // S2 has a different synth_int. |
179 | Env.setValue(Loc: S2.getSyntheticField(Name: "synth_int" ), |
180 | Val&: Env.create<IntegerValue>()); |
181 | EXPECT_FALSE(recordsEqual(S1, S2, Env)); |
182 | copyRecord(Src&: S1, Dst&: S2, Env); |
183 | EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
184 | |
185 | // S2 doesn't have a value for synth_int. |
186 | Env.clearValue(Loc: S2.getSyntheticField(Name: "synth_int" )); |
187 | EXPECT_FALSE(recordsEqual(S1, S2, Env)); |
188 | copyRecord(Src&: S1, Dst&: S2, Env); |
189 | EXPECT_TRUE(recordsEqual(S1, S2, Env)); |
190 | }); |
191 | } |
192 | |
193 | TEST(TransferTest, CopyRecordBetweenDerivedAndBase) { |
194 | std::string Code = R"( |
195 | struct A { |
196 | int i; |
197 | }; |
198 | |
199 | struct B : public A { |
200 | }; |
201 | |
202 | void target(A a, B b) { |
203 | (void)a.i; |
204 | // [[p]] |
205 | } |
206 | )" ; |
207 | auto SyntheticFieldCallback = [](QualType Ty) -> llvm::StringMap<QualType> { |
208 | CXXRecordDecl *ADecl = nullptr; |
209 | if (Ty.getAsString() == "A" ) |
210 | ADecl = Ty->getAsCXXRecordDecl(); |
211 | else if (Ty.getAsString() == "B" ) |
212 | ADecl = Ty->getAsCXXRecordDecl() |
213 | ->bases_begin() |
214 | ->getType() |
215 | ->getAsCXXRecordDecl(); |
216 | else |
217 | return {}; |
218 | QualType IntTy = getFieldNamed(ADecl, "i" )->getType(); |
219 | return {{"synth_int" , IntTy}}; |
220 | }; |
221 | // Test copying derived to base class. |
222 | runDataflow( |
223 | Code, SyntheticFieldCallback, |
224 | VerifyResults: [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
225 | ASTContext &ASTCtx) { |
226 | Environment Env = getEnvironmentAtAnnotation(AnnotationStates: Results, Annotation: "p" ).fork(); |
227 | |
228 | const ValueDecl *IDecl = findValueDecl(ASTCtx, Name: "i" ); |
229 | auto &A = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, Name: "a" ); |
230 | auto &B = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, Name: "b" ); |
231 | |
232 | EXPECT_NE(Env.getValue(*A.getChild(*IDecl)), |
233 | Env.getValue(*B.getChild(*IDecl))); |
234 | EXPECT_NE(Env.getValue(A.getSyntheticField("synth_int" )), |
235 | Env.getValue(B.getSyntheticField("synth_int" ))); |
236 | |
237 | copyRecord(Src&: B, Dst&: A, Env); |
238 | |
239 | EXPECT_EQ(Env.getValue(*A.getChild(*IDecl)), |
240 | Env.getValue(*B.getChild(*IDecl))); |
241 | EXPECT_EQ(Env.getValue(A.getSyntheticField("synth_int" )), |
242 | Env.getValue(B.getSyntheticField("synth_int" ))); |
243 | }); |
244 | // Test copying base to derived class. |
245 | runDataflow( |
246 | Code, SyntheticFieldCallback, |
247 | VerifyResults: [](const llvm::StringMap<DataflowAnalysisState<NoopLattice>> &Results, |
248 | ASTContext &ASTCtx) { |
249 | Environment Env = getEnvironmentAtAnnotation(AnnotationStates: Results, Annotation: "p" ).fork(); |
250 | |
251 | const ValueDecl *IDecl = findValueDecl(ASTCtx, Name: "i" ); |
252 | auto &A = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, Name: "a" ); |
253 | auto &B = getLocForDecl<RecordStorageLocation>(ASTCtx, Env, Name: "b" ); |
254 | |
255 | EXPECT_NE(Env.getValue(*A.getChild(*IDecl)), |
256 | Env.getValue(*B.getChild(*IDecl))); |
257 | EXPECT_NE(Env.getValue(A.getSyntheticField("synth_int" )), |
258 | Env.getValue(B.getSyntheticField("synth_int" ))); |
259 | |
260 | copyRecord(Src&: A, Dst&: B, Env); |
261 | |
262 | EXPECT_EQ(Env.getValue(*A.getChild(*IDecl)), |
263 | Env.getValue(*B.getChild(*IDecl))); |
264 | EXPECT_EQ(Env.getValue(A.getSyntheticField("synth_int" )), |
265 | Env.getValue(B.getSyntheticField("synth_int" ))); |
266 | }); |
267 | } |
268 | |
269 | } // namespace |
270 | } // namespace test |
271 | } // namespace dataflow |
272 | } // namespace clang |
273 | |