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