1 | //===- unittests/Analysis/FlowSensitive/ASTOpsTest.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/ASTOps.h" |
10 | #include "TestingSupport.h" |
11 | #include "clang/AST/Decl.h" |
12 | #include "clang/AST/DeclCXX.h" |
13 | #include "clang/ASTMatchers/ASTMatchers.h" |
14 | #include "clang/Basic/LLVM.h" |
15 | #include "clang/Frontend/ASTUnit.h" |
16 | #include "clang/Tooling/Tooling.h" |
17 | #include "gmock/gmock.h" |
18 | #include "gtest/gtest.h" |
19 | #include <memory> |
20 | #include <string> |
21 | |
22 | namespace { |
23 | |
24 | using namespace clang; |
25 | using namespace dataflow; |
26 | |
27 | using ast_matchers::cxxMethodDecl; |
28 | using ast_matchers::cxxRecordDecl; |
29 | using ast_matchers::hasName; |
30 | using ast_matchers::hasType; |
31 | using ast_matchers::initListExpr; |
32 | using ast_matchers::match; |
33 | using ast_matchers::selectFirst; |
34 | using test::findValueDecl; |
35 | using testing::IsEmpty; |
36 | using testing::UnorderedElementsAre; |
37 | |
38 | TEST(ASTOpsTest, RecordInitListHelperOnEmptyUnionInitList) { |
39 | // This is a regression test: The `RecordInitListHelper` used to assert-fail |
40 | // when called for the `InitListExpr` of an empty union. |
41 | std::string Code = R"cc( |
42 | struct S { |
43 | S() : UField{} {}; |
44 | |
45 | union U {} UField; |
46 | }; |
47 | )cc" ; |
48 | std::unique_ptr<ASTUnit> Unit = |
49 | tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only" , "-std=c++17" }); |
50 | auto &ASTCtx = Unit->getASTContext(); |
51 | |
52 | ASSERT_EQ(ASTCtx.getDiagnostics().getClient()->getNumErrors(), 0U); |
53 | |
54 | auto *InitList = selectFirst<InitListExpr>( |
55 | BoundTo: "init" , |
56 | Results: match(Matcher: initListExpr(hasType(InnerMatcher: cxxRecordDecl(hasName(Name: "U" )))).bind(ID: "init" ), |
57 | Context&: ASTCtx)); |
58 | ASSERT_NE(InitList, nullptr); |
59 | |
60 | RecordInitListHelper Helper(InitList); |
61 | EXPECT_THAT(Helper.base_inits(), IsEmpty()); |
62 | EXPECT_THAT(Helper.field_inits(), IsEmpty()); |
63 | } |
64 | |
65 | TEST(ASTOpsTest, ReferencedDeclsOnUnionInitList) { |
66 | // This is a regression test: `getReferencedDecls()` used to return a null |
67 | // `FieldDecl` in this case (in addition to the correct non-null `FieldDecl`) |
68 | // because `getInitializedFieldInUnion()` returns null for the syntactic form |
69 | // of the `InitListExpr`. |
70 | std::string Code = R"cc( |
71 | struct S { |
72 | S() : UField{0} {}; |
73 | |
74 | union U { |
75 | int I; |
76 | } UField; |
77 | }; |
78 | )cc" ; |
79 | std::unique_ptr<ASTUnit> Unit = |
80 | tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only" , "-std=c++17" }); |
81 | auto &ASTCtx = Unit->getASTContext(); |
82 | |
83 | ASSERT_EQ(ASTCtx.getDiagnostics().getClient()->getNumErrors(), 0U); |
84 | |
85 | auto *InitList = selectFirst<InitListExpr>( |
86 | BoundTo: "init" , |
87 | Results: match(Matcher: initListExpr(hasType(InnerMatcher: cxxRecordDecl(hasName(Name: "U" )))).bind(ID: "init" ), |
88 | Context&: ASTCtx)); |
89 | ASSERT_NE(InitList, nullptr); |
90 | auto *IDecl = cast<FieldDecl>(Val: findValueDecl(ASTCtx, Name: "I" )); |
91 | |
92 | EXPECT_THAT(getReferencedDecls(*InitList).Fields, |
93 | UnorderedElementsAre(IDecl)); |
94 | } |
95 | |
96 | TEST(ASTOpsTest, ReferencedDeclsLocalsNotParamsOrStatics) { |
97 | std::string Code = R"cc( |
98 | void func(int Param) { |
99 | static int Static = 0; |
100 | int Local = Param; |
101 | Local = Static; |
102 | } |
103 | )cc" ; |
104 | std::unique_ptr<ASTUnit> Unit = |
105 | tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only" , "-std=c++17" }); |
106 | auto &ASTCtx = Unit->getASTContext(); |
107 | |
108 | ASSERT_EQ(ASTCtx.getDiagnostics().getClient()->getNumErrors(), 0U); |
109 | |
110 | auto *Func = cast<FunctionDecl>(Val: findValueDecl(ASTCtx, Name: "func" )); |
111 | ASSERT_NE(Func, nullptr); |
112 | auto *LocalDecl = cast<VarDecl>(Val: findValueDecl(ASTCtx, Name: "Local" )); |
113 | |
114 | EXPECT_THAT(getReferencedDecls(*Func).Locals, |
115 | UnorderedElementsAre(LocalDecl)); |
116 | } |
117 | |
118 | TEST(ASTOpsTest, LambdaCaptures) { |
119 | std::string Code = R"cc( |
120 | void func(int CapturedByRef, int CapturedByValue, int NotCaptured) { |
121 | int Local; |
122 | auto Lambda = [&CapturedByRef, CapturedByValue, &Local](int LambdaParam) { |
123 | }; |
124 | } |
125 | )cc" ; |
126 | std::unique_ptr<ASTUnit> Unit = |
127 | tooling::buildASTFromCodeWithArgs(Code, Args: {"-fsyntax-only" , "-std=c++17" }); |
128 | auto &ASTCtx = Unit->getASTContext(); |
129 | |
130 | ASSERT_EQ(ASTCtx.getDiagnostics().getClient()->getNumErrors(), 0U); |
131 | |
132 | auto *LambdaCallOp = selectFirst<CXXMethodDecl>( |
133 | BoundTo: "l" , Results: match(Matcher: cxxMethodDecl(hasName(Name: "operator()" )).bind(ID: "l" ), Context&: ASTCtx)); |
134 | ASSERT_NE(LambdaCallOp, nullptr); |
135 | auto *Func = cast<FunctionDecl>(Val: findValueDecl(ASTCtx, Name: "func" )); |
136 | ASSERT_NE(Func, nullptr); |
137 | auto *CapturedByRefDecl = Func->getParamDecl(i: 0); |
138 | ASSERT_NE(CapturedByRefDecl, nullptr); |
139 | auto *CapturedByValueDecl = Func->getParamDecl(i: 1); |
140 | ASSERT_NE(CapturedByValueDecl, nullptr); |
141 | |
142 | EXPECT_THAT(getReferencedDecls(*Func).LambdaCapturedParams, IsEmpty()); |
143 | ReferencedDecls ForLambda = getReferencedDecls(*LambdaCallOp); |
144 | EXPECT_THAT(ForLambda.LambdaCapturedParams, |
145 | UnorderedElementsAre(CapturedByRefDecl, CapturedByValueDecl)); |
146 | // Captured locals must be seen in the body for them to appear in |
147 | // ReferencedDecls. |
148 | EXPECT_THAT(ForLambda.Locals, IsEmpty()); |
149 | } |
150 | |
151 | } // namespace |
152 | |