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: |
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, Opts: 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 | |
56 | IncludeCollector(Preprocessor &PP, SmallVectorImpl<StringRef> &IncludedFiles) |
57 | : PP(PP), IncludedFiles(IncludedFiles) {} |
58 | |
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(Elt: Filename); |
69 | } |
70 | }; |
71 | |
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 | Path: "head1.h" , ModificationTime: 0, |
80 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "#ifndef H1_H\n#define H1_H\n#endif\n" )); |
81 | VFS->addFile( |
82 | Path: "head2.h" , ModificationTime: 0, |
83 | Buffer: llvm::MemoryBuffer::getMemBuffer(InputData: "#ifndef H2_H\n#define H2_H\n#endif\n\n" |
84 | "extern int foo;\n" )); |
85 | VFS->addFile(Path: "head3.h" , ModificationTime: 0, |
86 | Buffer: llvm::MemoryBuffer::getMemBuffer( |
87 | InputData: "#ifndef H3_H\n#define H3_H\n#endif\n\n" |
88 | "#ifdef SOMEMAC\nextern int foo;\n#endif\n" )); |
89 | VFS->addFile(Path: "main.c" , ModificationTime: 0, |
90 | Buffer: llvm::MemoryBuffer::getMemBuffer( |
91 | InputData: "#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(SourceFile: *FE, IncludePos: SourceLocation(), FileCharacter: 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(Elt: std::make_unique<DepDirectives>()); |
111 | StringRef Input = (*FileMgr.getBufferForFile(Entry: File))->getBuffer(); |
112 | bool Err = scanSourceForDependencyDirectives( |
113 | Input, Tokens&: DepDirectivesObjects.back()->Tokens, |
114 | Directives&: 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 (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: *Target); |
132 | |
133 | SmallVector<StringRef> IncludedFiles; |
134 | PP.addPPCallbacks(C: std::make_unique<IncludeCollector>(args&: PP, args&: IncludedFiles)); |
135 | PP.EnterMainSourceFile(); |
136 | PP.LexTokensUntilEOF(); |
137 | |
138 | SmallVector<std::string> IncludedFilesSlash; |
139 | for (StringRef IncludedFile : IncludedFiles) |
140 | IncludedFilesSlash.push_back( |
141 | Elt: llvm::sys::path::convert_to_slash(path: 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 | |