1 | //===- unittests/AST/EvaluateAsRValueTest.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 | // \file |
10 | // \brief Unit tests for evaluation of constant initializers. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "clang/AST/ASTConsumer.h" |
15 | #include "clang/AST/ASTContext.h" |
16 | #include "clang/AST/RecursiveASTVisitor.h" |
17 | #include "clang/Tooling/Tooling.h" |
18 | #include "gtest/gtest.h" |
19 | #include <map> |
20 | #include <string> |
21 | |
22 | using namespace clang::tooling; |
23 | |
24 | namespace { |
25 | // For each variable name encountered, whether its initializer was a |
26 | // constant. |
27 | typedef std::map<std::string, bool> VarInfoMap; |
28 | |
29 | /// \brief Records information on variable initializers to a map. |
30 | class EvaluateConstantInitializersVisitor |
31 | : public clang::RecursiveASTVisitor<EvaluateConstantInitializersVisitor> { |
32 | public: |
33 | explicit EvaluateConstantInitializersVisitor(VarInfoMap &VarInfo) |
34 | : VarInfo(VarInfo) {} |
35 | |
36 | /// \brief Checks that isConstantInitializer and EvaluateAsRValue agree |
37 | /// and don't crash. |
38 | /// |
39 | /// For each VarDecl with an initializer this also records in VarInfo |
40 | /// whether the initializer could be evaluated as a constant. |
41 | bool VisitVarDecl(const clang::VarDecl *VD) { |
42 | if (const clang::Expr *Init = VD->getInit()) { |
43 | clang::Expr::EvalResult Result; |
44 | bool WasEvaluated = Init->EvaluateAsRValue(Result, Ctx: VD->getASTContext()); |
45 | VarInfo[VD->getNameAsString()] = WasEvaluated; |
46 | EXPECT_EQ(WasEvaluated, Init->isConstantInitializer(VD->getASTContext(), |
47 | false /*ForRef*/)); |
48 | } |
49 | return true; |
50 | } |
51 | |
52 | private: |
53 | VarInfoMap &VarInfo; |
54 | }; |
55 | |
56 | class EvaluateConstantInitializersAction : public clang::ASTFrontendAction { |
57 | public: |
58 | std::unique_ptr<clang::ASTConsumer> |
59 | CreateASTConsumer(clang::CompilerInstance &Compiler, |
60 | llvm::StringRef FilePath) override { |
61 | return std::make_unique<Consumer>(); |
62 | } |
63 | |
64 | private: |
65 | class Consumer : public clang::ASTConsumer { |
66 | public: |
67 | ~Consumer() override {} |
68 | |
69 | void HandleTranslationUnit(clang::ASTContext &Ctx) override { |
70 | VarInfoMap VarInfo; |
71 | EvaluateConstantInitializersVisitor Evaluator(VarInfo); |
72 | Evaluator.TraverseDecl(Ctx.getTranslationUnitDecl()); |
73 | EXPECT_EQ(2u, VarInfo.size()); |
74 | EXPECT_FALSE(VarInfo["Dependent" ]); |
75 | EXPECT_TRUE(VarInfo["Constant" ]); |
76 | EXPECT_EQ(2u, VarInfo.size()); |
77 | } |
78 | }; |
79 | }; |
80 | } |
81 | |
82 | TEST(EvaluateAsRValue, FailsGracefullyForUnknownTypes) { |
83 | // This is a regression test; the AST library used to trigger assertion |
84 | // failures because it assumed that the type of initializers was always |
85 | // known (which is true only after template instantiation). |
86 | std::string ModesToTest[] = {"-std=c++03" , "-std=c++11" , "-std=c++1y" }; |
87 | for (std::string const &Mode : ModesToTest) { |
88 | std::vector<std::string> Args(1, Mode); |
89 | Args.push_back(x: "-fno-delayed-template-parsing" ); |
90 | ASSERT_TRUE(runToolOnCodeWithArgs( |
91 | std::make_unique<EvaluateConstantInitializersAction>(), |
92 | "template <typename T>" |
93 | "struct vector {" |
94 | " explicit vector(int size);" |
95 | "};" |
96 | "template <typename R>" |
97 | "struct S {" |
98 | " vector<R> intervals() const {" |
99 | " vector<R> Dependent(2);" |
100 | " return Dependent;" |
101 | " }" |
102 | "};" |
103 | "void doSomething() {" |
104 | " int Constant = 2 + 2;" |
105 | " (void) Constant;" |
106 | "}" , |
107 | Args)); |
108 | } |
109 | } |
110 | |
111 | class CheckLValueToRValueConversionVisitor |
112 | : public clang::RecursiveASTVisitor<CheckLValueToRValueConversionVisitor> { |
113 | public: |
114 | bool VisitDeclRefExpr(const clang::DeclRefExpr *E) { |
115 | clang::Expr::EvalResult Result; |
116 | E->EvaluateAsRValue(Result, Ctx: E->getDecl()->getASTContext(), InConstantContext: true); |
117 | |
118 | EXPECT_TRUE(Result.Val.hasValue()); |
119 | // Since EvaluateAsRValue does an implicit lvalue-to-rvalue conversion, |
120 | // the result cannot be a LValue. |
121 | EXPECT_FALSE(Result.Val.isLValue()); |
122 | |
123 | return true; |
124 | } |
125 | }; |
126 | |
127 | class CheckConversionAction : public clang::ASTFrontendAction { |
128 | public: |
129 | std::unique_ptr<clang::ASTConsumer> |
130 | CreateASTConsumer(clang::CompilerInstance &Compiler, |
131 | llvm::StringRef FilePath) override { |
132 | return std::make_unique<Consumer>(); |
133 | } |
134 | |
135 | private: |
136 | class Consumer : public clang::ASTConsumer { |
137 | public: |
138 | ~Consumer() override {} |
139 | |
140 | void HandleTranslationUnit(clang::ASTContext &Ctx) override { |
141 | CheckLValueToRValueConversionVisitor Evaluator; |
142 | Evaluator.TraverseDecl(Ctx.getTranslationUnitDecl()); |
143 | } |
144 | }; |
145 | }; |
146 | |
147 | TEST(EvaluateAsRValue, LValueToRValueConversionWorks) { |
148 | std::string ModesToTest[] = {"" , "-fexperimental-new-constant-interpreter" }; |
149 | for (std::string const &Mode : ModesToTest) { |
150 | std::vector<std::string> Args(1, Mode); |
151 | ASSERT_TRUE(runToolOnCodeWithArgs(std::make_unique<CheckConversionAction>(), |
152 | "constexpr int a = 20;\n" |
153 | "static_assert(a == 20, \"\");\n" , |
154 | Args)); |
155 | } |
156 | } |
157 | |