1 | //===--- FileRemapper.cpp - File Remapping Helper -------------------------===// |
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/ARCMigrate/FileRemapper.h" |
10 | #include "clang/Basic/Diagnostic.h" |
11 | #include "clang/Basic/FileManager.h" |
12 | #include "clang/Lex/PreprocessorOptions.h" |
13 | #include "llvm/Support/FileSystem.h" |
14 | #include "llvm/Support/MemoryBuffer.h" |
15 | #include "llvm/Support/Path.h" |
16 | #include "llvm/Support/raw_ostream.h" |
17 | #include <fstream> |
18 | |
19 | using namespace clang; |
20 | using namespace arcmt; |
21 | |
22 | FileRemapper::FileRemapper() { |
23 | FileMgr.reset(p: new FileManager(FileSystemOptions())); |
24 | } |
25 | |
26 | FileRemapper::~FileRemapper() { |
27 | clear(); |
28 | } |
29 | |
30 | void FileRemapper::clear(StringRef outputDir) { |
31 | for (MappingsTy::iterator |
32 | I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) |
33 | resetTarget(targ&: I->second); |
34 | FromToMappings.clear(); |
35 | assert(ToFromMappings.empty()); |
36 | if (!outputDir.empty()) { |
37 | std::string infoFile = getRemapInfoFile(outputDir); |
38 | llvm::sys::fs::remove(path: infoFile); |
39 | } |
40 | } |
41 | |
42 | std::string FileRemapper::getRemapInfoFile(StringRef outputDir) { |
43 | assert(!outputDir.empty()); |
44 | SmallString<128> InfoFile = outputDir; |
45 | llvm::sys::path::append(path&: InfoFile, a: "remap" ); |
46 | return std::string(InfoFile); |
47 | } |
48 | |
49 | bool FileRemapper::initFromDisk(StringRef outputDir, DiagnosticsEngine &Diag, |
50 | bool ignoreIfFilesChanged) { |
51 | std::string infoFile = getRemapInfoFile(outputDir); |
52 | return initFromFile(filePath: infoFile, Diag, ignoreIfFilesChanged); |
53 | } |
54 | |
55 | bool FileRemapper::initFromFile(StringRef filePath, DiagnosticsEngine &Diag, |
56 | bool ignoreIfFilesChanged) { |
57 | assert(FromToMappings.empty() && |
58 | "initFromDisk should be called before any remap calls" ); |
59 | std::string infoFile = std::string(filePath); |
60 | if (!llvm::sys::fs::exists(Path: infoFile)) |
61 | return false; |
62 | |
63 | std::vector<std::pair<FileEntryRef, FileEntryRef>> pairs; |
64 | |
65 | llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> fileBuf = |
66 | llvm::MemoryBuffer::getFile(Filename: infoFile, /*IsText=*/true); |
67 | if (!fileBuf) |
68 | return report(err: "Error opening file: " + infoFile, Diag); |
69 | |
70 | SmallVector<StringRef, 64> lines; |
71 | fileBuf.get()->getBuffer().split(A&: lines, Separator: "\n" ); |
72 | |
73 | for (unsigned idx = 0; idx+3 <= lines.size(); idx += 3) { |
74 | StringRef fromFilename = lines[idx]; |
75 | unsigned long long timeModified; |
76 | if (lines[idx+1].getAsInteger(Radix: 10, Result&: timeModified)) |
77 | return report(err: "Invalid file data: '" + lines[idx+1] + "' not a number" , |
78 | Diag); |
79 | StringRef toFilename = lines[idx+2]; |
80 | |
81 | auto origFE = FileMgr->getOptionalFileRef(Filename: fromFilename); |
82 | if (!origFE) { |
83 | if (ignoreIfFilesChanged) |
84 | continue; |
85 | return report(err: "File does not exist: " + fromFilename, Diag); |
86 | } |
87 | auto newFE = FileMgr->getOptionalFileRef(Filename: toFilename); |
88 | if (!newFE) { |
89 | if (ignoreIfFilesChanged) |
90 | continue; |
91 | return report(err: "File does not exist: " + toFilename, Diag); |
92 | } |
93 | |
94 | if ((uint64_t)origFE->getModificationTime() != timeModified) { |
95 | if (ignoreIfFilesChanged) |
96 | continue; |
97 | return report(err: "File was modified: " + fromFilename, Diag); |
98 | } |
99 | |
100 | pairs.push_back(x: std::make_pair(x&: *origFE, y&: *newFE)); |
101 | } |
102 | |
103 | for (unsigned i = 0, e = pairs.size(); i != e; ++i) |
104 | remap(file: pairs[i].first, newfile: pairs[i].second); |
105 | |
106 | return false; |
107 | } |
108 | |
109 | bool FileRemapper::flushToDisk(StringRef outputDir, DiagnosticsEngine &Diag) { |
110 | using namespace llvm::sys; |
111 | |
112 | if (fs::create_directory(path: outputDir)) |
113 | return report(err: "Could not create directory: " + outputDir, Diag); |
114 | |
115 | std::string infoFile = getRemapInfoFile(outputDir); |
116 | return flushToFile(outputPath: infoFile, Diag); |
117 | } |
118 | |
119 | bool FileRemapper::flushToFile(StringRef outputPath, DiagnosticsEngine &Diag) { |
120 | using namespace llvm::sys; |
121 | |
122 | std::error_code EC; |
123 | std::string infoFile = std::string(outputPath); |
124 | llvm::raw_fd_ostream infoOut(infoFile, EC, llvm::sys::fs::OF_Text); |
125 | if (EC) |
126 | return report(err: EC.message(), Diag); |
127 | |
128 | for (MappingsTy::iterator |
129 | I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) { |
130 | |
131 | FileEntryRef origFE = I->first; |
132 | SmallString<200> origPath = StringRef(origFE.getName()); |
133 | fs::make_absolute(path&: origPath); |
134 | infoOut << origPath << '\n'; |
135 | infoOut << (uint64_t)origFE.getModificationTime() << '\n'; |
136 | |
137 | if (const auto *FE = std::get_if<FileEntryRef>(ptr: &I->second)) { |
138 | SmallString<200> newPath = StringRef(FE->getName()); |
139 | fs::make_absolute(path&: newPath); |
140 | infoOut << newPath << '\n'; |
141 | } else { |
142 | |
143 | SmallString<64> tempPath; |
144 | int fd; |
145 | if (fs::createTemporaryFile( |
146 | Prefix: path::filename(path: origFE.getName()), |
147 | Suffix: path::extension(path: origFE.getName()).drop_front(), ResultFD&: fd, ResultPath&: tempPath, |
148 | Flags: llvm::sys::fs::OF_Text)) |
149 | return report(err: "Could not create file: " + tempPath.str(), Diag); |
150 | |
151 | llvm::raw_fd_ostream newOut(fd, /*shouldClose=*/true); |
152 | llvm::MemoryBuffer *mem = std::get<llvm::MemoryBuffer *>(v&: I->second); |
153 | newOut.write(Ptr: mem->getBufferStart(), Size: mem->getBufferSize()); |
154 | newOut.close(); |
155 | |
156 | auto newE = FileMgr->getOptionalFileRef(Filename: tempPath); |
157 | if (newE) { |
158 | remap(file: origFE, newfile: *newE); |
159 | infoOut << newE->getName() << '\n'; |
160 | } |
161 | } |
162 | } |
163 | |
164 | infoOut.close(); |
165 | return false; |
166 | } |
167 | |
168 | bool FileRemapper::overwriteOriginal(DiagnosticsEngine &Diag, |
169 | StringRef outputDir) { |
170 | using namespace llvm::sys; |
171 | |
172 | for (MappingsTy::iterator |
173 | I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) { |
174 | FileEntryRef origFE = I->first; |
175 | assert(std::holds_alternative<llvm::MemoryBuffer *>(I->second)); |
176 | if (!fs::exists(Path: origFE.getName())) |
177 | return report(err: StringRef("File does not exist: " ) + origFE.getName(), |
178 | Diag); |
179 | |
180 | std::error_code EC; |
181 | llvm::raw_fd_ostream Out(origFE.getName(), EC, llvm::sys::fs::OF_None); |
182 | if (EC) |
183 | return report(err: EC.message(), Diag); |
184 | |
185 | llvm::MemoryBuffer *mem = std::get<llvm::MemoryBuffer *>(v&: I->second); |
186 | Out.write(Ptr: mem->getBufferStart(), Size: mem->getBufferSize()); |
187 | Out.close(); |
188 | } |
189 | |
190 | clear(outputDir); |
191 | return false; |
192 | } |
193 | |
194 | void FileRemapper::forEachMapping( |
195 | llvm::function_ref<void(StringRef, StringRef)> CaptureFile, |
196 | llvm::function_ref<void(StringRef, const llvm::MemoryBufferRef &)> |
197 | CaptureBuffer) const { |
198 | for (auto &Mapping : FromToMappings) { |
199 | if (const auto *FE = std::get_if<FileEntryRef>(ptr: &Mapping.second)) { |
200 | CaptureFile(Mapping.first.getName(), FE->getName()); |
201 | continue; |
202 | } |
203 | CaptureBuffer( |
204 | Mapping.first.getName(), |
205 | std::get<llvm::MemoryBuffer *>(v: Mapping.second)->getMemBufferRef()); |
206 | } |
207 | } |
208 | |
209 | void FileRemapper::applyMappings(PreprocessorOptions &PPOpts) const { |
210 | for (MappingsTy::const_iterator |
211 | I = FromToMappings.begin(), E = FromToMappings.end(); I != E; ++I) { |
212 | if (const auto *FE = std::get_if<FileEntryRef>(ptr: &I->second)) { |
213 | PPOpts.addRemappedFile(From: I->first.getName(), To: FE->getName()); |
214 | } else { |
215 | llvm::MemoryBuffer *mem = std::get<llvm::MemoryBuffer *>(v: I->second); |
216 | PPOpts.addRemappedFile(From: I->first.getName(), To: mem); |
217 | } |
218 | } |
219 | |
220 | PPOpts.RetainRemappedFileBuffers = true; |
221 | } |
222 | |
223 | void FileRemapper::remap(StringRef filePath, |
224 | std::unique_ptr<llvm::MemoryBuffer> memBuf) { |
225 | OptionalFileEntryRef File = getOriginalFile(filePath); |
226 | assert(File); |
227 | remap(file: *File, memBuf: std::move(memBuf)); |
228 | } |
229 | |
230 | void FileRemapper::remap(FileEntryRef File, |
231 | std::unique_ptr<llvm::MemoryBuffer> MemBuf) { |
232 | auto [It, New] = FromToMappings.insert(KV: {File, nullptr}); |
233 | if (!New) |
234 | resetTarget(targ&: It->second); |
235 | It->second = MemBuf.release(); |
236 | } |
237 | |
238 | void FileRemapper::remap(FileEntryRef File, FileEntryRef NewFile) { |
239 | auto [It, New] = FromToMappings.insert(KV: {File, nullptr}); |
240 | if (!New) |
241 | resetTarget(targ&: It->second); |
242 | It->second = NewFile; |
243 | ToFromMappings.insert(KV: {NewFile, File}); |
244 | } |
245 | |
246 | OptionalFileEntryRef FileRemapper::getOriginalFile(StringRef filePath) { |
247 | OptionalFileEntryRef File = FileMgr->getOptionalFileRef(Filename: filePath); |
248 | if (!File) |
249 | return std::nullopt; |
250 | // If we are updating a file that overridden an original file, |
251 | // actually update the original file. |
252 | auto I = ToFromMappings.find(Val: *File); |
253 | if (I != ToFromMappings.end()) { |
254 | *File = I->second; |
255 | assert(FromToMappings.contains(*File) && "Original file not in mappings!" ); |
256 | } |
257 | return File; |
258 | } |
259 | |
260 | void FileRemapper::resetTarget(Target &targ) { |
261 | if (std::holds_alternative<llvm::MemoryBuffer *>(v: targ)) { |
262 | llvm::MemoryBuffer *oldmem = std::get<llvm::MemoryBuffer *>(v&: targ); |
263 | delete oldmem; |
264 | } else { |
265 | FileEntryRef toFE = std::get<FileEntryRef>(v&: targ); |
266 | ToFromMappings.erase(Val: toFE); |
267 | } |
268 | } |
269 | |
270 | bool FileRemapper::report(const Twine &err, DiagnosticsEngine &Diag) { |
271 | Diag.Report(DiagID: Diag.getCustomDiagID(L: DiagnosticsEngine::Error, FormatString: "%0" )) |
272 | << err.str(); |
273 | return true; |
274 | } |
275 | |