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 93 TEST_F(ModuleCacheTest, CachedModuleNewPath) { 94 addDuplicateFrameworks(); 95 96 SmallString<256> MCPArg("-fmodules-cache-path="); 97 MCPArg.append(ModuleCachePath); 98 IntrusiveRefCntPtr<DiagnosticsEngine> Diags = 99 CompilerInstance::createDiagnostics(new DiagnosticOptions()); 100 CreateInvocationOptions CIOpts; 101 CIOpts.Diags = Diags; 102 CIOpts.VFS = llvm::vfs::createPhysicalFileSystem(); 103 104 // First run should pass with no errors 105 const char *Args[] = {"clang", "-fmodules", "-Fframeworks", 106 MCPArg.c_str(), "-working-directory", TestDir.c_str(), 107 "test.m"}; 108 std::shared_ptr<CompilerInvocation> Invocation = 109 createInvocation(Args, CIOpts); 110 ASSERT_TRUE(Invocation); 111 CompilerInstance Instance; 112 Instance.setDiagnostics(Diags.get()); 113 Instance.setInvocation(Invocation); 114 SyntaxOnlyAction Action; 115 ASSERT_TRUE(Instance.ExecuteAction(Action)); 116 ASSERT_FALSE(Diags->hasErrorOccurred()); 117 118 // Now add `frameworks2` to the search path. `Top.pcm` will have a reference 119 // to the `M` from `frameworks`, but a search will find the `M` from 120 // `frameworks2` - causing a mismatch and it to be considered out of date. 121 // 122 // Normally this would be fine - `M` and the modules it depends on would be 123 // rebuilt. However, since we have a shared module cache and thus an already 124 // finalized `Top`, recompiling `Top` will cause the existing module to be 125 // removed from the cache, causing possible crashed if it is ever used. 126 // 127 // Make sure that an error occurs instead. 128 const char *Args2[] = {"clang", "-fmodules", "-Fframeworks2", 129 "-Fframeworks", MCPArg.c_str(), "-working-directory", 130 TestDir.c_str(), "test.m"}; 131 std::shared_ptr<CompilerInvocation> Invocation2 = 132 createInvocation(Args2, CIOpts); 133 ASSERT_TRUE(Invocation2); 134 CompilerInstance Instance2(Instance.getPCHContainerOperations(), 135 &Instance.getModuleCache()); 136 Instance2.setDiagnostics(Diags.get()); 137 Instance2.setInvocation(Invocation2); 138 SyntaxOnlyAction Action2; 139 ASSERT_FALSE(Instance2.ExecuteAction(Action2)); 140 ASSERT_TRUE(Diags->hasErrorOccurred()); 141 } 142 143 TEST_F(ModuleCacheTest, CachedModuleNewPathAllowErrors) { 144 addDuplicateFrameworks(); 145 146 SmallString<256> MCPArg("-fmodules-cache-path="); 147 MCPArg.append(ModuleCachePath); 148 IntrusiveRefCntPtr<DiagnosticsEngine> Diags = 149 CompilerInstance::createDiagnostics(new DiagnosticOptions()); 150 CreateInvocationOptions CIOpts; 151 CIOpts.Diags = Diags; 152 153 // First run should pass with no errors 154 const char *Args[] = {"clang", "-fmodules", "-Fframeworks", 155 MCPArg.c_str(), "-working-directory", TestDir.c_str(), 156 "test.m"}; 157 std::shared_ptr<CompilerInvocation> Invocation = 158 createInvocation(Args, CIOpts); 159 ASSERT_TRUE(Invocation); 160 CompilerInstance Instance; 161 Instance.setDiagnostics(Diags.get()); 162 Instance.setInvocation(Invocation); 163 SyntaxOnlyAction Action; 164 ASSERT_TRUE(Instance.ExecuteAction(Action)); 165 ASSERT_FALSE(Diags->hasErrorOccurred()); 166 167 // Same as `CachedModuleNewPath` but while allowing errors. This is a hard 168 // failure where the module wasn't created, so it should still fail. 169 const char *Args2[] = { 170 "clang", "-fmodules", "-Fframeworks2", 171 "-Fframeworks", MCPArg.c_str(), "-working-directory", 172 TestDir.c_str(), "-Xclang", "-fallow-pcm-with-compiler-errors", 173 "test.m"}; 174 std::shared_ptr<CompilerInvocation> Invocation2 = 175 createInvocation(Args2, CIOpts); 176 ASSERT_TRUE(Invocation2); 177 CompilerInstance Instance2(Instance.getPCHContainerOperations(), 178 &Instance.getModuleCache()); 179 Instance2.setDiagnostics(Diags.get()); 180 Instance2.setInvocation(Invocation2); 181 SyntaxOnlyAction Action2; 182 ASSERT_FALSE(Instance2.ExecuteAction(Action2)); 183 ASSERT_TRUE(Diags->hasErrorOccurred()); 184 } 185 186 } // anonymous namespace 187