clang  3.7.0
Replacement.cpp
Go to the documentation of this file.
1 //===--- Replacement.cpp - Framework for clang refactoring tools ----------===//
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 // Implements classes to support/store refactorings.
11 //
12 //===----------------------------------------------------------------------===//
13 
14 #include "clang/Basic/Diagnostic.h"
19 #include "clang/Lex/Lexer.h"
22 #include "llvm/Support/FileSystem.h"
23 #include "llvm/Support/Path.h"
24 #include "llvm/Support/raw_os_ostream.h"
25 
26 namespace clang {
27 namespace tooling {
28 
29 static const char * const InvalidLocation = "";
30 
32  : FilePath(InvalidLocation) {}
33 
34 Replacement::Replacement(StringRef FilePath, unsigned Offset, unsigned Length,
35  StringRef ReplacementText)
36  : FilePath(FilePath), ReplacementRange(Offset, Length),
37  ReplacementText(ReplacementText) {}
38 
40  unsigned Length, StringRef ReplacementText) {
41  setFromSourceLocation(Sources, Start, Length, ReplacementText);
42 }
43 
45  const CharSourceRange &Range,
46  StringRef ReplacementText,
47  const LangOptions &LangOpts) {
48  setFromSourceRange(Sources, Range, ReplacementText, LangOpts);
49 }
50 
52  return FilePath != InvalidLocation;
53 }
54 
55 bool Replacement::apply(Rewriter &Rewrite) const {
56  SourceManager &SM = Rewrite.getSourceMgr();
57  const FileEntry *Entry = SM.getFileManager().getFile(FilePath);
58  if (!Entry)
59  return false;
60  FileID ID;
61  // FIXME: Use SM.translateFile directly.
62  SourceLocation Location = SM.translateFileLineCol(Entry, 1, 1);
63  ID = Location.isValid() ?
64  SM.getFileID(Location) :
66  // FIXME: We cannot check whether Offset + Length is in the file, as
67  // the remapping API is not public in the RewriteBuffer.
68  const SourceLocation Start =
69  SM.getLocForStartOfFile(ID).
70  getLocWithOffset(ReplacementRange.getOffset());
71  // ReplaceText returns false on success.
72  // ReplaceText only fails if the source location is not a file location, in
73  // which case we already returned false earlier.
74  bool RewriteSucceeded = !Rewrite.ReplaceText(
75  Start, ReplacementRange.getLength(), ReplacementText);
76  assert(RewriteSucceeded);
77  return RewriteSucceeded;
78 }
79 
80 std::string Replacement::toString() const {
81  std::string Result;
82  llvm::raw_string_ostream Stream(Result);
83  Stream << FilePath << ": " << ReplacementRange.getOffset() << ":+"
84  << ReplacementRange.getLength() << ":\"" << ReplacementText << "\"";
85  return Stream.str();
86 }
87 
88 bool operator<(const Replacement &LHS, const Replacement &RHS) {
89  if (LHS.getOffset() != RHS.getOffset())
90  return LHS.getOffset() < RHS.getOffset();
91 
92  // Apply longer replacements first, specifically so that deletions are
93  // executed before insertions. It is (hopefully) never the intention to
94  // delete parts of newly inserted code.
95  if (LHS.getLength() != RHS.getLength())
96  return LHS.getLength() > RHS.getLength();
97 
98  if (LHS.getFilePath() != RHS.getFilePath())
99  return LHS.getFilePath() < RHS.getFilePath();
100  return LHS.getReplacementText() < RHS.getReplacementText();
101 }
102 
103 bool operator==(const Replacement &LHS, const Replacement &RHS) {
104  return LHS.getOffset() == RHS.getOffset() &&
105  LHS.getLength() == RHS.getLength() &&
106  LHS.getFilePath() == RHS.getFilePath() &&
107  LHS.getReplacementText() == RHS.getReplacementText();
108 }
109 
110 void Replacement::setFromSourceLocation(const SourceManager &Sources,
111  SourceLocation Start, unsigned Length,
112  StringRef ReplacementText) {
113  const std::pair<FileID, unsigned> DecomposedLocation =
114  Sources.getDecomposedLoc(Start);
115  const FileEntry *Entry = Sources.getFileEntryForID(DecomposedLocation.first);
116  if (Entry) {
117  // Make FilePath absolute so replacements can be applied correctly when
118  // relative paths for files are used.
119  llvm::SmallString<256> FilePath(Entry->getName());
120  std::error_code EC = llvm::sys::fs::make_absolute(FilePath);
121  this->FilePath = EC ? FilePath.c_str() : Entry->getName();
122  } else {
123  this->FilePath = InvalidLocation;
124  }
125  this->ReplacementRange = Range(DecomposedLocation.second, Length);
126  this->ReplacementText = ReplacementText;
127 }
128 
129 // FIXME: This should go into the Lexer, but we need to figure out how
130 // to handle ranges for refactoring in general first - there is no obvious
131 // good way how to integrate this into the Lexer yet.
132 static int getRangeSize(const SourceManager &Sources,
133  const CharSourceRange &Range,
134  const LangOptions &LangOpts) {
135  SourceLocation SpellingBegin = Sources.getSpellingLoc(Range.getBegin());
136  SourceLocation SpellingEnd = Sources.getSpellingLoc(Range.getEnd());
137  std::pair<FileID, unsigned> Start = Sources.getDecomposedLoc(SpellingBegin);
138  std::pair<FileID, unsigned> End = Sources.getDecomposedLoc(SpellingEnd);
139  if (Start.first != End.first) return -1;
140  if (Range.isTokenRange())
141  End.second += Lexer::MeasureTokenLength(SpellingEnd, Sources, LangOpts);
142  return End.second - Start.second;
143 }
144 
145 void Replacement::setFromSourceRange(const SourceManager &Sources,
146  const CharSourceRange &Range,
147  StringRef ReplacementText,
148  const LangOptions &LangOpts) {
149  setFromSourceLocation(Sources, Sources.getSpellingLoc(Range.getBegin()),
150  getRangeSize(Sources, Range, LangOpts),
151  ReplacementText);
152 }
153 
154 unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position) {
155  unsigned NewPosition = Position;
156  for (Replacements::iterator I = Replaces.begin(), E = Replaces.end(); I != E;
157  ++I) {
158  if (I->getOffset() >= Position)
159  break;
160  if (I->getOffset() + I->getLength() > Position)
161  NewPosition += I->getOffset() + I->getLength() - Position;
162  NewPosition += I->getReplacementText().size() - I->getLength();
163  }
164  return NewPosition;
165 }
166 
167 // FIXME: Remove this function when Replacements is implemented as std::vector
168 // instead of std::set.
169 unsigned shiftedCodePosition(const std::vector<Replacement> &Replaces,
170  unsigned Position) {
171  unsigned NewPosition = Position;
172  for (std::vector<Replacement>::const_iterator I = Replaces.begin(),
173  E = Replaces.end();
174  I != E; ++I) {
175  if (I->getOffset() >= Position)
176  break;
177  if (I->getOffset() + I->getLength() > Position)
178  NewPosition += I->getOffset() + I->getLength() - Position;
179  NewPosition += I->getReplacementText().size() - I->getLength();
180  }
181  return NewPosition;
182 }
183 
184 void deduplicate(std::vector<Replacement> &Replaces,
185  std::vector<Range> &Conflicts) {
186  if (Replaces.empty())
187  return;
188 
189  auto LessNoPath = [](const Replacement &LHS, const Replacement &RHS) {
190  if (LHS.getOffset() != RHS.getOffset())
191  return LHS.getOffset() < RHS.getOffset();
192  if (LHS.getLength() != RHS.getLength())
193  return LHS.getLength() < RHS.getLength();
194  return LHS.getReplacementText() < RHS.getReplacementText();
195  };
196 
197  auto EqualNoPath = [](const Replacement &LHS, const Replacement &RHS) {
198  return LHS.getOffset() == RHS.getOffset() &&
199  LHS.getLength() == RHS.getLength() &&
200  LHS.getReplacementText() == RHS.getReplacementText();
201  };
202 
203  // Deduplicate. We don't want to deduplicate based on the path as we assume
204  // that all replacements refer to the same file (or are symlinks).
205  std::sort(Replaces.begin(), Replaces.end(), LessNoPath);
206  Replaces.erase(std::unique(Replaces.begin(), Replaces.end(), EqualNoPath),
207  Replaces.end());
208 
209  // Detect conflicts
210  Range ConflictRange(Replaces.front().getOffset(),
211  Replaces.front().getLength());
212  unsigned ConflictStart = 0;
213  unsigned ConflictLength = 1;
214  for (unsigned i = 1; i < Replaces.size(); ++i) {
215  Range Current(Replaces[i].getOffset(), Replaces[i].getLength());
216  if (ConflictRange.overlapsWith(Current)) {
217  // Extend conflicted range
218  ConflictRange = Range(ConflictRange.getOffset(),
219  std::max(ConflictRange.getLength(),
220  Current.getOffset() + Current.getLength() -
221  ConflictRange.getOffset()));
222  ++ConflictLength;
223  } else {
224  if (ConflictLength > 1)
225  Conflicts.push_back(Range(ConflictStart, ConflictLength));
226  ConflictRange = Current;
227  ConflictStart = i;
228  ConflictLength = 1;
229  }
230  }
231 
232  if (ConflictLength > 1)
233  Conflicts.push_back(Range(ConflictStart, ConflictLength));
234 }
235 
236 bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite) {
237  bool Result = true;
238  for (Replacements::const_iterator I = Replaces.begin(),
239  E = Replaces.end();
240  I != E; ++I) {
241  if (I->isApplicable()) {
242  Result = I->apply(Rewrite) && Result;
243  } else {
244  Result = false;
245  }
246  }
247  return Result;
248 }
249 
250 // FIXME: Remove this function when Replacements is implemented as std::vector
251 // instead of std::set.
252 bool applyAllReplacements(const std::vector<Replacement> &Replaces,
253  Rewriter &Rewrite) {
254  bool Result = true;
255  for (std::vector<Replacement>::const_iterator I = Replaces.begin(),
256  E = Replaces.end();
257  I != E; ++I) {
258  if (I->isApplicable()) {
259  Result = I->apply(Rewrite) && Result;
260  } else {
261  Result = false;
262  }
263  }
264  return Result;
265 }
266 
267 std::string applyAllReplacements(StringRef Code, const Replacements &Replaces) {
268  FileManager Files((FileSystemOptions()));
269  DiagnosticsEngine Diagnostics(
271  new DiagnosticOptions);
272  SourceManager SourceMgr(Diagnostics, Files);
273  Rewriter Rewrite(SourceMgr, LangOptions());
274  std::unique_ptr<llvm::MemoryBuffer> Buf =
275  llvm::MemoryBuffer::getMemBuffer(Code, "<stdin>");
276  const clang::FileEntry *Entry =
277  Files.getVirtualFile("<stdin>", Buf->getBufferSize(), 0);
278  SourceMgr.overrideFileContents(Entry, std::move(Buf));
279  FileID ID =
281  for (Replacements::const_iterator I = Replaces.begin(), E = Replaces.end();
282  I != E; ++I) {
283  Replacement Replace("<stdin>", I->getOffset(), I->getLength(),
284  I->getReplacementText());
285  if (!Replace.apply(Rewrite))
286  return "";
287  }
288  std::string Result;
289  llvm::raw_string_ostream OS(Result);
290  Rewrite.getEditBuffer(ID).write(OS);
291  OS.flush();
292  return Result;
293 }
294 
295 } // end namespace tooling
296 } // end namespace clang
297 
int Position
SourceLocation getBegin() const
Implements support for file system lookup, file system caching, and directory search management...
Definition: FileManager.h:115
SourceManager & getSourceMgr() const
Definition: Rewriter.h:64
Defines the clang::FileManager interface and associated types.
SourceLocation getSpellingLoc(SourceLocation Loc) const
Given a SourceLocation object, return the spelling location referenced by the ID. ...
Defines the SourceManager interface.
std::set< Replacement > Replacements
A set of Replacements. FIXME: Change to a vector and deduplicate in the RefactoringTool.
Definition: Replacement.h:141
unsigned getLength() const
Definition: Replacement.h:44
bool operator==(const Replacement &LHS, const Replacement &RHS)
Equal-to operator between two Replacements.
void deduplicate(std::vector< Replacement > &Replaces, std::vector< Range > &Conflicts)
Removes duplicate Replacements and reports if Replacements conflict with one another. All Replacements are assumed to be in the same file.
bool apply(Rewriter &Rewrite) const
Applies the replacement on the Rewriter.
Definition: Replacement.cpp:55
RewriteBuffer & getEditBuffer(FileID FID)
Definition: Rewriter.cpp:225
bool applyAllReplacements(const Replacements &Replaces, Rewriter &Rewrite)
Apply all replacements in Replaces to the Rewriter Rewrite.
StringRef getReplacementText() const
Definition: Replacement.h:110
unsigned getLength() const
Definition: Replacement.h:109
static int getRangeSize(const SourceManager &Sources, const CharSourceRange &Range, const LangOptions &LangOpts)
bool ReplaceText(SourceLocation Start, unsigned OrigLength, StringRef NewStr)
Definition: Rewriter.cpp:305
Keeps track of the various options that can be enabled, which controls the dialect of C or C++ that i...
Definition: LangOptions.h:48
uint32_t Offset
Definition: CacheTokens.cpp:43
Concrete class used by the front-end to report problems and issues.
Definition: Diagnostic.h:135
SourceLocation translateFileLineCol(const FileEntry *SourceFile, unsigned Line, unsigned Col) const
Get the source location for the given file:line:col triplet.
Replacement()
Creates an invalid (not applicable) replacement.
Definition: Replacement.cpp:31
const FileEntry * getFile(StringRef Filename, bool OpenFile=false, bool CacheFailure=true)
Lookup, cache, and verify the specified file (real or virtual).
const FileEntry * getFileEntryForID(FileID FID) const
Returns the FileEntry record for the provided FileID.
FileID getFileID(SourceLocation SpellingLoc) const
Return the FileID for a SourceLocation.
A text replacement.
Definition: Replacement.h:70
A source range independent of the SourceManager.
Definition: Replacement.h:36
const SmallVectorImpl< AnnotatedLine * >::const_iterator End
bool operator<(const Replacement &LHS, const Replacement &RHS)
Less-than operator between two Replacements.
Definition: Replacement.cpp:88
ID
Defines the set of possible language-specific address spaces.
Definition: AddressSpaces.h:27
SourceManager & SM
Represents a character-granular source range.
SourceLocation getEnd() const
static unsigned MeasureTokenLength(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
Definition: Lexer.cpp:406
FileID createFileID(const FileEntry *SourceFile, SourceLocation IncludePos, SrcMgr::CharacteristicKind FileCharacter, int LoadedID=0, unsigned LoadedOffset=0)
Create a new FileID that represents the specified file being #included from the specified IncludePosi...
SourceManager & SourceMgr
Definition: Format.cpp:1205
FileManager & getFileManager() const
unsigned shiftedCodePosition(const Replacements &Replaces, unsigned Position)
Calculates how a code Position is shifted when Replaces are applied.
unsigned getOffset() const
Definition: Replacement.h:108
The result type of a method or function.
void overrideFileContents(const FileEntry *SourceFile, llvm::MemoryBuffer *Buffer, bool DoNotFree)
Override the contents of the given source file by providing an already-allocated buffer.
bool isTokenRange() const
Return true if the end of this range specifies the start of the last token. Return false if the end o...
static const char *const InvalidLocation
Definition: Replacement.cpp:29
const char * getName() const
Definition: FileManager.h:84
Encodes a location in the source. The SourceManager can decode this to get at the full include stack...
bool isValid() const
Return true if this is a valid SourceLocation object.
Options for controlling the compiler diagnostics engine.
StringRef getFilePath() const
Accessors.
Definition: Replacement.h:107
Cached information about one file (either on disk or in the virtual file system). ...
Definition: FileManager.h:53
unsigned getOffset() const
Accessors.
Definition: Replacement.h:43
const FileEntry * getVirtualFile(StringRef Filename, off_t Size, time_t ModificationTime)
Retrieve a file entry for a "virtual" file that acts as if there were a file with the given name on d...
An opaque identifier used by SourceManager which refers to a source file (MemoryBuffer) along with it...
std::string toString() const
Returns a human readable string representation.
Definition: Replacement.cpp:80
Used for handling and querying diagnostic IDs.
Defines the Diagnostic-related interfaces.
raw_ostream & write(raw_ostream &Stream) const
Write to Stream the result of applying all changes to the original buffer. Note that it isn't safe to...
Definition: Rewriter.cpp:27
bool isApplicable() const
Returns whether this replacement can be applied to a file.
Definition: Replacement.cpp:51
Keeps track of options that affect how file operations are performed.
FormatToken * Current
Defines the Diagnostic IDs-related interfaces.
SourceLocation getLocForStartOfFile(FileID FID) const
Return the source location corresponding to the first byte of the specified file. ...
std::pair< FileID, unsigned > getDecomposedLoc(SourceLocation Loc) const
Decompose the specified location into a raw FileID + Offset pair.
This class handles loading and caching of source files into memory.