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
22using namespace llvm;
23using namespace clang;
24
25namespace {
26
27class 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(path&: ModuleCachePath, a: "mcp");
33 ASSERT_FALSE(sys::fs::create_directories(ModuleCachePath));
34 }
35
36 void TearDown() override { sys::fs::remove_directories(path: TestDir); }
37
38public:
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(path&: AbsPath, a: 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(Path: "test.m", Contents: R"cpp(
58 @import Top;
59 )cpp");
60
61 addFile(Path: "frameworks/Top.framework/Headers/top.h", Contents: R"cpp(
62 @import M;
63 )cpp");
64 addFile(Path: "frameworks/Top.framework/Modules/module.modulemap", Contents: R"cpp(
65 framework module Top [system] {
66 header "top.h"
67 export *
68 }
69 )cpp");
70
71 addFile(Path: "frameworks/M.framework/Headers/m.h", Contents: R"cpp(
72 void foo();
73 )cpp");
74 addFile(Path: "frameworks/M.framework/Modules/module.modulemap", Contents: R"cpp(
75 framework module M [system] {
76 header "m.h"
77 export *
78 }
79 )cpp");
80
81 addFile(Path: "frameworks2/M.framework/Headers/m.h", Contents: R"cpp(
82 void foo();
83 )cpp");
84 addFile(Path: "frameworks2/M.framework/Modules/module.modulemap", Contents: 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
104TEST_F(ModuleCacheTest, CachedModuleNewPath) {
105 addDuplicateFrameworks();
106
107 SmallString<256> MCPArg("-fmodules-cache-path=");
108 MCPArg.append(RHS: ModuleCachePath);
109 IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
110 CompilerInstance::createDiagnostics(Opts: new DiagnosticOptions());
111 CreateInvocationOptions CIOpts;
112 CIOpts.Diags = Diags;
113 CIOpts.VFS = llvm::vfs::createPhysicalFileSystem();
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, Opts: 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(Args: Args2, Opts: 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
154TEST_F(ModuleCacheTest, CachedModuleNewPathAllowErrors) {
155 addDuplicateFrameworks();
156
157 SmallString<256> MCPArg("-fmodules-cache-path=");
158 MCPArg.append(RHS: ModuleCachePath);
159 IntrusiveRefCntPtr<DiagnosticsEngine> Diags =
160 CompilerInstance::createDiagnostics(Opts: new DiagnosticOptions());
161 CreateInvocationOptions CIOpts;
162 CIOpts.Diags = Diags;
163 CIOpts.VFS = llvm::vfs::createPhysicalFileSystem();
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, Opts: 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(Args: Args2, Opts: 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

source code of clang/unittests/Serialization/ModuleCacheTest.cpp