1 //===- DependencyScannerTest.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/AST/ASTConsumer.h" 10 #include "clang/AST/DeclCXX.h" 11 #include "clang/AST/DeclGroup.h" 12 #include "clang/Frontend/ASTUnit.h" 13 #include "clang/Frontend/CompilerInstance.h" 14 #include "clang/Frontend/FrontendAction.h" 15 #include "clang/Frontend/FrontendActions.h" 16 #include "clang/Tooling/CompilationDatabase.h" 17 #include "clang/Tooling/DependencyScanning/DependencyScanningTool.h" 18 #include "clang/Tooling/Tooling.h" 19 #include "llvm/ADT/STLExtras.h" 20 #include "llvm/MC/TargetRegistry.h" 21 #include "llvm/Support/FormatVariadic.h" 22 #include "llvm/Support/Path.h" 23 #include "llvm/Support/TargetSelect.h" 24 #include "llvm/Testing/Support/Error.h" 25 #include "gtest/gtest.h" 26 #include <algorithm> 27 #include <string> 28 29 using namespace clang; 30 using namespace tooling; 31 using namespace dependencies; 32 33 namespace { 34 35 /// Prints out all of the gathered dependencies into a string. 36 class TestFileCollector : public DependencyFileGenerator { 37 public: 38 TestFileCollector(DependencyOutputOptions &Opts, 39 std::vector<std::string> &Deps) 40 : DependencyFileGenerator(Opts), Deps(Deps) {} 41 42 void finishedMainFile(DiagnosticsEngine &Diags) override { 43 auto NewDeps = getDependencies(); 44 Deps.insert(Deps.end(), NewDeps.begin(), NewDeps.end()); 45 } 46 47 private: 48 std::vector<std::string> &Deps; 49 }; 50 51 class TestDependencyScanningAction : public tooling::ToolAction { 52 public: 53 TestDependencyScanningAction(std::vector<std::string> &Deps) : Deps(Deps) {} 54 55 bool runInvocation(std::shared_ptr<CompilerInvocation> Invocation, 56 FileManager *FileMgr, 57 std::shared_ptr<PCHContainerOperations> PCHContainerOps, 58 DiagnosticConsumer *DiagConsumer) override { 59 CompilerInstance Compiler(std::move(PCHContainerOps)); 60 Compiler.setInvocation(std::move(Invocation)); 61 Compiler.setFileManager(FileMgr); 62 63 Compiler.createDiagnostics(FileMgr->getVirtualFileSystem(), DiagConsumer, 64 /*ShouldOwnClient=*/false); 65 if (!Compiler.hasDiagnostics()) 66 return false; 67 68 Compiler.createSourceManager(*FileMgr); 69 Compiler.addDependencyCollector(std::make_shared<TestFileCollector>( 70 Compiler.getInvocation().getDependencyOutputOpts(), Deps)); 71 72 auto Action = std::make_unique<PreprocessOnlyAction>(); 73 return Compiler.ExecuteAction(*Action); 74 } 75 76 private: 77 std::vector<std::string> &Deps; 78 }; 79 80 } // namespace 81 82 TEST(DependencyScanner, ScanDepsReuseFilemanager) { 83 std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"}; 84 StringRef CWD = "/root"; 85 FixedCompilationDatabase CDB(CWD, Compilation); 86 87 auto VFS = new llvm::vfs::InMemoryFileSystem(); 88 VFS->setCurrentWorkingDirectory(CWD); 89 auto Sept = llvm::sys::path::get_separator(); 90 std::string HeaderPath = 91 std::string(llvm::formatv("{0}root{0}header.h", Sept)); 92 std::string SymlinkPath = 93 std::string(llvm::formatv("{0}root{0}symlink.h", Sept)); 94 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept)); 95 96 VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n")); 97 VFS->addHardLink(SymlinkPath, HeaderPath); 98 VFS->addFile(TestPath, 0, 99 llvm::MemoryBuffer::getMemBuffer( 100 "#include \"symlink.h\"\n#include \"header.h\"\n")); 101 102 ClangTool Tool(CDB, {"test.cpp"}, std::make_shared<PCHContainerOperations>(), 103 VFS); 104 Tool.clearArgumentsAdjusters(); 105 std::vector<std::string> Deps; 106 TestDependencyScanningAction Action(Deps); 107 Tool.run(&Action); 108 using llvm::sys::path::convert_to_slash; 109 // The first invocation should return dependencies in order of access. 110 ASSERT_EQ(Deps.size(), 3u); 111 EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp"); 112 EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h"); 113 EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h"); 114 115 // The file manager should still have two FileEntries, as one file is a 116 // hardlink. 117 FileManager &Files = Tool.getFiles(); 118 EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u); 119 120 Deps.clear(); 121 Tool.run(&Action); 122 // The second invocation should have the same order of dependencies. 123 ASSERT_EQ(Deps.size(), 3u); 124 EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp"); 125 EXPECT_EQ(convert_to_slash(Deps[1]), "/root/symlink.h"); 126 EXPECT_EQ(convert_to_slash(Deps[2]), "/root/header.h"); 127 128 EXPECT_EQ(Files.getNumUniqueRealFiles(), 2u); 129 } 130 131 TEST(DependencyScanner, ScanDepsReuseFilemanagerSkippedFile) { 132 std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"}; 133 StringRef CWD = "/root"; 134 FixedCompilationDatabase CDB(CWD, Compilation); 135 136 auto VFS = new llvm::vfs::InMemoryFileSystem(); 137 VFS->setCurrentWorkingDirectory(CWD); 138 auto Sept = llvm::sys::path::get_separator(); 139 std::string HeaderPath = 140 std::string(llvm::formatv("{0}root{0}header.h", Sept)); 141 std::string SymlinkPath = 142 std::string(llvm::formatv("{0}root{0}symlink.h", Sept)); 143 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept)); 144 std::string Test2Path = 145 std::string(llvm::formatv("{0}root{0}test2.cpp", Sept)); 146 147 VFS->addFile(HeaderPath, 0, 148 llvm::MemoryBuffer::getMemBuffer("#pragma once\n")); 149 VFS->addHardLink(SymlinkPath, HeaderPath); 150 VFS->addFile(TestPath, 0, 151 llvm::MemoryBuffer::getMemBuffer( 152 "#include \"header.h\"\n#include \"symlink.h\"\n")); 153 VFS->addFile(Test2Path, 0, 154 llvm::MemoryBuffer::getMemBuffer( 155 "#include \"symlink.h\"\n#include \"header.h\"\n")); 156 157 ClangTool Tool(CDB, {"test.cpp", "test2.cpp"}, 158 std::make_shared<PCHContainerOperations>(), VFS); 159 Tool.clearArgumentsAdjusters(); 160 std::vector<std::string> Deps; 161 TestDependencyScanningAction Action(Deps); 162 Tool.run(&Action); 163 using llvm::sys::path::convert_to_slash; 164 ASSERT_EQ(Deps.size(), 6u); 165 EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp"); 166 EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h"); 167 EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h"); 168 EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test2.cpp"); 169 EXPECT_EQ(convert_to_slash(Deps[4]), "/root/symlink.h"); 170 EXPECT_EQ(convert_to_slash(Deps[5]), "/root/header.h"); 171 } 172 173 TEST(DependencyScanner, ScanDepsReuseFilemanagerHasInclude) { 174 std::vector<std::string> Compilation = {"-c", "-E", "-MT", "test.cpp.o"}; 175 StringRef CWD = "/root"; 176 FixedCompilationDatabase CDB(CWD, Compilation); 177 178 auto VFS = new llvm::vfs::InMemoryFileSystem(); 179 VFS->setCurrentWorkingDirectory(CWD); 180 auto Sept = llvm::sys::path::get_separator(); 181 std::string HeaderPath = 182 std::string(llvm::formatv("{0}root{0}header.h", Sept)); 183 std::string SymlinkPath = 184 std::string(llvm::formatv("{0}root{0}symlink.h", Sept)); 185 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept)); 186 187 VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n")); 188 VFS->addHardLink(SymlinkPath, HeaderPath); 189 VFS->addFile( 190 TestPath, 0, 191 llvm::MemoryBuffer::getMemBuffer("#if __has_include(\"header.h\") && " 192 "__has_include(\"symlink.h\")\n#endif")); 193 194 ClangTool Tool(CDB, {"test.cpp", "test.cpp"}, 195 std::make_shared<PCHContainerOperations>(), VFS); 196 Tool.clearArgumentsAdjusters(); 197 std::vector<std::string> Deps; 198 TestDependencyScanningAction Action(Deps); 199 Tool.run(&Action); 200 using llvm::sys::path::convert_to_slash; 201 ASSERT_EQ(Deps.size(), 6u); 202 EXPECT_EQ(convert_to_slash(Deps[0]), "/root/test.cpp"); 203 EXPECT_EQ(convert_to_slash(Deps[1]), "/root/header.h"); 204 EXPECT_EQ(convert_to_slash(Deps[2]), "/root/symlink.h"); 205 EXPECT_EQ(convert_to_slash(Deps[3]), "/root/test.cpp"); 206 EXPECT_EQ(convert_to_slash(Deps[4]), "/root/header.h"); 207 EXPECT_EQ(convert_to_slash(Deps[5]), "/root/symlink.h"); 208 } 209 210 TEST(DependencyScanner, ScanDepsWithFS) { 211 std::vector<std::string> CommandLine = {"clang", 212 "-target", 213 "x86_64-apple-macosx10.7", 214 "-c", 215 "test.cpp", 216 "-o" 217 "test.cpp.o"}; 218 StringRef CWD = "/root"; 219 220 auto VFS = new llvm::vfs::InMemoryFileSystem(); 221 VFS->setCurrentWorkingDirectory(CWD); 222 auto Sept = llvm::sys::path::get_separator(); 223 std::string HeaderPath = 224 std::string(llvm::formatv("{0}root{0}header.h", Sept)); 225 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.cpp", Sept)); 226 227 VFS->addFile(HeaderPath, 0, llvm::MemoryBuffer::getMemBuffer("\n")); 228 VFS->addFile(TestPath, 0, 229 llvm::MemoryBuffer::getMemBuffer("#include \"header.h\"\n")); 230 231 DependencyScanningService Service(ScanningMode::DependencyDirectivesScan, 232 ScanningOutputFormat::Make); 233 DependencyScanningTool ScanTool(Service, VFS); 234 235 std::string DepFile; 236 ASSERT_THAT_ERROR( 237 ScanTool.getDependencyFile(CommandLine, CWD).moveInto(DepFile), 238 llvm::Succeeded()); 239 using llvm::sys::path::convert_to_slash; 240 EXPECT_EQ(convert_to_slash(DepFile), 241 "test.cpp.o: /root/test.cpp /root/header.h\n"); 242 } 243 244 TEST(DependencyScanner, ScanDepsWithModuleLookup) { 245 std::vector<std::string> CommandLine = { 246 "clang", 247 "-target", 248 "x86_64-apple-macosx10.7", 249 "-c", 250 "test.m", 251 "-o" 252 "test.m.o", 253 "-fmodules", 254 "-I/root/SomeSources", 255 }; 256 StringRef CWD = "/root"; 257 258 auto VFS = new llvm::vfs::InMemoryFileSystem(); 259 VFS->setCurrentWorkingDirectory(CWD); 260 auto Sept = llvm::sys::path::get_separator(); 261 std::string OtherPath = 262 std::string(llvm::formatv("{0}root{0}SomeSources{0}other.h", Sept)); 263 std::string TestPath = std::string(llvm::formatv("{0}root{0}test.m", Sept)); 264 265 VFS->addFile(OtherPath, 0, llvm::MemoryBuffer::getMemBuffer("\n")); 266 VFS->addFile(TestPath, 0, llvm::MemoryBuffer::getMemBuffer("@import Foo;\n")); 267 268 struct InterceptorFS : llvm::vfs::ProxyFileSystem { 269 std::vector<std::string> StatPaths; 270 std::vector<std::string> ReadFiles; 271 272 InterceptorFS(IntrusiveRefCntPtr<FileSystem> UnderlyingFS) 273 : ProxyFileSystem(UnderlyingFS) {} 274 275 llvm::ErrorOr<llvm::vfs::Status> status(const Twine &Path) override { 276 StatPaths.push_back(Path.str()); 277 return ProxyFileSystem::status(Path); 278 } 279 280 llvm::ErrorOr<std::unique_ptr<llvm::vfs::File>> 281 openFileForRead(const Twine &Path) override { 282 ReadFiles.push_back(Path.str()); 283 return ProxyFileSystem::openFileForRead(Path); 284 } 285 }; 286 287 auto InterceptFS = llvm::makeIntrusiveRefCnt<InterceptorFS>(VFS); 288 289 DependencyScanningService Service(ScanningMode::DependencyDirectivesScan, 290 ScanningOutputFormat::Make); 291 DependencyScanningTool ScanTool(Service, InterceptFS); 292 293 // This will fail with "fatal error: module 'Foo' not found" but it doesn't 294 // matter, the point of the test is to check that files are not read 295 // unnecessarily. 296 std::string DepFile; 297 ASSERT_THAT_ERROR( 298 ScanTool.getDependencyFile(CommandLine, CWD).moveInto(DepFile), 299 llvm::Failed()); 300 301 EXPECT_TRUE(llvm::find(InterceptFS->StatPaths, OtherPath) == 302 InterceptFS->StatPaths.end()); 303 EXPECT_EQ(InterceptFS->ReadFiles, std::vector<std::string>{"test.m"}); 304 } 305