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