xref: /llvm-project/clang/unittests/Lex/PPDependencyDirectivesTest.cpp (revision 0559eaff5ac4afae14dcbb6396da2a0c3f325cd8)
1 //===- unittests/Lex/PPDependencyDirectivesTest.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/Basic/Diagnostic.h"
10 #include "clang/Basic/DiagnosticOptions.h"
11 #include "clang/Basic/FileManager.h"
12 #include "clang/Basic/LangOptions.h"
13 #include "clang/Basic/SourceManager.h"
14 #include "clang/Basic/TargetInfo.h"
15 #include "clang/Basic/TargetOptions.h"
16 #include "clang/Lex/DependencyDirectivesScanner.h"
17 #include "clang/Lex/HeaderSearch.h"
18 #include "clang/Lex/HeaderSearchOptions.h"
19 #include "clang/Lex/ModuleLoader.h"
20 #include "clang/Lex/Preprocessor.h"
21 #include "clang/Lex/PreprocessorOptions.h"
22 #include "llvm/Testing/Support/Error.h"
23 #include "gtest/gtest.h"
24 #include <optional>
25 
26 using namespace clang;
27 
28 namespace {
29 
30 // The test fixture.
31 class PPDependencyDirectivesTest : public ::testing::Test {
32 protected:
PPDependencyDirectivesTest()33   PPDependencyDirectivesTest()
34       : FileMgr(FileMgrOpts), DiagID(new DiagnosticIDs()),
35         Diags(DiagID, new DiagnosticOptions, new IgnoringDiagConsumer()),
36         SourceMgr(Diags, FileMgr), TargetOpts(new TargetOptions) {
37     TargetOpts->Triple = "x86_64-apple-macos12";
38     Target = TargetInfo::CreateTargetInfo(Diags, TargetOpts);
39   }
40 
41   FileSystemOptions FileMgrOpts;
42   FileManager FileMgr;
43   IntrusiveRefCntPtr<DiagnosticIDs> DiagID;
44   DiagnosticsEngine Diags;
45   SourceManager SourceMgr;
46   LangOptions LangOpts;
47   std::shared_ptr<TargetOptions> TargetOpts;
48   IntrusiveRefCntPtr<TargetInfo> Target;
49 };
50 
51 class IncludeCollector : public PPCallbacks {
52 public:
53   Preprocessor &PP;
54   SmallVectorImpl<StringRef> &IncludedFiles;
55 
IncludeCollector(Preprocessor & PP,SmallVectorImpl<StringRef> & IncludedFiles)56   IncludeCollector(Preprocessor &PP, SmallVectorImpl<StringRef> &IncludedFiles)
57       : PP(PP), IncludedFiles(IncludedFiles) {}
58 
LexedFileChanged(FileID FID,LexedFileChangeReason Reason,SrcMgr::CharacteristicKind FileType,FileID PrevFID,SourceLocation Loc)59   void LexedFileChanged(FileID FID, LexedFileChangeReason Reason,
60                         SrcMgr::CharacteristicKind FileType, FileID PrevFID,
61                         SourceLocation Loc) override {
62     if (Reason != LexedFileChangeReason::EnterFile)
63       return;
64     if (FID == PP.getPredefinesFileID())
65       return;
66     StringRef Filename =
67         PP.getSourceManager().getSLocEntry(FID).getFile().getName();
68     IncludedFiles.push_back(Filename);
69   }
70 };
71 
TEST_F(PPDependencyDirectivesTest,MacroGuard)72 TEST_F(PPDependencyDirectivesTest, MacroGuard) {
73   // "head1.h" has a macro guard and should only be included once.
74   // "head2.h" and "head3.h" have tokens following the macro check, they should
75   // be included multiple times.
76 
77   auto VFS = new llvm::vfs::InMemoryFileSystem();
78   VFS->addFile(
79       "head1.h", 0,
80       llvm::MemoryBuffer::getMemBuffer("#ifndef H1_H\n#define H1_H\n#endif\n"));
81   VFS->addFile(
82       "head2.h", 0,
83       llvm::MemoryBuffer::getMemBuffer("#ifndef H2_H\n#define H2_H\n#endif\n\n"
84                                        "extern int foo;\n"));
85   VFS->addFile("head3.h", 0,
86                llvm::MemoryBuffer::getMemBuffer(
87                    "#ifndef H3_H\n#define H3_H\n#endif\n\n"
88                    "#ifdef SOMEMAC\nextern int foo;\n#endif\n"));
89   VFS->addFile("main.c", 0,
90                llvm::MemoryBuffer::getMemBuffer(
91                    "#include \"head1.h\"\n#include \"head1.h\"\n"
92                    "#include \"head2.h\"\n#include \"head2.h\"\n"
93                    "#include \"head3.h\"\n#include \"head3.h\"\n"));
94   FileMgr.setVirtualFileSystem(VFS);
95 
96   OptionalFileEntryRef FE;
97   ASSERT_THAT_ERROR(FileMgr.getFileRef("main.c").moveInto(FE),
98                     llvm::Succeeded());
99   SourceMgr.setMainFileID(
100       SourceMgr.createFileID(*FE, SourceLocation(), SrcMgr::C_User));
101 
102   struct DepDirectives {
103     SmallVector<dependency_directives_scan::Token> Tokens;
104     SmallVector<dependency_directives_scan::Directive> Directives;
105   };
106   SmallVector<std::unique_ptr<DepDirectives>> DepDirectivesObjects;
107 
108   auto getDependencyDirectives = [&](FileEntryRef File)
109       -> std::optional<ArrayRef<dependency_directives_scan::Directive>> {
110     DepDirectivesObjects.push_back(std::make_unique<DepDirectives>());
111     StringRef Input = (*FileMgr.getBufferForFile(File))->getBuffer();
112     bool Err = scanSourceForDependencyDirectives(
113         Input, DepDirectivesObjects.back()->Tokens,
114         DepDirectivesObjects.back()->Directives);
115     EXPECT_FALSE(Err);
116     return llvm::ArrayRef(DepDirectivesObjects.back()->Directives);
117   };
118 
119   auto PPOpts = std::make_shared<PreprocessorOptions>();
120   PPOpts->DependencyDirectivesForFile = [&](FileEntryRef File)
121       -> std::optional<ArrayRef<dependency_directives_scan::Directive>> {
122     return getDependencyDirectives(File);
123   };
124 
125   TrivialModuleLoader ModLoader;
126   HeaderSearch HeaderInfo(std::make_shared<HeaderSearchOptions>(), SourceMgr,
127                           Diags, LangOpts, Target.get());
128   Preprocessor PP(PPOpts, Diags, LangOpts, SourceMgr, HeaderInfo, ModLoader,
129                   /*IILookup =*/nullptr,
130                   /*OwnsHeaderSearch =*/false);
131   PP.Initialize(*Target);
132 
133   SmallVector<StringRef> IncludedFiles;
134   PP.addPPCallbacks(std::make_unique<IncludeCollector>(PP, IncludedFiles));
135   PP.EnterMainSourceFile();
136   PP.LexTokensUntilEOF();
137 
138   SmallVector<std::string> IncludedFilesSlash;
139   for (StringRef IncludedFile : IncludedFiles)
140     IncludedFilesSlash.push_back(
141         llvm::sys::path::convert_to_slash(IncludedFile));
142   SmallVector<std::string> ExpectedIncludes{
143       "main.c", "./head1.h", "./head2.h", "./head2.h", "./head3.h", "./head3.h",
144   };
145   EXPECT_EQ(IncludedFilesSlash, ExpectedIncludes);
146 }
147 
148 } // anonymous namespace
149