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 | |
26 | namespace { |
27 | |
28 | using namespace clang; |
29 | using namespace dataflow; |
30 | using ::clang::dataflow::test::findValueDecl; |
31 | using ::clang::dataflow::test::getFieldValue; |
32 | using ::testing::Contains; |
33 | using ::testing::IsNull; |
34 | using ::testing::NotNull; |
35 | |
36 | class EnvironmentTest : public ::testing::Test { |
37 | protected: |
38 | EnvironmentTest() : DAContext(std::make_unique<WatchedLiteralsSolver>()) {} |
39 | |
40 | DataflowAnalysisContext DAContext; |
41 | }; |
42 | |
43 | TEST_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 | |
65 | TEST_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 | |
111 | TEST_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 | |
156 | TEST_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 | |
198 | TEST_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. |
229 | TEST_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 | |
279 | TEST_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 | |
325 | TEST_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. |
361 | TEST_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. |
410 | TEST_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. |
453 | TEST_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 | |
494 | TEST_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. |
526 | TEST_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 | |