Clang Project

clang_source_code/unittests/Frontend/FrontendActionTest.cpp
1//===- unittests/Frontend/FrontendActionTest.cpp - FrontendAction tests ---===//
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/Frontend/FrontendAction.h"
10#include "clang/AST/ASTConsumer.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/AST/RecursiveASTVisitor.h"
13#include "clang/Frontend/CompilerInstance.h"
14#include "clang/Frontend/CompilerInvocation.h"
15#include "clang/Frontend/FrontendActions.h"
16#include "clang/Lex/Preprocessor.h"
17#include "clang/Lex/PreprocessorOptions.h"
18#include "clang/Sema/Sema.h"
19#include "clang/Serialization/InMemoryModuleCache.h"
20#include "llvm/ADT/Triple.h"
21#include "llvm/Support/MemoryBuffer.h"
22#include "llvm/Support/ToolOutputFile.h"
23#include "gtest/gtest.h"
24
25using namespace llvm;
26using namespace clang;
27
28namespace {
29
30class TestASTFrontendAction : public ASTFrontendAction {
31public:
32  TestASTFrontendAction(bool enableIncrementalProcessing = false,
33                        bool actOnEndOfTranslationUnit = false)
34    : EnableIncrementalProcessing(enableIncrementalProcessing),
35      ActOnEndOfTranslationUnit(actOnEndOfTranslationUnit) { }
36
37  bool EnableIncrementalProcessing;
38  bool ActOnEndOfTranslationUnit;
39  std::vector<std::stringdecl_names;
40
41  bool BeginSourceFileAction(CompilerInstance &ci) override {
42    if (EnableIncrementalProcessing)
43      ci.getPreprocessor().enableIncrementalProcessing();
44
45    return ASTFrontendAction::BeginSourceFileAction(ci);
46  }
47
48  std::unique_ptr<ASTConsumerCreateASTConsumer(CompilerInstance &CI,
49                                                 StringRef InFile) override {
50    return llvm::make_unique<Visitor>(CI, ActOnEndOfTranslationUnit,
51                                      decl_names);
52  }
53
54private:
55  class Visitor : public ASTConsumerpublic RecursiveASTVisitor<Visitor> {
56  public:
57    Visitor(CompilerInstance &CIbool ActOnEndOfTranslationUnit,
58            std::vector<std::string> &decl_names) :
59      CI(CI), ActOnEndOfTranslationUnit(ActOnEndOfTranslationUnit),
60      decl_names_(decl_names) {}
61
62    void HandleTranslationUnit(ASTContext &context) override {
63      if (ActOnEndOfTranslationUnit) {
64        CI.getSema().ActOnEndOfTranslationUnit();
65      }
66      TraverseDecl(context.getTranslationUnitDecl());
67    }
68
69    virtual bool VisitNamedDecl(NamedDecl *Decl) {
70      decl_names_.push_back(Decl->getQualifiedNameAsString());
71      return true;
72    }
73
74  private:
75    CompilerInstance &CI;
76    bool ActOnEndOfTranslationUnit;
77    std::vector<std::string> &decl_names_;
78  };
79};
80
81TEST(ASTFrontendAction, Sanity) {
82  auto invocation = std::make_shared<CompilerInvocation>();
83  invocation->getPreprocessorOpts().addRemappedFile(
84      "test.cc",
85      MemoryBuffer::getMemBuffer("int main() { float x; }").release());
86  invocation->getFrontendOpts().Inputs.push_back(
87      FrontendInputFile("test.cc"InputKind::CXX));
88  invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
89  invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
90  CompilerInstance compiler;
91  compiler.setInvocation(std::move(invocation));
92  compiler.createDiagnostics();
93
94  TestASTFrontendAction test_action;
95  ASSERT_TRUE(compiler.ExecuteAction(test_action));
96  ASSERT_EQ(2Utest_action.decl_names.size());
97  EXPECT_EQ("main"test_action.decl_names[0]);
98  EXPECT_EQ("x"test_action.decl_names[1]);
99}
100
101TEST(ASTFrontendAction, IncrementalParsing) {
102  auto invocation = std::make_shared<CompilerInvocation>();
103  invocation->getPreprocessorOpts().addRemappedFile(
104      "test.cc",
105      MemoryBuffer::getMemBuffer("int main() { float x; }").release());
106  invocation->getFrontendOpts().Inputs.push_back(
107      FrontendInputFile("test.cc"InputKind::CXX));
108  invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
109  invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
110  CompilerInstance compiler;
111  compiler.setInvocation(std::move(invocation));
112  compiler.createDiagnostics();
113
114  TestASTFrontendAction test_action(/*enableIncrementalProcessing=*/true);
115  ASSERT_TRUE(compiler.ExecuteAction(test_action));
116  ASSERT_EQ(2Utest_action.decl_names.size());
117  EXPECT_EQ("main"test_action.decl_names[0]);
118  EXPECT_EQ("x"test_action.decl_names[1]);
119}
120
121TEST(ASTFrontendAction, LateTemplateIncrementalParsing) {
122  auto invocation = std::make_shared<CompilerInvocation>();
123  invocation->getLangOpts()->CPlusPlus = true;
124  invocation->getLangOpts()->DelayedTemplateParsing = true;
125  invocation->getPreprocessorOpts().addRemappedFile(
126    "test.cc", MemoryBuffer::getMemBuffer(
127      "template<typename T> struct A { A(T); T data; };\n"
128      "template<typename T> struct B: public A<T> {\n"
129      "  B();\n"
130      "  B(B const& b): A<T>(b.data) {}\n"
131      "};\n"
132      "B<char> c() { return B<char>(); }\n").release());
133  invocation->getFrontendOpts().Inputs.push_back(
134      FrontendInputFile("test.cc"InputKind::CXX));
135  invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
136  invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
137  CompilerInstance compiler;
138  compiler.setInvocation(std::move(invocation));
139  compiler.createDiagnostics();
140
141  TestASTFrontendAction test_action(/*enableIncrementalProcessing=*/true,
142                                    /*actOnEndOfTranslationUnit=*/true);
143  ASSERT_TRUE(compiler.ExecuteAction(test_action));
144  ASSERT_EQ(13Utest_action.decl_names.size());
145  EXPECT_EQ("A"test_action.decl_names[0]);
146  EXPECT_EQ("c"test_action.decl_names[12]);
147}
148
149struct TestPPCallbacks : public PPCallbacks {
150  TestPPCallbacks() : SeenEnd(false) {}
151
152  void EndOfMainFile() override { SeenEnd = true; }
153
154  bool SeenEnd;
155};
156
157class TestPPCallbacksFrontendAction : public PreprocessorFrontendAction {
158  TestPPCallbacks *Callbacks;
159
160public:
161  TestPPCallbacksFrontendAction(TestPPCallbacks *C)
162      : Callbacks(C), SeenEnd(false) {}
163
164  void ExecuteAction() override {
165    Preprocessor &PP = getCompilerInstance().getPreprocessor();
166    PP.addPPCallbacks(std::unique_ptr<TestPPCallbacks>(Callbacks));
167    PP.EnterMainSourceFile();
168  }
169  void EndSourceFileAction() override { SeenEnd = Callbacks->SeenEnd; }
170
171  bool SeenEnd;
172};
173
174TEST(PreprocessorFrontendAction, EndSourceFile) {
175  auto Invocation = std::make_shared<CompilerInvocation>();
176  Invocation->getPreprocessorOpts().addRemappedFile(
177      "test.cc",
178      MemoryBuffer::getMemBuffer("int main() { float x; }").release());
179  Invocation->getFrontendOpts().Inputs.push_back(
180      FrontendInputFile("test.cc"InputKind::CXX));
181  Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
182  Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
183  CompilerInstance Compiler;
184  Compiler.setInvocation(std::move(Invocation));
185  Compiler.createDiagnostics();
186
187  TestPPCallbacks *Callbacks = new TestPPCallbacks;
188  TestPPCallbacksFrontendAction TestAction(Callbacks);
189  ASSERT_FALSE(Callbacks->SeenEnd);
190  ASSERT_FALSE(TestAction.SeenEnd);
191  ASSERT_TRUE(Compiler.ExecuteAction(TestAction));
192  // Check that EndOfMainFile was called before EndSourceFileAction.
193  ASSERT_TRUE(TestAction.SeenEnd);
194}
195
196class TypoExternalSemaSource : public ExternalSemaSource {
197  CompilerInstance &CI;
198
199public:
200  TypoExternalSemaSource(CompilerInstance &CI) : CI(CI) {}
201
202  TypoCorrection CorrectTypo(const DeclarationNameInfo &Typoint LookupKind,
203                             Scope *SCXXScopeSpec *SS,
204                             CorrectionCandidateCallback &CCC,
205                             DeclContext *MemberContextbool EnteringContext,
206                             const ObjCObjectPointerType *OPT) override {
207    // Generate a fake typo correction with one attached note.
208    ASTContext &Ctx = CI.getASTContext();
209    TypoCorrection TC(DeclarationName(&Ctx.Idents.get("moo")));
210    unsigned DiagID = Ctx.getDiagnostics().getCustomDiagID(
211        DiagnosticsEngine::Note"This is a note");
212    TC.addExtraDiagnostic(PartialDiagnostic(DiagIDCtx.getDiagAllocator()));
213    return TC;
214  }
215};
216
217struct TypoDiagnosticConsumer : public DiagnosticConsumer {
218  void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
219                        const Diagnostic &Info) override {
220    // Capture errors and notes. There should be one of each.
221    if (DiagLevel == DiagnosticsEngine::Error) {
222      assert(Error.empty());
223      Info.FormatDiagnostic(Error);
224    } else {
225      assert(Note.empty());
226      Info.FormatDiagnostic(Note);
227    }
228  }
229  SmallString<32Error;
230  SmallString<32Note;
231};
232
233TEST(ASTFrontendAction, ExternalSemaSource) {
234  auto Invocation = std::make_shared<CompilerInvocation>();
235  Invocation->getLangOpts()->CPlusPlus = true;
236  Invocation->getPreprocessorOpts().addRemappedFile(
237      "test.cc", MemoryBuffer::getMemBuffer("void fooo();\n"
238                                            "int main() { foo(); }")
239                     .release());
240  Invocation->getFrontendOpts().Inputs.push_back(
241      FrontendInputFile("test.cc"InputKind::CXX));
242  Invocation->getFrontendOpts().ProgramAction = frontend::ParseSyntaxOnly;
243  Invocation->getTargetOpts().Triple = "i386-unknown-linux-gnu";
244  CompilerInstance Compiler;
245  Compiler.setInvocation(std::move(Invocation));
246  auto *TDC = new TypoDiagnosticConsumer;
247  Compiler.createDiagnostics(TDC/*ShouldOwnClient=*/true);
248  Compiler.setExternalSemaSource(new TypoExternalSemaSource(Compiler));
249
250  SyntaxOnlyAction TestAction;
251  ASSERT_TRUE(Compiler.ExecuteAction(TestAction));
252  // There should be one error correcting to 'moo' and a note attached to it.
253  EXPECT_EQ("use of undeclared identifier 'foo'; did you mean 'moo'?",
254            TDC->Error.str().str());
255  EXPECT_EQ("This is a note"TDC->Note.str().str());
256}
257
258TEST(GeneratePCHFrontendAction, CacheGeneratedPCH) {
259  // Create a temporary file for writing out the PCH that will be cleaned up.
260  int PCHFD;
261  llvm::SmallString<128PCHFilename;
262  ASSERT_FALSE(
263      llvm::sys::fs::createTemporaryFile("test.h""pch", PCHFD, PCHFilename));
264  llvm::ToolOutputFile PCHFile(PCHFilename, PCHFD);
265
266  for (bool ShouldCache : {falsetrue}) {
267    auto Invocation = std::make_shared<CompilerInvocation>();
268    Invocation->getLangOpts()->CacheGeneratedPCH = ShouldCache;
269    Invocation->getPreprocessorOpts().addRemappedFile(
270        "test.h",
271        MemoryBuffer::getMemBuffer("int foo(void) { return 1; }\n").release());
272    Invocation->getFrontendOpts().Inputs.push_back(
273        FrontendInputFile("test.h"InputKind::C));
274    Invocation->getFrontendOpts().OutputFile = StringRef(PCHFilename);
275    Invocation->getFrontendOpts().ProgramAction = frontend::GeneratePCH;
276    Invocation->getTargetOpts().Triple = "x86_64-apple-darwin19.0.0";
277    CompilerInstance Compiler;
278    Compiler.setInvocation(std::move(Invocation));
279    Compiler.createDiagnostics();
280
281    GeneratePCHAction TestAction;
282    ASSERT_TRUE(Compiler.ExecuteAction(TestAction));
283
284    // Check whether the PCH was cached.
285    if (ShouldCache)
286      EXPECT_EQ(InMemoryModuleCache::Final,
287                Compiler.getModuleCache().getPCMState(PCHFilename));
288    else
289      EXPECT_EQ(InMemoryModuleCache::Unknown,
290                Compiler.getModuleCache().getPCMState(PCHFilename));
291  }
292}
293
294// anonymous namespace
295