xref: /llvm-project/clang/unittests/Serialization/ModuleCacheTest.cpp (revision df9a14d7bbf1180e4f1474254c9d7ed6bcb4ce55)
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