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/AST/ExprCXX.h"
13#include "clang/AST/Stmt.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
15#include "clang/ASTMatchers/ASTMatchers.h"
16#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
17#include "clang/Analysis/FlowSensitive/StorageLocation.h"
18#include "clang/Analysis/FlowSensitive/Value.h"
19#include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h"
20#include "clang/Tooling/Tooling.h"
21#include "gmock/gmock.h"
22#include "gtest/gtest.h"
23#include <memory>
24#include <string>
25
26namespace {
27
28using namespace clang;
29using namespace dataflow;
30using ::clang::dataflow::test::findValueDecl;
31using ::clang::dataflow::test::getFieldValue;
32using ::testing::Contains;
33using ::testing::IsNull;
34using ::testing::NotNull;
35
36class EnvironmentTest : public ::testing::Test {
37protected:
38 EnvironmentTest() : DAContext(std::make_unique<WatchedLiteralsSolver>()) {}
39
40 DataflowAnalysisContext DAContext;
41};
42
43TEST_F(EnvironmentTest, FlowCondition) {
44 Environment Env(DAContext);
45 auto &A = Env.arena();
46
47 EXPECT_TRUE(Env.proves(A.makeLiteral(true)));
48 EXPECT_TRUE(Env.allows(A.makeLiteral(true)));
49 EXPECT_FALSE(Env.proves(A.makeLiteral(false)));
50 EXPECT_FALSE(Env.allows(A.makeLiteral(false)));
51
52 auto &X = A.makeAtomRef(A: A.makeAtom());
53 EXPECT_FALSE(Env.proves(X));
54 EXPECT_TRUE(Env.allows(X));
55
56 Env.assume(X);
57 EXPECT_TRUE(Env.proves(X));
58 EXPECT_TRUE(Env.allows(X));
59
60 auto &NotX = A.makeNot(Val: X);
61 EXPECT_FALSE(Env.proves(NotX));
62 EXPECT_FALSE(Env.allows(NotX));
63}
64
65TEST_F(EnvironmentTest, SetAndGetValueOnCfgOmittedNodes) {
66 // Check that we can set a value on an expression that is omitted from the CFG
67 // (see `ignoreCFGOmittedNodes()`), then retrieve that same value from the
68 // expression. This is a regression test; `setValue()` and `getValue()`
69 // previously did not use `ignoreCFGOmittedNodes()` consistently.
70
71 using namespace ast_matchers;
72
73 std::string Code = R"cc(
74 struct S {
75 int f();
76 };
77 void target() {
78 // Method call on a temporary produces an `ExprWithCleanups`.
79 S().f();
80 (1);
81 }
82 )cc";
83
84 auto Unit =
85 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++17"});
86 auto &Context = Unit->getASTContext();
87
88 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
89
90 const ExprWithCleanups *WithCleanups = selectFirst<ExprWithCleanups>(
91 BoundTo: "cleanups",
92 Results: match(Matcher: exprWithCleanups(hasType(InnerMatcher: isInteger())).bind(ID: "cleanups"), Context));
93 ASSERT_NE(WithCleanups, nullptr);
94
95 const ParenExpr *Paren = selectFirst<ParenExpr>(
96 BoundTo: "paren", Results: match(Matcher: parenExpr(hasType(InnerMatcher: isInteger())).bind(ID: "paren"), Context));
97 ASSERT_NE(Paren, nullptr);
98
99 Environment Env(DAContext);
100 IntegerValue *Val1 =
101 cast<IntegerValue>(Env.createValue(Type: Unit->getASTContext().IntTy));
102 Env.setValue(*WithCleanups, *Val1);
103 EXPECT_EQ(Env.getValue(*WithCleanups), Val1);
104
105 IntegerValue *Val2 =
106 cast<IntegerValue>(Env.createValue(Type: Unit->getASTContext().IntTy));
107 Env.setValue(*Paren, *Val2);
108 EXPECT_EQ(Env.getValue(*Paren), Val2);
109}
110
111TEST_F(EnvironmentTest, CreateValueRecursiveType) {
112 using namespace ast_matchers;
113
114 std::string Code = R"cc(
115 struct Recursive {
116 bool X;
117 Recursive *R;
118 };
119 // Use both fields to force them to be created with `createValue`.
120 void Usage(Recursive R) { (void)R.X; (void)R.R; }
121 )cc";
122
123 auto Unit =
124 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
125 auto &Context = Unit->getASTContext();
126
127 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
128
129 auto Results =
130 match(Matcher: qualType(hasDeclaration(InnerMatcher: recordDecl(
131 hasName(Name: "Recursive"),
132 has(fieldDecl(hasName(Name: "R")).bind(ID: "field-r")))))
133 .bind(ID: "target"),
134 Context);
135 const QualType *TyPtr = selectFirst<QualType>(BoundTo: "target", Results);
136 ASSERT_THAT(TyPtr, NotNull());
137 QualType Ty = *TyPtr;
138 ASSERT_FALSE(Ty.isNull());
139
140 const FieldDecl *R = selectFirst<FieldDecl>(BoundTo: "field-r", Results);
141 ASSERT_THAT(R, NotNull());
142
143 Results = match(Matcher: functionDecl(hasName(Name: "Usage")).bind(ID: "fun"), Context);
144 const auto *Fun = selectFirst<FunctionDecl>(BoundTo: "fun", Results);
145 ASSERT_THAT(Fun, NotNull());
146
147 // Verify that the struct and the field (`R`) with first appearance of the
148 // type is created successfully.
149 Environment Env(DAContext, *Fun);
150 Env.initialize();
151 auto &SLoc = cast<RecordStorageLocation>(Val&: Env.createObject(Ty));
152 PointerValue *PV = cast_or_null<PointerValue>(Val: getFieldValue(&SLoc, *R, Env));
153 EXPECT_THAT(PV, NotNull());
154}
155
156TEST_F(EnvironmentTest, DifferentReferenceLocInJoin) {
157 // This tests the case where the storage location for a reference-type
158 // variable is different for two states being joined. We used to believe this
159 // could not happen and therefore had an assertion disallowing this; this test
160 // exists to demonstrate that we can handle this condition without a failing
161 // assertion. See also the discussion here:
162 // https://discourse.llvm.org/t/70086/6
163
164 using namespace ast_matchers;
165
166 std::string Code = R"cc(
167 void f(int &ref) {}
168 )cc";
169
170 auto Unit =
171 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
172 auto &Context = Unit->getASTContext();
173
174 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
175
176 const ValueDecl *Ref = findValueDecl(ASTCtx&: Context, Name: "ref");
177
178 Environment Env1(DAContext);
179 StorageLocation &Loc1 = Env1.createStorageLocation(Context.IntTy);
180 Env1.setStorageLocation(D: *Ref, Loc&: Loc1);
181
182 Environment Env2(DAContext);
183 StorageLocation &Loc2 = Env2.createStorageLocation(Context.IntTy);
184 Env2.setStorageLocation(D: *Ref, Loc&: Loc2);
185
186 EXPECT_NE(&Loc1, &Loc2);
187
188 Environment::ValueModel Model;
189 Environment EnvJoined =
190 Environment::join(EnvA: Env1, EnvB: Env2, Model, ExprBehavior: Environment::DiscardExprState);
191
192 // Joining environments with different storage locations for the same
193 // declaration results in the declaration being removed from the joined
194 // environment.
195 EXPECT_EQ(EnvJoined.getStorageLocation(*Ref), nullptr);
196}
197
198TEST_F(EnvironmentTest, InitGlobalVarsFun) {
199 using namespace ast_matchers;
200
201 std::string Code = R"cc(
202 int Global = 0;
203 int Target () { return Global; }
204 )cc";
205
206 auto Unit =
207 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
208 auto &Context = Unit->getASTContext();
209
210 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
211
212 auto Results =
213 match(Matcher: decl(anyOf(varDecl(hasName(Name: "Global")).bind(ID: "global"),
214 functionDecl(hasName(Name: "Target")).bind(ID: "target"))),
215 Context);
216 const auto *Fun = selectFirst<FunctionDecl>(BoundTo: "target", Results);
217 const auto *Var = selectFirst<VarDecl>(BoundTo: "global", Results);
218 ASSERT_THAT(Fun, NotNull());
219 ASSERT_THAT(Var, NotNull());
220
221 // Verify the global variable is populated when we analyze `Target`.
222 Environment Env(DAContext, *Fun);
223 Env.initialize();
224 EXPECT_THAT(Env.getValue(*Var), NotNull());
225}
226
227// Tests that fields mentioned only in default member initializers are included
228// in the set of tracked fields.
229TEST_F(EnvironmentTest, IncludeFieldsFromDefaultInitializers) {
230 using namespace ast_matchers;
231
232 std::string Code = R"cc(
233 struct S {
234 S() {}
235 int X = 3;
236 int Y = X;
237 };
238 S foo();
239 )cc";
240
241 auto Unit =
242 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
243 auto &Context = Unit->getASTContext();
244
245 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
246
247 auto Results = match(
248 Matcher: qualType(hasDeclaration(
249 InnerMatcher: cxxRecordDecl(hasName(Name: "S"),
250 hasMethod(InnerMatcher: cxxConstructorDecl().bind(ID: "target")))
251 .bind(ID: "struct")))
252 .bind(ID: "ty"),
253 Context);
254 const auto *Constructor = selectFirst<FunctionDecl>(BoundTo: "target", Results);
255 const auto *Rec = selectFirst<RecordDecl>(BoundTo: "struct", Results);
256 const auto QTy = *selectFirst<QualType>(BoundTo: "ty", Results);
257 ASSERT_THAT(Constructor, NotNull());
258 ASSERT_THAT(Rec, NotNull());
259 ASSERT_FALSE(QTy.isNull());
260
261 auto Fields = Rec->fields();
262 FieldDecl *XDecl = nullptr;
263 for (FieldDecl *Field : Fields) {
264 if (Field->getNameAsString() == "X") {
265 XDecl = Field;
266 break;
267 }
268 }
269 ASSERT_THAT(XDecl, NotNull());
270
271 // Verify that the `X` field of `S` is populated when analyzing the
272 // constructor, even though it is not referenced directly in the constructor.
273 Environment Env(DAContext, *Constructor);
274 Env.initialize();
275 auto &Loc = cast<RecordStorageLocation>(Val&: Env.createObject(Ty: QTy));
276 EXPECT_THAT(getFieldValue(&Loc, *XDecl, Env), NotNull());
277}
278
279TEST_F(EnvironmentTest, InitGlobalVarsFieldFun) {
280 using namespace ast_matchers;
281
282 std::string Code = R"cc(
283 struct S { int Bar; };
284 S Global = {0};
285 int Target () { return Global.Bar; }
286 )cc";
287
288 auto Unit =
289 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
290 auto &Context = Unit->getASTContext();
291
292 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
293
294 auto Results =
295 match(Matcher: decl(anyOf(varDecl(hasName(Name: "Global")).bind(ID: "global"),
296 functionDecl(hasName(Name: "Target")).bind(ID: "target"))),
297 Context);
298 const auto *Fun = selectFirst<FunctionDecl>(BoundTo: "target", Results);
299 const auto *GlobalDecl = selectFirst<VarDecl>(BoundTo: "global", Results);
300 ASSERT_THAT(Fun, NotNull());
301 ASSERT_THAT(GlobalDecl, NotNull());
302
303 ASSERT_TRUE(GlobalDecl->getType()->isStructureType());
304 auto GlobalFields = GlobalDecl->getType()->getAsRecordDecl()->fields();
305
306 FieldDecl *BarDecl = nullptr;
307 for (FieldDecl *Field : GlobalFields) {
308 if (Field->getNameAsString() == "Bar") {
309 BarDecl = Field;
310 break;
311 }
312 FAIL() << "Unexpected field: " << Field->getNameAsString();
313 }
314 ASSERT_THAT(BarDecl, NotNull());
315
316 // Verify the global variable is populated when we analyze `Target`.
317 Environment Env(DAContext, *Fun);
318 Env.initialize();
319 const auto *GlobalLoc =
320 cast<RecordStorageLocation>(Val: Env.getStorageLocation(*GlobalDecl));
321 auto *BarVal = getFieldValue(GlobalLoc, *BarDecl, Env);
322 EXPECT_TRUE(isa<IntegerValue>(BarVal));
323}
324
325TEST_F(EnvironmentTest, InitGlobalVarsConstructor) {
326 using namespace ast_matchers;
327
328 std::string Code = R"cc(
329 int Global = 0;
330 struct Target {
331 Target() : Field(Global) {}
332 int Field;
333 };
334 )cc";
335
336 auto Unit =
337 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
338 auto &Context = Unit->getASTContext();
339
340 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
341
342 auto Results =
343 match(Matcher: decl(anyOf(
344 varDecl(hasName(Name: "Global")).bind(ID: "global"),
345 cxxConstructorDecl(ofClass(InnerMatcher: hasName(Name: "Target"))).bind(ID: "target"))),
346 Context);
347 const auto *Ctor = selectFirst<CXXConstructorDecl>(BoundTo: "target", Results);
348 const auto *Var = selectFirst<VarDecl>(BoundTo: "global", Results);
349 ASSERT_TRUE(Ctor != nullptr);
350 ASSERT_THAT(Var, NotNull());
351
352 // Verify the global variable is populated when we analyze `Target`.
353 Environment Env(DAContext, *Ctor);
354 Env.initialize();
355 EXPECT_THAT(Env.getValue(*Var), NotNull());
356}
357
358// Pointers to Members are a tricky case of accessor calls, complicated further
359// when using templates where the pointer to the member is a template argument.
360// This is a repro of a failure case seen in the wild.
361TEST_F(EnvironmentTest,
362 ModelMemberForAccessorUsingMethodPointerThroughTemplate) {
363 using namespace ast_matchers;
364
365 std::string Code = R"cc(
366 struct S {
367 int accessor() {return member;}
368
369 int member = 0;
370 };
371
372 template <auto method>
373 int Target(S* S) {
374 return (S->*method)();
375 }
376
377 // We want to analyze the instantiation of Target for the accessor.
378 int Instantiator () {S S; return Target<&S::accessor>(&S); }
379 )cc";
380
381 auto Unit =
382 // C++17 for the simplifying use of auto in the template declaration.
383 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++17"});
384 auto &Context = Unit->getASTContext();
385
386 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
387
388 auto Results = match(
389 Matcher: decl(anyOf(functionDecl(hasName(Name: "Target"), isTemplateInstantiation())
390 .bind(ID: "target"),
391 fieldDecl(hasName(Name: "member")).bind(ID: "member"),
392 recordDecl(hasName(Name: "S")).bind(ID: "struct"))),
393 Context);
394 const auto *Fun = selectFirst<FunctionDecl>(BoundTo: "target", Results);
395 const auto *Struct = selectFirst<RecordDecl>(BoundTo: "struct", Results);
396 const auto *Member = selectFirst<FieldDecl>(BoundTo: "member", Results);
397 ASSERT_THAT(Fun, NotNull());
398 ASSERT_THAT(Struct, NotNull());
399 ASSERT_THAT(Member, NotNull());
400
401 // Verify that `member` is modeled for `S` when we analyze
402 // `Target<&S::accessor>`.
403 Environment Env(DAContext, *Fun);
404 Env.initialize();
405 EXPECT_THAT(DAContext.getModeledFields(QualType(Struct->getTypeForDecl(), 0)),
406 Contains(Member));
407}
408
409// This is a repro of a failure case seen in the wild.
410TEST_F(EnvironmentTest, CXXDefaultInitExprResultObjIsWrappedExprResultObj) {
411 using namespace ast_matchers;
412
413 std::string Code = R"cc(
414 struct Inner {};
415
416 struct S {
417 S() {}
418
419 Inner i = {};
420 };
421 )cc";
422
423 auto Unit =
424 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
425 auto &Context = Unit->getASTContext();
426
427 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
428
429 auto Results =
430 match(Matcher: cxxConstructorDecl(
431 hasAnyConstructorInitializer(InnerMatcher: cxxCtorInitializer(
432 withInitializer(InnerMatcher: expr().bind(ID: "default_init_expr")))))
433 .bind(ID: "ctor"),
434 Context);
435 const auto *Constructor = selectFirst<CXXConstructorDecl>(BoundTo: "ctor", Results);
436 const auto *DefaultInit =
437 selectFirst<CXXDefaultInitExpr>(BoundTo: "default_init_expr", Results);
438
439 Environment Env(DAContext, *Constructor);
440 Env.initialize();
441 EXPECT_EQ(&Env.getResultObjectLocation(*DefaultInit),
442 &Env.getResultObjectLocation(*DefaultInit->getExpr()));
443}
444
445// This test verifies the behavior of `getResultObjectLocation()` in
446// scenarios involving inherited constructors.
447// Since the specific AST node of interest `CXXConstructorDecl` is implicitly
448// generated, we cannot annotate any statements inside of it as we do in tests
449// within TransferTest. Thus, the only way to get the right `Environment` is by
450// explicitly initializing it as we do in tests within EnvironmentTest.
451// This is why this test is not inside TransferTest, where most of the tests for
452// `getResultObjectLocation()` are located.
453TEST_F(EnvironmentTest, ResultObjectLocationForInheritedCtorInitExpr) {
454 using namespace ast_matchers;
455
456 std::string Code = R"(
457 struct Base {
458 Base(int b) {}
459 };
460 struct Derived : Base {
461 using Base::Base;
462 };
463
464 Derived d = Derived(0);
465 )";
466
467 auto Unit =
468 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++20"});
469 auto &Context = Unit->getASTContext();
470
471 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
472
473 auto Results =
474 match(Matcher: cxxConstructorDecl(
475 hasAnyConstructorInitializer(InnerMatcher: cxxCtorInitializer(
476 withInitializer(InnerMatcher: expr().bind(ID: "inherited_ctor_init_expr")))))
477 .bind(ID: "ctor"),
478 Context);
479 const auto *Constructor = selectFirst<CXXConstructorDecl>(BoundTo: "ctor", Results);
480 const auto *InheritedCtorInit = selectFirst<CXXInheritedCtorInitExpr>(
481 BoundTo: "inherited_ctor_init_expr", Results);
482
483 EXPECT_EQ(InheritedCtorInit->child_begin(), InheritedCtorInit->child_end());
484
485 Environment Env(DAContext, *Constructor);
486 Env.initialize();
487
488 RecordStorageLocation &Loc = Env.getResultObjectLocation(*InheritedCtorInit);
489 EXPECT_NE(&Loc, nullptr);
490
491 EXPECT_EQ(&Loc, Env.getThisPointeeStorageLocation());
492}
493
494TEST_F(EnvironmentTest, Stmt) {
495 using namespace ast_matchers;
496
497 std::string Code = R"cc(
498 struct S { int i; };
499 void foo() {
500 S AnS = S{1};
501 }
502 )cc";
503 auto Unit =
504 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
505 auto &Context = Unit->getASTContext();
506
507 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
508
509 auto *DeclStatement = const_cast<DeclStmt *>(selectFirst<DeclStmt>(
510 BoundTo: "d", Results: match(Matcher: declStmt(hasSingleDecl(InnerMatcher: varDecl(hasName(Name: "AnS")))).bind(ID: "d"),
511 Context)));
512 ASSERT_THAT(DeclStatement, NotNull());
513 auto *Init = (cast<VarDecl>(Val: *DeclStatement->decl_begin()))->getInit();
514 ASSERT_THAT(Init, NotNull());
515
516 // Verify that we can retrieve the result object location for the initializer
517 // expression when we analyze the DeclStmt for `AnS`.
518 Environment Env(DAContext, *DeclStatement);
519 // Don't crash when initializing.
520 Env.initialize();
521 // And don't crash when retrieving the result object location.
522 Env.getResultObjectLocation(RecordPRValue: *Init);
523}
524
525// This is a crash repro.
526TEST_F(EnvironmentTest, LambdaCapturingThisInFieldInitializer) {
527 using namespace ast_matchers;
528 std::string Code = R"cc(
529 struct S {
530 int f{[this]() { return 1; }()};
531 };
532 )cc";
533
534 auto Unit =
535 tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only", "-std=c++11"});
536 auto &Context = Unit->getASTContext();
537
538 ASSERT_EQ(Context.getDiagnostics().getClient()->getNumErrors(), 0U);
539
540 auto *LambdaCallOperator = selectFirst<CXXMethodDecl>(
541 BoundTo: "method", Results: match(Matcher: cxxMethodDecl(hasName(Name: "operator()"),
542 ofClass(InnerMatcher: cxxRecordDecl(isLambda())))
543 .bind(ID: "method"),
544 Context));
545
546 Environment Env(DAContext, *LambdaCallOperator);
547 // Don't crash when initializing.
548 Env.initialize();
549 // And initialize the captured `this` pointee.
550 ASSERT_NE(nullptr, Env.getThisPointeeStorageLocation());
551}
552
553} // namespace
554

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

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