1//===- unittests/Analysis/FlowSensitive/DataflowEnvironmentTest.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/DataflowEnvironment.h"
10#include "TestingSupport.h"
11#include "clang/AST/DeclCXX.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/ASTMatchers/ASTMatchers.h"
14#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
15#include "clang/Analysis/FlowSensitive/StorageLocation.h"
16#include "clang/Analysis/FlowSensitive/Value.h"
17#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
18#include "clang/Tooling/Tooling.h"
19#include "gmock/gmock.h"
20#include "gtest/gtest.h"
21#include <memory>
22
23namespace {
24
25using namespace clang;
26using namespace dataflow;
27using ::clang::dataflow::test::findValueDecl;
28using ::clang::dataflow::test::getFieldValue;
29using ::testing::Contains;
30using ::testing::IsNull;
31using ::testing::NotNull;
32
33class EnvironmentTest : public ::testing::Test {
34protected:
35 EnvironmentTest() : DAContext(std::make_unique<WatchedLiteralsSolver>()) {}
36
37 DataflowAnalysisContext DAContext;
38};
39
40TEST_F(EnvironmentTest, FlowCondition) {
41 Environment Env(DAContext);
42 auto &A = Env.arena();
43
44 EXPECT_TRUE(Env.proves(A.makeLiteral(true)));
45 EXPECT_TRUE(Env.allows(A.makeLiteral(true)));
46 EXPECT_FALSE(Env.proves(A.makeLiteral(false)));
47 EXPECT_FALSE(Env.allows(A.makeLiteral(false)));
48
49 auto &X = A.makeAtomRef(A: A.makeAtom());
50 EXPECT_FALSE(Env.proves(X));
51 EXPECT_TRUE(Env.allows(X));
52
53 Env.assume(X);
54 EXPECT_TRUE(Env.proves(X));
55 EXPECT_TRUE(Env.allows(X));
56
57 auto &NotX = A.makeNot(Val: X);
58 EXPECT_FALSE(Env.proves(NotX));
59 EXPECT_FALSE(Env.allows(NotX));
60}
61
62TEST_F(EnvironmentTest, SetAndGetValueOnCfgOmittedNodes) {
63 // Check that we can set a value on an expression that is omitted from the CFG
64 // (see `ignoreCFGOmittedNodes()`), then retrieve that same value from the
65 // expression. This is a regression test; `setValue()` and `getValue()`
66 // previously did not use `ignoreCFGOmittedNodes()` consistently.
67
68 using namespace ast_matchers;
69
70 std::string Code = R"cc(
71 struct S {
72 int f();
73 };
74 void target() {
75 // Method call on a temporary produces an `ExprWithCleanups`.
76 S().f();
77 (1);
78 }
79 )cc";
80
81 auto Unit =
82 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++17"});
83 auto &Context = Unit->getASTContext();
84
85 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
86
87 const ExprWithCleanups *WithCleanups = selectFirst<ExprWithCleanups>(
88 BoundTo: "cleanups",
89 Results: match(Matcher: exprWithCleanups(hasType(InnerMatcher: isInteger())).bind(ID: "cleanups"), Context));
90 ASSERT_NE(WithCleanups, nullptr);
91
92 const ParenExpr *Paren = selectFirst<ParenExpr>(
93 BoundTo: "paren", Results: match(Matcher: parenExpr(hasType(InnerMatcher: isInteger())).bind(ID: "paren"), Context));
94 ASSERT_NE(Paren, nullptr);
95
96 Environment Env(DAContext);
97 IntegerValue *Val1 =
98 cast<IntegerValue>(Env.createValue(Type: Unit->getASTContext().IntTy));
99 Env.setValue(*WithCleanups, *Val1);
100 EXPECT_EQ(Env.getValue(*WithCleanups), Val1);
101
102 IntegerValue *Val2 =
103 cast<IntegerValue>(Env.createValue(Type: Unit->getASTContext().IntTy));
104 Env.setValue(*Paren, *Val2);
105 EXPECT_EQ(Env.getValue(*Paren), Val2);
106}
107
108TEST_F(EnvironmentTest, CreateValueRecursiveType) {
109 using namespace ast_matchers;
110
111 std::string Code = R"cc(
112 struct Recursive {
113 bool X;
114 Recursive *R;
115 };
116 // Use both fields to force them to be created with `createValue`.
117 void Usage(Recursive R) { (void)R.X; (void)R.R; }
118 )cc";
119
120 auto Unit =
121 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
122 auto &Context = Unit->getASTContext();
123
124 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
125
126 auto Results =
127 match(Matcher: qualType(hasDeclaration(InnerMatcher: recordDecl(
128 hasName(Name: "Recursive"),
129 has(fieldDecl(hasName(Name: "R")).bind(ID: "field-r")))))
130 .bind(ID: "target"),
131 Context);
132 const QualType *TyPtr = selectFirst<QualType>(BoundTo: "target", Results);
133 ASSERT_THAT(TyPtr, NotNull());
134 QualType Ty = *TyPtr;
135 ASSERT_FALSE(Ty.isNull());
136
137 const FieldDecl *R = selectFirst<FieldDecl>(BoundTo: "field-r", Results);
138 ASSERT_THAT(R, NotNull());
139
140 Results = match(Matcher: functionDecl(hasName(Name: "Usage")).bind(ID: "fun"), Context);
141 const auto *Fun = selectFirst<FunctionDecl>(BoundTo: "fun", Results);
142 ASSERT_THAT(Fun, NotNull());
143
144 // Verify that the struct and the field (`R`) with first appearance of the
145 // type is created successfully.
146 Environment Env(DAContext, *Fun);
147 Env.initialize();
148 auto &SLoc = cast<RecordStorageLocation>(Val&: Env.createObject(Ty));
149 PointerValue *PV = cast_or_null<PointerValue>(getFieldValue(&SLoc, *R, Env));
150 EXPECT_THAT(PV, NotNull());
151}
152
153TEST_F(EnvironmentTest, DifferentReferenceLocInJoin) {
154 // This tests the case where the storage location for a reference-type
155 // variable is different for two states being joined. We used to believe this
156 // could not happen and therefore had an assertion disallowing this; this test
157 // exists to demonstrate that we can handle this condition without a failing
158 // assertion. See also the discussion here:
159 // https://discourse.llvm.org/t/70086/6
160
161 using namespace ast_matchers;
162
163 std::string Code = R"cc(
164 void f(int &ref) {}
165 )cc";
166
167 auto Unit =
168 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
169 auto &Context = Unit->getASTContext();
170
171 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
172
173 const ValueDecl *Ref = findValueDecl(ASTCtx&: Context, Name: "ref");
174
175 Environment Env1(DAContext);
176 StorageLocation &Loc1 = Env1.createStorageLocation(Context.IntTy);
177 Env1.setStorageLocation(D: *Ref, Loc&: Loc1);
178
179 Environment Env2(DAContext);
180 StorageLocation &Loc2 = Env2.createStorageLocation(Context.IntTy);
181 Env2.setStorageLocation(D: *Ref, Loc&: Loc2);
182
183 EXPECT_NE(&Loc1, &Loc2);
184
185 Environment::ValueModel Model;
186 Environment EnvJoined =
187 Environment::join(EnvA: Env1, EnvB: Env2, Model, ExprBehavior: Environment::DiscardExprState);
188
189 // Joining environments with different storage locations for the same
190 // declaration results in the declaration being removed from the joined
191 // environment.
192 EXPECT_EQ(EnvJoined.getStorageLocation(*Ref), nullptr);
193}
194
195TEST_F(EnvironmentTest, InitGlobalVarsFun) {
196 using namespace ast_matchers;
197
198 std::string Code = R"cc(
199 int Global = 0;
200 int Target () { return Global; }
201 )cc";
202
203 auto Unit =
204 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
205 auto &Context = Unit->getASTContext();
206
207 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
208
209 auto Results =
210 match(Matcher: decl(anyOf(varDecl(hasName(Name: "Global")).bind(ID: "global"),
211 functionDecl(hasName(Name: "Target")).bind(ID: "target"))),
212 Context);
213 const auto *Fun = selectFirst<FunctionDecl>(BoundTo: "target", Results);
214 const auto *Var = selectFirst<VarDecl>(BoundTo: "global", Results);
215 ASSERT_THAT(Fun, NotNull());
216 ASSERT_THAT(Var, NotNull());
217
218 // Verify the global variable is populated when we analyze `Target`.
219 Environment Env(DAContext, *Fun);
220 Env.initialize();
221 EXPECT_THAT(Env.getValue(*Var), NotNull());
222}
223
224// Tests that fields mentioned only in default member initializers are included
225// in the set of tracked fields.
226TEST_F(EnvironmentTest, IncludeFieldsFromDefaultInitializers) {
227 using namespace ast_matchers;
228
229 std::string Code = R"cc(
230 struct S {
231 S() {}
232 int X = 3;
233 int Y = X;
234 };
235 S foo();
236 )cc";
237
238 auto Unit =
239 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
240 auto &Context = Unit->getASTContext();
241
242 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
243
244 auto Results = match(
245 Matcher: qualType(hasDeclaration(
246 InnerMatcher: cxxRecordDecl(hasName(Name: "S"),
247 hasMethod(InnerMatcher: cxxConstructorDecl().bind(ID: "target")))
248 .bind(ID: "struct")))
249 .bind(ID: "ty"),
250 Context);
251 const auto *Constructor = selectFirst<FunctionDecl>(BoundTo: "target", Results);
252 const auto *Rec = selectFirst<RecordDecl>(BoundTo: "struct", Results);
253 const auto QTy = *selectFirst<QualType>(BoundTo: "ty", Results);
254 ASSERT_THAT(Constructor, NotNull());
255 ASSERT_THAT(Rec, NotNull());
256 ASSERT_FALSE(QTy.isNull());
257
258 auto Fields = Rec->fields();
259 FieldDecl *XDecl = nullptr;
260 for (FieldDecl *Field : Fields) {
261 if (Field->getNameAsString() == "X") {
262 XDecl = Field;
263 break;
264 }
265 }
266 ASSERT_THAT(XDecl, NotNull());
267
268 // Verify that the `X` field of `S` is populated when analyzing the
269 // constructor, even though it is not referenced directly in the constructor.
270 Environment Env(DAContext, *Constructor);
271 Env.initialize();
272 auto &Loc = cast<RecordStorageLocation>(Val&: Env.createObject(Ty: QTy));
273 EXPECT_THAT(getFieldValue(&Loc, *XDecl, Env), NotNull());
274}
275
276TEST_F(EnvironmentTest, InitGlobalVarsFieldFun) {
277 using namespace ast_matchers;
278
279 std::string Code = R"cc(
280 struct S { int Bar; };
281 S Global = {0};
282 int Target () { return Global.Bar; }
283 )cc";
284
285 auto Unit =
286 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
287 auto &Context = Unit->getASTContext();
288
289 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
290
291 auto Results =
292 match(Matcher: decl(anyOf(varDecl(hasName(Name: "Global")).bind(ID: "global"),
293 functionDecl(hasName(Name: "Target")).bind(ID: "target"))),
294 Context);
295 const auto *Fun = selectFirst<FunctionDecl>(BoundTo: "target", Results);
296 const auto *GlobalDecl = selectFirst<VarDecl>(BoundTo: "global", Results);
297 ASSERT_THAT(Fun, NotNull());
298 ASSERT_THAT(GlobalDecl, NotNull());
299
300 ASSERT_TRUE(GlobalDecl->getType()->isStructureType());
301 auto GlobalFields = GlobalDecl->getType()->getAsRecordDecl()->fields();
302
303 FieldDecl *BarDecl = nullptr;
304 for (FieldDecl *Field : GlobalFields) {
305 if (Field->getNameAsString() == "Bar") {
306 BarDecl = Field;
307 break;
308 }
309 FAIL() << "Unexpected field: " << Field->getNameAsString();
310 }
311 ASSERT_THAT(BarDecl, NotNull());
312
313 // Verify the global variable is populated when we analyze `Target`.
314 Environment Env(DAContext, *Fun);
315 Env.initialize();
316 const auto *GlobalLoc =
317 cast<RecordStorageLocation>(Val: Env.getStorageLocation(*GlobalDecl));
318 auto *BarVal = getFieldValue(GlobalLoc, *BarDecl, Env);
319 EXPECT_TRUE(isa<IntegerValue>(BarVal));
320}
321
322TEST_F(EnvironmentTest, InitGlobalVarsConstructor) {
323 using namespace ast_matchers;
324
325 std::string Code = R"cc(
326 int Global = 0;
327 struct Target {
328 Target() : Field(Global) {}
329 int Field;
330 };
331 )cc";
332
333 auto Unit =
334 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
335 auto &Context = Unit->getASTContext();
336
337 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
338
339 auto Results =
340 match(Matcher: decl(anyOf(
341 varDecl(hasName(Name: "Global")).bind(ID: "global"),
342 cxxConstructorDecl(ofClass(InnerMatcher: hasName(Name: "Target"))).bind(ID: "target"))),
343 Context);
344 const auto *Ctor = selectFirst<CXXConstructorDecl>(BoundTo: "target", Results);
345 const auto *Var = selectFirst<VarDecl>(BoundTo: "global", Results);
346 ASSERT_TRUE(Ctor != nullptr);
347 ASSERT_THAT(Var, NotNull());
348
349 // Verify the global variable is populated when we analyze `Target`.
350 Environment Env(DAContext, *Ctor);
351 Env.initialize();
352 EXPECT_THAT(Env.getValue(*Var), NotNull());
353}
354
355// Pointers to Members are a tricky case of accessor calls, complicated further
356// when using templates where the pointer to the member is a template argument.
357// This is a repro of a failure case seen in the wild.
358TEST_F(EnvironmentTest,
359 ModelMemberForAccessorUsingMethodPointerThroughTemplate) {
360 using namespace ast_matchers;
361
362 std::string Code = R"cc(
363 struct S {
364 int accessor() {return member;}
365
366 int member = 0;
367 };
368
369 template <auto method>
370 int Target(S* S) {
371 return (S->*method)();
372 }
373
374 // We want to analyze the instantiation of Target for the accessor.
375 int Instantiator () {S S; return Target<&S::accessor>(&S); }
376 )cc";
377
378 auto Unit =
379 // C++17 for the simplifying use of auto in the template declaration.
380 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++17"});
381 auto &Context = Unit->getASTContext();
382
383 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
384
385 auto Results = match(
386 Matcher: decl(anyOf(functionDecl(hasName(Name: "Target"), isTemplateInstantiation())
387 .bind(ID: "target"),
388 fieldDecl(hasName(Name: "member")).bind(ID: "member"),
389 recordDecl(hasName(Name: "S")).bind(ID: "struct"))),
390 Context);
391 const auto *Fun = selectFirst<FunctionDecl>(BoundTo: "target", Results);
392 const auto *Struct = selectFirst<RecordDecl>(BoundTo: "struct", Results);
393 const auto *Member = selectFirst<FieldDecl>(BoundTo: "member", Results);
394 ASSERT_THAT(Fun, NotNull());
395 ASSERT_THAT(Struct, NotNull());
396 ASSERT_THAT(Member, NotNull());
397
398 // Verify that `member` is modeled for `S` when we analyze
399 // `Target<&S::accessor>`.
400 Environment Env(DAContext, *Fun);
401 Env.initialize();
402 EXPECT_THAT(DAContext.getModeledFields(QualType(Struct->getTypeForDecl(), 0)),
403 Contains(Member));
404}
405
406} // namespace
407

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