clang-tools  4.0.0
ApplyReplacements.cpp
Go to the documentation of this file.
1 //===-- ApplyReplacements.cpp - Apply and deduplicate replacements --------===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 ///
10 /// \file
11 /// \brief This file provides the implementation for deduplicating, detecting
12 /// conflicts in, and applying collections of Replacements.
13 ///
14 /// FIXME: Use Diagnostics for output instead of llvm::errs().
15 ///
16 //===----------------------------------------------------------------------===//
18 #include "clang/Basic/LangOptions.h"
19 #include "clang/Basic/SourceManager.h"
20 #include "clang/Format/Format.h"
21 #include "clang/Lex/Lexer.h"
22 #include "clang/Rewrite/Core/Rewriter.h"
23 #include "clang/Tooling/DiagnosticsYaml.h"
24 #include "clang/Tooling/ReplacementsYaml.h"
25 #include "llvm/ADT/ArrayRef.h"
26 #include "llvm/Support/FileSystem.h"
27 #include "llvm/Support/MemoryBuffer.h"
28 #include "llvm/Support/Path.h"
29 #include "llvm/Support/raw_ostream.h"
30 
31 using namespace llvm;
32 using namespace clang;
33 
34 static void eatDiagnostics(const SMDiagnostic &, void *) {}
35 
36 namespace clang {
37 namespace replace {
38 
40  const llvm::StringRef Directory, TUReplacements &TUs,
41  TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics) {
42  using namespace llvm::sys::fs;
43  using namespace llvm::sys::path;
44 
45  std::error_code ErrorCode;
46 
47  for (recursive_directory_iterator I(Directory, ErrorCode), E;
48  I != E && !ErrorCode; I.increment(ErrorCode)) {
49  if (filename(I->path())[0] == '.') {
50  // Indicate not to descend into directories beginning with '.'
51  I.no_push();
52  continue;
53  }
54 
55  if (extension(I->path()) != ".yaml")
56  continue;
57 
58  TUFiles.push_back(I->path());
59 
60  ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
61  MemoryBuffer::getFile(I->path());
62  if (std::error_code BufferError = Out.getError()) {
63  errs() << "Error reading " << I->path() << ": " << BufferError.message()
64  << "\n";
65  continue;
66  }
67 
68  yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
69  tooling::TranslationUnitReplacements TU;
70  YIn >> TU;
71  if (YIn.error()) {
72  // File doesn't appear to be a header change description. Ignore it.
73  continue;
74  }
75 
76  // Only keep files that properly parse.
77  TUs.push_back(TU);
78  }
79 
80  return ErrorCode;
81 }
82 
83 std::error_code
85  TUDiagnostics &TUs, TUReplacementFiles &TUFiles,
86  clang::DiagnosticsEngine &Diagnostics) {
87  using namespace llvm::sys::fs;
88  using namespace llvm::sys::path;
89 
90  std::error_code ErrorCode;
91 
92  for (recursive_directory_iterator I(Directory, ErrorCode), E;
93  I != E && !ErrorCode; I.increment(ErrorCode)) {
94  if (filename(I->path())[0] == '.') {
95  // Indicate not to descend into directories beginning with '.'
96  I.no_push();
97  continue;
98  }
99 
100  if (extension(I->path()) != ".yaml")
101  continue;
102 
103  TUFiles.push_back(I->path());
104 
105  ErrorOr<std::unique_ptr<MemoryBuffer>> Out =
106  MemoryBuffer::getFile(I->path());
107  if (std::error_code BufferError = Out.getError()) {
108  errs() << "Error reading " << I->path() << ": " << BufferError.message()
109  << "\n";
110  continue;
111  }
112 
113  yaml::Input YIn(Out.get()->getBuffer(), nullptr, &eatDiagnostics);
114  tooling::TranslationUnitDiagnostics TU;
115  YIn >> TU;
116  if (YIn.error()) {
117  // File doesn't appear to be a header change description. Ignore it.
118  continue;
119  }
120 
121  // Only keep files that properly parse.
122  TUs.push_back(TU);
123  }
124 
125  return ErrorCode;
126 }
127 
128 /// \brief Dumps information for a sequence of conflicting Replacements.
129 ///
130 /// \param[in] File FileEntry for the file the conflicting Replacements are
131 /// for.
132 /// \param[in] ConflictingReplacements List of conflicting Replacements.
133 /// \param[in] SM SourceManager used for reporting.
134 static void reportConflict(
135  const FileEntry *File,
136  const llvm::ArrayRef<clang::tooling::Replacement> ConflictingReplacements,
137  SourceManager &SM) {
138  FileID FID = SM.translateFile(File);
139  if (FID.isInvalid())
140  FID = SM.createFileID(File, SourceLocation(), SrcMgr::C_User);
141 
142  // FIXME: Output something a little more user-friendly (e.g. unified diff?)
143  errs() << "The following changes conflict:\n";
144  for (const tooling::Replacement &R : ConflictingReplacements) {
145  if (R.getLength() == 0) {
146  errs() << " Insert at " << SM.getLineNumber(FID, R.getOffset()) << ":"
147  << SM.getColumnNumber(FID, R.getOffset()) << " "
148  << R.getReplacementText() << "\n";
149  } else {
150  if (R.getReplacementText().empty())
151  errs() << " Remove ";
152  else
153  errs() << " Replace ";
154 
155  errs() << SM.getLineNumber(FID, R.getOffset()) << ":"
156  << SM.getColumnNumber(FID, R.getOffset()) << "-"
157  << SM.getLineNumber(FID, R.getOffset() + R.getLength() - 1) << ":"
158  << SM.getColumnNumber(FID, R.getOffset() + R.getLength() - 1);
159 
160  if (R.getReplacementText().empty())
161  errs() << "\n";
162  else
163  errs() << " with \"" << R.getReplacementText() << "\"\n";
164  }
165  }
166 }
167 
168 // FIXME: Remove this function after changing clang-apply-replacements to use
169 // Replacements class.
170 bool applyAllReplacements(const std::vector<tooling::Replacement> &Replaces,
171  Rewriter &Rewrite) {
172  bool Result = true;
173  for (auto I = Replaces.begin(), E = Replaces.end(); I != E; ++I) {
174  if (I->isApplicable()) {
175  Result = I->apply(Rewrite) && Result;
176  } else {
177  Result = false;
178  }
179  }
180  return Result;
181 }
182 
183 // FIXME: moved from libToolingCore. remove this when std::vector<Replacement>
184 // is replaced with tooling::Replacements class.
185 static void deduplicate(std::vector<tooling::Replacement> &Replaces,
186  std::vector<tooling::Range> &Conflicts) {
187  if (Replaces.empty())
188  return;
189 
190  auto LessNoPath = [](const tooling::Replacement &LHS,
191  const tooling::Replacement &RHS) {
192  if (LHS.getOffset() != RHS.getOffset())
193  return LHS.getOffset() < RHS.getOffset();
194  if (LHS.getLength() != RHS.getLength())
195  return LHS.getLength() < RHS.getLength();
196  return LHS.getReplacementText() < RHS.getReplacementText();
197  };
198 
199  auto EqualNoPath = [](const tooling::Replacement &LHS,
200  const tooling::Replacement &RHS) {
201  return LHS.getOffset() == RHS.getOffset() &&
202  LHS.getLength() == RHS.getLength() &&
203  LHS.getReplacementText() == RHS.getReplacementText();
204  };
205 
206  // Deduplicate. We don't want to deduplicate based on the path as we assume
207  // that all replacements refer to the same file (or are symlinks).
208  std::sort(Replaces.begin(), Replaces.end(), LessNoPath);
209  Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath),
210  Replaces.end());
211 
212  // Detect conflicts
213  tooling::Range ConflictRange(Replaces.front().getOffset(),
214  Replaces.front().getLength());
215  unsigned ConflictStart = 0;
216  unsigned ConflictLength = 1;
217  for (unsigned i = 1; i < Replaces.size(); ++i) {
218  tooling::Range Current(Replaces[i].getOffset(), Replaces[i].getLength());
219  if (ConflictRange.overlapsWith(Current)) {
220  // Extend conflicted range
221  ConflictRange =
222  tooling::Range(ConflictRange.getOffset(),
223  std::max(ConflictRange.getLength(),
224  Current.getOffset() + Current.getLength() -
225  ConflictRange.getOffset()));
226  ++ConflictLength;
227  } else {
228  if (ConflictLength > 1)
229  Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength));
230  ConflictRange = Current;
231  ConflictStart = i;
232  ConflictLength = 1;
233  }
234  }
235 
236  if (ConflictLength > 1)
237  Conflicts.push_back(tooling::Range(ConflictStart, ConflictLength));
238 }
239 
240 /// \brief Deduplicates and tests for conflicts among the replacements for each
241 /// file in \c Replacements. Any conflicts found are reported.
242 ///
243 /// \post Replacements[i].getOffset() <= Replacements[i+1].getOffset().
244 ///
245 /// \param[in,out] Replacements Container of all replacements grouped by file
246 /// to be deduplicated and checked for conflicts.
247 /// \param[in] SM SourceManager required for conflict reporting.
248 ///
249 /// \returns \parblock
250 /// \li true if conflicts were detected
251 /// \li false if no conflicts were detected
253  SourceManager &SM) {
254  bool conflictsFound = false;
255 
256  for (auto &FileAndReplacements : Replacements) {
257  const FileEntry *Entry = FileAndReplacements.first;
258  auto &Replacements = FileAndReplacements.second;
259  assert(Entry != nullptr && "No file entry!");
260 
261  std::vector<tooling::Range> Conflicts;
262  deduplicate(FileAndReplacements.second, Conflicts);
263 
264  if (Conflicts.empty())
265  continue;
266 
267  conflictsFound = true;
268 
269  errs() << "There are conflicting changes to " << Entry->getName() << ":\n";
270 
271  for (const tooling::Range &Conflict : Conflicts) {
272  auto ConflictingReplacements = llvm::makeArrayRef(
273  &Replacements[Conflict.getOffset()], Conflict.getLength());
274  reportConflict(Entry, ConflictingReplacements, SM);
275  }
276  }
277 
278  return conflictsFound;
279 }
280 
282  FileToReplacementsMap &GroupedReplacements,
283  clang::SourceManager &SM) {
284 
285  // Group all replacements by target file.
286  std::set<StringRef> Warned;
287  for (const auto &TU : TUs) {
288  for (const tooling::Replacement &R : TU.Replacements) {
289  // Use the file manager to deduplicate paths. FileEntries are
290  // automatically canonicalized.
291  const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath());
292  if (!Entry && Warned.insert(R.getFilePath()).second) {
293  errs() << "Described file '" << R.getFilePath()
294  << "' doesn't exist. Ignoring...\n";
295  continue;
296  }
297  GroupedReplacements[Entry].push_back(R);
298  }
299  }
300 
301  // Ask clang to deduplicate and report conflicts.
302  return !deduplicateAndDetectConflicts(GroupedReplacements, SM);
303 }
304 
306  FileToReplacementsMap &GroupedReplacements,
307  clang::SourceManager &SM) {
308 
309  // Group all replacements by target file.
310  std::set<StringRef> Warned;
311  for (const auto &TU : TUs) {
312  for (const auto &D : TU.Diagnostics) {
313  for (const auto &Fix : D.Fix) {
314  for (const tooling::Replacement &R : Fix.second) {
315  // Use the file manager to deduplicate paths. FileEntries are
316  // automatically canonicalized.
317  const FileEntry *Entry = SM.getFileManager().getFile(R.getFilePath());
318  if (!Entry && Warned.insert(R.getFilePath()).second) {
319  errs() << "Described file '" << R.getFilePath()
320  << "' doesn't exist. Ignoring...\n";
321  continue;
322  }
323  GroupedReplacements[Entry].push_back(R);
324  }
325  }
326  }
327  }
328 
329  // Ask clang to deduplicate and report conflicts.
330  return !deduplicateAndDetectConflicts(GroupedReplacements, SM);
331 }
332 
333 bool applyReplacements(const FileToReplacementsMap &GroupedReplacements,
334  clang::Rewriter &Rewrites) {
335 
336  // Apply all changes
337  //
338  // FIXME: No longer certain GroupedReplacements is really the best kind of
339  // data structure for applying replacements. Rewriter certainly doesn't care.
340  // However, until we nail down the design of ReplacementGroups, might as well
341  // leave this as is.
342  for (const auto &FileAndReplacements : GroupedReplacements) {
343  if (!applyAllReplacements(FileAndReplacements.second, Rewrites))
344  return false;
345  }
346 
347  return true;
348 }
349 
351  const std::vector<clang::tooling::Replacement> &Replaces) {
352  RangeVector ChangedRanges;
353 
354  // Generate the new ranges from the replacements.
355  int Shift = 0;
356  for (const tooling::Replacement &R : Replaces) {
357  unsigned Offset = R.getOffset() + Shift;
358  unsigned Length = R.getReplacementText().size();
359  Shift += Length - R.getLength();
360  ChangedRanges.push_back(tooling::Range(Offset, Length));
361  }
362 
363  return ChangedRanges;
364 }
365 
366 bool writeFiles(const clang::Rewriter &Rewrites) {
367 
368  for (auto BufferI = Rewrites.buffer_begin(), BufferE = Rewrites.buffer_end();
369  BufferI != BufferE; ++BufferI) {
370  StringRef FileName =
371  Rewrites.getSourceMgr().getFileEntryForID(BufferI->first)->getName();
372 
373  std::error_code EC;
374  llvm::raw_fd_ostream FileStream(FileName, EC, llvm::sys::fs::F_Text);
375  if (EC) {
376  errs() << "Warning: Could not write to " << EC.message() << "\n";
377  continue;
378  }
379  BufferI->second.write(FileStream);
380  }
381 
382  return true;
383 }
384 
386  clang::DiagnosticsEngine &Diagnostics) {
387  bool Success = true;
388  for (const auto &Filename : Files) {
389  std::error_code Error = llvm::sys::fs::remove(Filename);
390  if (Error) {
391  Success = false;
392  // FIXME: Use Diagnostics for outputting errors.
393  errs() << "Error deleting file: " << Filename << "\n";
394  errs() << Error.message() << "\n";
395  errs() << "Please delete the file manually\n";
396  }
397  }
398  return Success;
399 }
400 
401 } // end namespace replace
402 } // end namespace clang
static bool deduplicateAndDetectConflicts(FileToReplacementsMap &Replacements, SourceManager &SM)
Deduplicates and tests for conflicts among the replacements for each file in Replacements.
static void reportConflict(const FileEntry *File, const llvm::ArrayRef< clang::tooling::Replacement > ConflictingReplacements, SourceManager &SM)
Dumps information for a sequence of conflicting Replacements.
llvm::DenseMap< const clang::FileEntry *, std::vector< clang::tooling::Replacement > > FileToReplacementsMap
Map mapping file name to Replacements targeting that file.
bool deleteReplacementFiles(const TUReplacementFiles &Files, clang::DiagnosticsEngine &Diagnostics)
Delete the replacement files.
HeaderHandle File
std::vector< clang::tooling::TranslationUnitReplacements > TUReplacements
Collection of TranslationUnitReplacements.
static void eatDiagnostics(const SMDiagnostic &, void *)
bool writeFiles(const clang::Rewriter &Rewrites)
Write the contents of FileContents to disk.
static cl::opt< std::string > Directory(cl::Positional, cl::Required, cl::desc("<Search Root Directory>"))
SourceManager & SM
static cl::opt< std::string > Input("input", cl::desc("YAML file to load oldname-newname pairs from."), cl::Optional, cl::cat(ClangRenameOptions))
std::string Filename
Filename as a string.
std::vector< clang::tooling::Range > RangeVector
Collection of source ranges.
static void deduplicate(std::vector< tooling::Replacement > &Replaces, std::vector< tooling::Range > &Conflicts)
RangeVector calculateChangedRanges(const std::vector< clang::tooling::Replacement > &Replacements)
Given a collection of Replacements for a single file, produces a list of source ranges that enclose t...
std::error_code collectReplacementsFromDirectory(const llvm::StringRef Directory, TUReplacements &TUs, TUReplacementFiles &TUFiles, clang::DiagnosticsEngine &Diagnostics)
Recursively descends through a directory structure rooted at Directory and attempts to deserialize *...
CharSourceRange Range
SourceRange for the file name.
FileManager Files
Definition: ClangTidy.cpp:239
This file provides the interface for deduplicating, detecting conflicts in, and applying collections ...
std::vector< clang::tooling::TranslationUnitDiagnostics > TUDiagnostics
Collection of TranslationUniDiagnostics.
std::vector< std::string > TUReplacementFiles
Collection of TranslationUnitReplacement files.
static cl::opt< bool > Fix("fix", cl::desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), cl::cat(ClangTidyCategory))
bool applyAllReplacements(const std::vector< tooling::Replacement > &Replaces, Rewriter &Rewrite)
bool mergeAndDeduplicate(const TUReplacements &TUs, FileToReplacementsMap &GroupedReplacements, clang::SourceManager &SM)
Deduplicate, check for conflicts, and apply all Replacements stored in TUs.
const NamedDecl * Result
Definition: USRFinder.cpp:162
static bool applyReplacements(const std::vector< tooling::Replacement > &Replacements, std::string &Result, DiagnosticsEngine &Diagnostics)
Apply Replacements and return the new file contents.