1 //===- unittests/Serialization/ModuleCacheTest.cpp - CI 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/Basic/FileManager.h" 10 #include "clang/Frontend/CompilerInstance.h" 11 #include "clang/Frontend/CompilerInvocation.h" 12 #include "clang/Frontend/FrontendActions.h" 13 #include "clang/Frontend/Utils.h" 14 #include "clang/Lex/HeaderSearch.h" 15 #include "llvm/ADT/SmallString.h" 16 #include "llvm/Support/FileSystem.h" 17 #include "llvm/Support/VirtualFileSystem.h" 18 #include "llvm/Support/raw_ostream.h" 19 20 #include "gtest/gtest.h" 21 22 using namespace llvm; 23 using namespace clang; 24 25 namespace { 26 27 class ModuleCacheTest : public ::testing::Test { 28 void SetUp() override { 29 ASSERT_FALSE(sys::fs::createUniqueDirectory("modulecache-test", TestDir)); 30 31 ModuleCachePath = SmallString<256>(TestDir); 32 sys::path::append(ModuleCachePath, "mcp"); 33 ASSERT_FALSE(sys::fs::create_directories(ModuleCachePath)); 34 } 35 36 void TearDown() override { sys::fs::remove_directories(TestDir); } 37 38 public: 39 SmallString<256> TestDir; 40 SmallString<256> ModuleCachePath; 41 42 void addFile(StringRef Path, StringRef Contents) { 43 ASSERT_FALSE(sys::path::is_absolute(Path)); 44 45 SmallString<256> AbsPath(TestDir); 46 sys::path::append(AbsPath, Path); 47 48 std::error_code EC; 49 ASSERT_FALSE( 50 sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath))); 51 llvm::raw_fd_ostream OS(AbsPath, EC); 52 ASSERT_FALSE(EC); 53 OS << Contents; 54 } 55 56 void addDuplicateFrameworks() { 57 addFile("test.m", R"cpp( 58 @import Top; 59 )cpp"); 60 61 addFile("frameworks/Top.framework/Headers/top.h", R"cpp( 62 @import M; 63 )cpp"); 64 addFile("frameworks/Top.framework/Modules/module.modulemap", R"cpp( 65 framework module Top [system] { 66 header "top.h" 67 export * 68 } 69 )cpp"); 70 71 addFile("frameworks/M.framework/Headers/m.h", R"cpp( 72 void foo(); 73 )cpp"); 74 addFile("frameworks/M.framework/Modules/module.modulemap", R"cpp( 75 framework module M [system] { 76 header "m.h" 77 export * 78 } 79 )cpp"); 80 81 addFile("frameworks2/M.framework/Headers/m.h", R"cpp( 82 void foo(); 83 )cpp"); 84 addFile("frameworks2/M.framework/Modules/module.modulemap", R"cpp( 85 framework module M [system] { 86 header "m.h" 87 export * 88 } 89 )cpp"); 90 } 91 92 std::unique_ptr<CompilerInvocation> 93 createInvocationAndEnableFree(ArrayRef<const char *> Args, 94 CreateInvocationOptions Opts) { 95 std::unique_ptr<CompilerInvocation> Invocation = 96 createInvocation(Args, Opts); 97 if (Invocation) 98 Invocation->getFrontendOpts().DisableFree = false; 99 100 return Invocation; 101 } 102 }; 103 104 TEST_F(ModuleCacheTest, CachedModuleNewPath) { 105 addDuplicateFrameworks(); 106 107 SmallString<256> MCPArg("-fmodules-cache-path="); 108 MCPArg.append(ModuleCachePath); 109 CreateInvocationOptions CIOpts; 110 CIOpts.VFS = llvm::vfs::createPhysicalFileSystem(); 111 IntrusiveRefCntPtr<DiagnosticsEngine> Diags = 112 CompilerInstance::createDiagnostics(*CIOpts.VFS, new DiagnosticOptions()); 113 CIOpts.Diags = Diags; 114 115 // First run should pass with no errors 116 const char *Args[] = {"clang", "-fmodules", "-Fframeworks", 117 MCPArg.c_str(), "-working-directory", TestDir.c_str(), 118 "test.m"}; 119 std::shared_ptr<CompilerInvocation> Invocation = 120 createInvocationAndEnableFree(Args, CIOpts); 121 ASSERT_TRUE(Invocation); 122 CompilerInstance Instance; 123 Instance.setDiagnostics(Diags.get()); 124 Instance.setInvocation(Invocation); 125 SyntaxOnlyAction Action; 126 ASSERT_TRUE(Instance.ExecuteAction(Action)); 127 ASSERT_FALSE(Diags->hasErrorOccurred()); 128 129 // Now add `frameworks2` to the search path. `Top.pcm` will have a reference 130 // to the `M` from `frameworks`, but a search will find the `M` from 131 // `frameworks2` - causing a mismatch and it to be considered out of date. 132 // 133 // Normally this would be fine - `M` and the modules it depends on would be 134 // rebuilt. However, since we have a shared module cache and thus an already 135 // finalized `Top`, recompiling `Top` will cause the existing module to be 136 // removed from the cache, causing possible crashed if it is ever used. 137 // 138 // Make sure that an error occurs instead. 139 const char *Args2[] = {"clang", "-fmodules", "-Fframeworks2", 140 "-Fframeworks", MCPArg.c_str(), "-working-directory", 141 TestDir.c_str(), "test.m"}; 142 std::shared_ptr<CompilerInvocation> Invocation2 = 143 createInvocationAndEnableFree(Args2, CIOpts); 144 ASSERT_TRUE(Invocation2); 145 CompilerInstance Instance2(Instance.getPCHContainerOperations(), 146 &Instance.getModuleCache()); 147 Instance2.setDiagnostics(Diags.get()); 148 Instance2.setInvocation(Invocation2); 149 SyntaxOnlyAction Action2; 150 ASSERT_FALSE(Instance2.ExecuteAction(Action2)); 151 ASSERT_TRUE(Diags->hasErrorOccurred()); 152 } 153 154 TEST_F(ModuleCacheTest, CachedModuleNewPathAllowErrors) { 155 addDuplicateFrameworks(); 156 157 SmallString<256> MCPArg("-fmodules-cache-path="); 158 MCPArg.append(ModuleCachePath); 159 CreateInvocationOptions CIOpts; 160 CIOpts.VFS = llvm::vfs::createPhysicalFileSystem(); 161 IntrusiveRefCntPtr<DiagnosticsEngine> Diags = 162 CompilerInstance::createDiagnostics(*CIOpts.VFS, new DiagnosticOptions()); 163 CIOpts.Diags = Diags; 164 165 // First run should pass with no errors 166 const char *Args[] = {"clang", "-fmodules", "-Fframeworks", 167 MCPArg.c_str(), "-working-directory", TestDir.c_str(), 168 "test.m"}; 169 std::shared_ptr<CompilerInvocation> Invocation = 170 createInvocationAndEnableFree(Args, CIOpts); 171 ASSERT_TRUE(Invocation); 172 CompilerInstance Instance; 173 Instance.setDiagnostics(Diags.get()); 174 Instance.setInvocation(Invocation); 175 SyntaxOnlyAction Action; 176 ASSERT_TRUE(Instance.ExecuteAction(Action)); 177 ASSERT_FALSE(Diags->hasErrorOccurred()); 178 179 // Same as `CachedModuleNewPath` but while allowing errors. This is a hard 180 // failure where the module wasn't created, so it should still fail. 181 const char *Args2[] = { 182 "clang", "-fmodules", "-Fframeworks2", 183 "-Fframeworks", MCPArg.c_str(), "-working-directory", 184 TestDir.c_str(), "-Xclang", "-fallow-pcm-with-compiler-errors", 185 "test.m"}; 186 std::shared_ptr<CompilerInvocation> Invocation2 = 187 createInvocationAndEnableFree(Args2, CIOpts); 188 ASSERT_TRUE(Invocation2); 189 CompilerInstance Instance2(Instance.getPCHContainerOperations(), 190 &Instance.getModuleCache()); 191 Instance2.setDiagnostics(Diags.get()); 192 Instance2.setInvocation(Invocation2); 193 SyntaxOnlyAction Action2; 194 ASSERT_FALSE(Instance2.ExecuteAction(Action2)); 195 ASSERT_TRUE(Diags->hasErrorOccurred()); 196 } 197 198 } // anonymous namespace 199