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(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(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 42 std::string FileRemapper::getRemapInfoFile(StringRef outputDir) { 43 assert(!outputDir.empty()); 44 SmallString<128> InfoFile = outputDir; 45 llvm::sys::path::append(InfoFile, "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(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(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(infoFile, /*IsText=*/true); 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 auto origFE = FileMgr->getOptionalFileRef(fromFilename); 82 if (!origFE) { 83 if (ignoreIfFilesChanged) 84 continue; 85 return report("File does not exist: " + fromFilename, Diag); 86 } 87 auto newFE = FileMgr->getOptionalFileRef(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 109 bool 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 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(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(origPath); 134 infoOut << origPath << '\n'; 135 infoOut << (uint64_t)origFE.getModificationTime() << '\n'; 136 137 if (const auto *FE = std::get_if<FileEntryRef>(&I->second)) { 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( 146 path::filename(origFE.getName()), 147 path::extension(origFE.getName()).drop_front(), fd, tempPath, 148 llvm::sys::fs::OF_Text)) 149 return report("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 *>(I->second); 153 newOut.write(mem->getBufferStart(), mem->getBufferSize()); 154 newOut.close(); 155 156 auto newE = FileMgr->getOptionalFileRef(tempPath); 157 if (newE) { 158 remap(origFE, *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(origFE.getName())) 177 return report(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(EC.message(), Diag); 184 185 llvm::MemoryBuffer *mem = std::get<llvm::MemoryBuffer *>(I->second); 186 Out.write(mem->getBufferStart(), 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>(&Mapping.second)) { 200 CaptureFile(Mapping.first.getName(), FE->getName()); 201 continue; 202 } 203 CaptureBuffer( 204 Mapping.first.getName(), 205 std::get<llvm::MemoryBuffer *>(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>(&I->second)) { 213 PPOpts.addRemappedFile(I->first.getName(), FE->getName()); 214 } else { 215 llvm::MemoryBuffer *mem = std::get<llvm::MemoryBuffer *>(I->second); 216 PPOpts.addRemappedFile(I->first.getName(), 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, std::move(memBuf)); 228 } 229 230 void FileRemapper::remap(FileEntryRef File, 231 std::unique_ptr<llvm::MemoryBuffer> MemBuf) { 232 auto [It, New] = FromToMappings.insert({File, nullptr}); 233 if (!New) 234 resetTarget(It->second); 235 It->second = MemBuf.release(); 236 } 237 238 void FileRemapper::remap(FileEntryRef File, FileEntryRef NewFile) { 239 auto [It, New] = FromToMappings.insert({File, nullptr}); 240 if (!New) 241 resetTarget(It->second); 242 It->second = NewFile; 243 ToFromMappings.insert({NewFile, File}); 244 } 245 246 OptionalFileEntryRef FileRemapper::getOriginalFile(StringRef filePath) { 247 OptionalFileEntryRef File = FileMgr->getOptionalFileRef(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(*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 *>(targ)) { 262 llvm::MemoryBuffer *oldmem = std::get<llvm::MemoryBuffer *>(targ); 263 delete oldmem; 264 } else { 265 FileEntryRef toFE = std::get<FileEntryRef>(targ); 266 ToFromMappings.erase(toFE); 267 } 268 } 269 270 bool FileRemapper::report(const Twine &err, DiagnosticsEngine &Diag) { 271 Diag.Report(Diag.getCustomDiagID(DiagnosticsEngine::Error, "%0")) 272 << err.str(); 273 return true; 274 } 275