diff options
Diffstat (limited to 'clang/lib/Edit')
| -rw-r--r-- | clang/lib/Edit/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | clang/lib/Edit/Commit.cpp | 345 | ||||
| -rw-r--r-- | clang/lib/Edit/EditedSource.cpp | 329 | ||||
| -rw-r--r-- | clang/lib/Edit/Makefile | 14 | ||||
| -rw-r--r-- | clang/lib/Edit/RewriteObjCFoundationAPI.cpp | 587 | 
5 files changed, 1282 insertions, 0 deletions
diff --git a/clang/lib/Edit/CMakeLists.txt b/clang/lib/Edit/CMakeLists.txt new file mode 100644 index 0000000..c87478c --- /dev/null +++ b/clang/lib/Edit/CMakeLists.txt @@ -0,0 +1,7 @@ +set(LLVM_USED_LIBS clangBasic clangAST clangLex) + +add_clang_library(clangEdit +  Commit.cpp +  EditedSource.cpp +  RewriteObjCFoundationAPI.cpp +  ) diff --git a/clang/lib/Edit/Commit.cpp b/clang/lib/Edit/Commit.cpp new file mode 100644 index 0000000..c45ee1f --- /dev/null +++ b/clang/lib/Edit/Commit.cpp @@ -0,0 +1,345 @@ +//===----- Commit.cpp - A unit of edits -----------------------------------===// +// +//                     The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Edit/Commit.h" +#include "clang/Edit/EditedSource.h" +#include "clang/Lex/Lexer.h" +#include "clang/Lex/PreprocessingRecord.h" +#include "clang/Basic/SourceManager.h" + +using namespace clang; +using namespace edit; + +SourceLocation Commit::Edit::getFileLocation(SourceManager &SM) const { +  SourceLocation Loc = SM.getLocForStartOfFile(Offset.getFID()); +  Loc = Loc.getLocWithOffset(Offset.getOffset()); +  assert(Loc.isFileID()); +  return Loc; +} + +CharSourceRange Commit::Edit::getFileRange(SourceManager &SM) const { +  SourceLocation Loc = getFileLocation(SM); +  return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length)); +} + +CharSourceRange Commit::Edit::getInsertFromRange(SourceManager &SM) const { +  SourceLocation Loc = SM.getLocForStartOfFile(InsertFromRangeOffs.getFID()); +  Loc = Loc.getLocWithOffset(InsertFromRangeOffs.getOffset()); +  assert(Loc.isFileID()); +  return CharSourceRange::getCharRange(Loc, Loc.getLocWithOffset(Length)); +} + +Commit::Commit(EditedSource &Editor) +  : SourceMgr(Editor.getSourceManager()), LangOpts(Editor.getLangOpts()), +    PPRec(Editor.getPreprocessingRecord()), +    Editor(&Editor), IsCommitable(true) { } + +bool Commit::insert(SourceLocation loc, StringRef text, +                    bool afterToken, bool beforePreviousInsertions) { +  if (text.empty()) +    return true; + +  FileOffset Offs; +  if ((!afterToken && !canInsert(loc, Offs)) || +      ( afterToken && !canInsertAfterToken(loc, Offs, loc))) { +    IsCommitable = false; +    return false; +  } + +  addInsert(loc, Offs, text, beforePreviousInsertions); +  return true; +} + +bool Commit::insertFromRange(SourceLocation loc, +                             CharSourceRange range, +                             bool afterToken, bool beforePreviousInsertions) { +  FileOffset RangeOffs; +  unsigned RangeLen; +  if (!canRemoveRange(range, RangeOffs, RangeLen)) { +    IsCommitable = false; +    return false; +  } + +  FileOffset Offs; +  if ((!afterToken && !canInsert(loc, Offs)) || +      ( afterToken && !canInsertAfterToken(loc, Offs, loc))) { +    IsCommitable = false; +    return false; +  } + +  if (PPRec && +      PPRec->areInDifferentConditionalDirectiveRegion(loc, range.getBegin())) { +    IsCommitable = false; +    return false; +  } + +  addInsertFromRange(loc, Offs, RangeOffs, RangeLen, beforePreviousInsertions); +  return true; +} + +bool Commit::remove(CharSourceRange range) { +  FileOffset Offs; +  unsigned Len; +  if (!canRemoveRange(range, Offs, Len)) { +    IsCommitable = false; +    return false; +  } + +  addRemove(range.getBegin(), Offs, Len); +  return true; +} + +bool Commit::insertWrap(StringRef before, CharSourceRange range, +                        StringRef after) { +  bool commitableBefore = insert(range.getBegin(), before, /*afterToken=*/false, +                                 /*beforePreviousInsertions=*/true); +  bool commitableAfter; +  if (range.isTokenRange()) +    commitableAfter = insertAfterToken(range.getEnd(), after); +  else +    commitableAfter = insert(range.getEnd(), after); + +  return commitableBefore && commitableAfter; +} + +bool Commit::replace(CharSourceRange range, StringRef text) { +  if (text.empty()) +    return remove(range); + +  FileOffset Offs; +  unsigned Len; +  if (!canInsert(range.getBegin(), Offs) || !canRemoveRange(range, Offs, Len)) { +    IsCommitable = false; +    return false; +  } + +  addRemove(range.getBegin(), Offs, Len); +  addInsert(range.getBegin(), Offs, text, false); +  return true; +} + +bool Commit::replaceWithInner(CharSourceRange range, +                              CharSourceRange replacementRange) { +  FileOffset OuterBegin; +  unsigned OuterLen; +  if (!canRemoveRange(range, OuterBegin, OuterLen)) { +    IsCommitable = false; +    return false; +  } + +  FileOffset InnerBegin; +  unsigned InnerLen; +  if (!canRemoveRange(replacementRange, InnerBegin, InnerLen)) { +    IsCommitable = false; +    return false; +  } + +  FileOffset OuterEnd = OuterBegin.getWithOffset(OuterLen); +  FileOffset InnerEnd = InnerBegin.getWithOffset(InnerLen);  +  if (OuterBegin.getFID() != InnerBegin.getFID() || +      InnerBegin < OuterBegin || +      InnerBegin > OuterEnd || +      InnerEnd > OuterEnd) { +    IsCommitable = false; +    return false; +  } + +  addRemove(range.getBegin(), +            OuterBegin, InnerBegin.getOffset() - OuterBegin.getOffset()); +  addRemove(replacementRange.getEnd(), +            InnerEnd, OuterEnd.getOffset() - InnerEnd.getOffset()); +  return true; +} + +bool Commit::replaceText(SourceLocation loc, StringRef text, +                         StringRef replacementText) { +  if (text.empty() || replacementText.empty()) +    return true; + +  FileOffset Offs; +  unsigned Len; +  if (!canReplaceText(loc, replacementText, Offs, Len)) { +    IsCommitable = false; +    return false; +  } + +  addRemove(loc, Offs, Len); +  addInsert(loc, Offs, text, false); +  return true; +} + +void Commit::addInsert(SourceLocation OrigLoc, FileOffset Offs, StringRef text, +                       bool beforePreviousInsertions) { +  if (text.empty()) +    return; + +  Edit data; +  data.Kind = Act_Insert; +  data.OrigLoc = OrigLoc; +  data.Offset = Offs; +  data.Text = text; +  data.BeforePrev = beforePreviousInsertions; +  CachedEdits.push_back(data); +} + +void Commit::addInsertFromRange(SourceLocation OrigLoc, FileOffset Offs, +                                FileOffset RangeOffs, unsigned RangeLen, +                                bool beforePreviousInsertions) { +  if (RangeLen == 0) +    return; + +  Edit data; +  data.Kind = Act_InsertFromRange; +  data.OrigLoc = OrigLoc; +  data.Offset = Offs; +  data.InsertFromRangeOffs = RangeOffs; +  data.Length = RangeLen; +  data.BeforePrev = beforePreviousInsertions; +  CachedEdits.push_back(data); +} + +void Commit::addRemove(SourceLocation OrigLoc, +                       FileOffset Offs, unsigned Len) { +  if (Len == 0) +    return; + +  Edit data; +  data.Kind = Act_Remove; +  data.OrigLoc = OrigLoc; +  data.Offset = Offs; +  data.Length = Len; +  CachedEdits.push_back(data); +} + +bool Commit::canInsert(SourceLocation loc, FileOffset &offs) { +  if (loc.isInvalid()) +    return false; + +  if (loc.isMacroID()) +    isAtStartOfMacroExpansion(loc, &loc); + +  const SourceManager &SM = SourceMgr; +  while (SM.isMacroArgExpansion(loc)) +    loc = SM.getImmediateSpellingLoc(loc); + +  if (loc.isMacroID()) +    if (!isAtStartOfMacroExpansion(loc, &loc)) +      return false; + +  if (SM.isInSystemHeader(loc)) +    return false; + +  std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc); +  if (locInfo.first.isInvalid()) +    return false; +  offs = FileOffset(locInfo.first, locInfo.second); +  return canInsertInOffset(loc, offs); +} + +bool Commit::canInsertAfterToken(SourceLocation loc, FileOffset &offs, +                                 SourceLocation &AfterLoc) { +  if (loc.isInvalid()) + +    return false; + +  SourceLocation spellLoc = SourceMgr.getSpellingLoc(loc); +  unsigned tokLen = Lexer::MeasureTokenLength(spellLoc, SourceMgr, LangOpts); +  AfterLoc = loc.getLocWithOffset(tokLen); + +  if (loc.isMacroID()) +    isAtEndOfMacroExpansion(loc, &loc); + +  const SourceManager &SM = SourceMgr; +  while (SM.isMacroArgExpansion(loc)) +    loc = SM.getImmediateSpellingLoc(loc); + +  if (loc.isMacroID()) +    if (!isAtEndOfMacroExpansion(loc, &loc)) +      return false; + +  if (SM.isInSystemHeader(loc)) +    return false; + +  loc = Lexer::getLocForEndOfToken(loc, 0, SourceMgr, LangOpts); +  if (loc.isInvalid()) +    return false; + +  std::pair<FileID, unsigned> locInfo = SM.getDecomposedLoc(loc); +  if (locInfo.first.isInvalid()) +    return false; +  offs = FileOffset(locInfo.first, locInfo.second); +  return canInsertInOffset(loc, offs); +} + +bool Commit::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) { +  for (unsigned i = 0, e = CachedEdits.size(); i != e; ++i) { +    Edit &act = CachedEdits[i]; +    if (act.Kind == Act_Remove) { +      if (act.Offset.getFID() == Offs.getFID() && +          Offs > act.Offset && Offs < act.Offset.getWithOffset(act.Length)) +        return false; // position has been removed. +    } +  } + +  if (!Editor) +    return true; +  return Editor->canInsertInOffset(OrigLoc, Offs); +} + +bool Commit::canRemoveRange(CharSourceRange range, +                            FileOffset &Offs, unsigned &Len) { +  const SourceManager &SM = SourceMgr; +  range = Lexer::makeFileCharRange(range, SM, LangOpts); +  if (range.isInvalid()) +    return false; +   +  if (range.getBegin().isMacroID() || range.getEnd().isMacroID()) +    return false; +  if (SM.isInSystemHeader(range.getBegin()) || +      SM.isInSystemHeader(range.getEnd())) +    return false; + +  if (PPRec && PPRec->rangeIntersectsConditionalDirective(range.getAsRange())) +    return false; + +  std::pair<FileID, unsigned> beginInfo = SM.getDecomposedLoc(range.getBegin()); +  std::pair<FileID, unsigned> endInfo = SM.getDecomposedLoc(range.getEnd()); +  if (beginInfo.first != endInfo.first || +      beginInfo.second > endInfo.second) +    return false; + +  Offs = FileOffset(beginInfo.first, beginInfo.second); +  Len = endInfo.second - beginInfo.second; +  return true; +} + +bool Commit::canReplaceText(SourceLocation loc, StringRef text, +                            FileOffset &Offs, unsigned &Len) { +  assert(!text.empty()); + +  if (!canInsert(loc, Offs)) +    return false; + +  // Try to load the file buffer. +  bool invalidTemp = false; +  StringRef file = SourceMgr.getBufferData(Offs.getFID(), &invalidTemp); +  if (invalidTemp) +    return false; + +  return file.substr(Offs.getOffset()).startswith(text); +} + +bool Commit::isAtStartOfMacroExpansion(SourceLocation loc, +                                       SourceLocation *MacroBegin) const { +  return Lexer::isAtStartOfMacroExpansion(loc, SourceMgr, LangOpts, MacroBegin); +} +bool Commit::isAtEndOfMacroExpansion(SourceLocation loc, +                                     SourceLocation *MacroEnd) const { +  return Lexer::isAtEndOfMacroExpansion(loc, SourceMgr, LangOpts, MacroEnd); +} diff --git a/clang/lib/Edit/EditedSource.cpp b/clang/lib/Edit/EditedSource.cpp new file mode 100644 index 0000000..5b7fa4a --- /dev/null +++ b/clang/lib/Edit/EditedSource.cpp @@ -0,0 +1,329 @@ +//===----- EditedSource.cpp - Collection of source edits ------------------===// +// +//                     The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "clang/Edit/EditedSource.h" +#include "clang/Edit/Commit.h" +#include "clang/Edit/EditsReceiver.h" +#include "clang/Lex/Lexer.h" +#include "clang/Basic/SourceManager.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/Twine.h" + +using namespace clang; +using namespace edit; + +void EditsReceiver::remove(CharSourceRange range) { +  replace(range, StringRef()); +} + +StringRef EditedSource::copyString(const Twine &twine) { +  llvm::SmallString<128> Data; +  return copyString(twine.toStringRef(Data)); +} + +bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) { +  FileEditsTy::iterator FA = getActionForOffset(Offs); +  if (FA != FileEdits.end()) { +    if (FA->first != Offs) +      return false; // position has been removed. +  } + +  if (SourceMgr.isMacroArgExpansion(OrigLoc)) { +    SourceLocation +      DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first; +    SourceLocation +      ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first; +    llvm::DenseMap<unsigned, SourceLocation>::iterator +      I = ExpansionToArgMap.find(ExpLoc.getRawEncoding()); +    if (I != ExpansionToArgMap.end() && I->second != DefArgLoc) +      return false; // Trying to write in a macro argument input that has +                 // already been written for another argument of the same macro.  +  } + +  return true; +} + +bool EditedSource::commitInsert(SourceLocation OrigLoc, +                                FileOffset Offs, StringRef text, +                                bool beforePreviousInsertions) { +  if (!canInsertInOffset(OrigLoc, Offs)) +    return false; +  if (text.empty()) +    return true; + +  if (SourceMgr.isMacroArgExpansion(OrigLoc)) { +    SourceLocation +      DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first; +    SourceLocation +      ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first; +    ExpansionToArgMap[ExpLoc.getRawEncoding()] = DefArgLoc; +  } +   +  FileEdit &FA = FileEdits[Offs]; +  if (FA.Text.empty()) { +    FA.Text = copyString(text); +    return true; +  } + +  Twine concat; +  if (beforePreviousInsertions) +    concat = Twine(text) + FA.Text; +  else +    concat = Twine(FA.Text) +  text; + +  FA.Text = copyString(concat); +  return true; +} + +bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc, +                                   FileOffset Offs, +                                   FileOffset InsertFromRangeOffs, unsigned Len, +                                   bool beforePreviousInsertions) { +  if (Len == 0) +    return true; + +  llvm::SmallString<128> StrVec; +  FileOffset BeginOffs = InsertFromRangeOffs; +  FileOffset EndOffs = BeginOffs.getWithOffset(Len); +  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs); +  if (I != FileEdits.begin()) +    --I; + +  for (; I != FileEdits.end(); ++I) { +    FileEdit &FA = I->second; +    FileOffset B = I->first; +    FileOffset E = B.getWithOffset(FA.RemoveLen); + +    if (BeginOffs < E) { +      if (BeginOffs >= B) { +        BeginOffs = E; +        ++I; +      } +      break; +    } +  } + +  for (; I != FileEdits.end() && EndOffs > I->first; ++I) { +    FileEdit &FA = I->second; +    FileOffset B = I->first; +    FileOffset E = B.getWithOffset(FA.RemoveLen); + +    if (BeginOffs < B) { +      bool Invalid = false; +      StringRef text = getSourceText(BeginOffs, B, Invalid); +      if (Invalid) +        return false; +      StrVec += text; +    } +    StrVec += FA.Text; +    BeginOffs = E; +  } + +  if (BeginOffs < EndOffs) { +    bool Invalid = false; +    StringRef text = getSourceText(BeginOffs, EndOffs, Invalid); +    if (Invalid) +      return false; +    StrVec += text; +  } + +  return commitInsert(OrigLoc, Offs, StrVec.str(), beforePreviousInsertions); +} + +void EditedSource::commitRemove(SourceLocation OrigLoc, +                                FileOffset BeginOffs, unsigned Len) { +  if (Len == 0) +    return; + +  FileOffset EndOffs = BeginOffs.getWithOffset(Len); +  FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs); +  if (I != FileEdits.begin()) +    --I; + +  for (; I != FileEdits.end(); ++I) { +    FileEdit &FA = I->second; +    FileOffset B = I->first; +    FileOffset E = B.getWithOffset(FA.RemoveLen); + +    if (BeginOffs < E) +      break; +  } + +  FileOffset TopBegin, TopEnd; +  FileEdit *TopFA = 0; + +  if (I == FileEdits.end()) { +    FileEditsTy::iterator +      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit())); +    NewI->second.RemoveLen = Len; +    return; +  } + +  FileEdit &FA = I->second; +  FileOffset B = I->first; +  FileOffset E = B.getWithOffset(FA.RemoveLen); +  if (BeginOffs < B) { +    FileEditsTy::iterator +      NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit())); +    TopBegin = BeginOffs; +    TopEnd = EndOffs; +    TopFA = &NewI->second; +    TopFA->RemoveLen = Len; +  } else { +    TopBegin = B; +    TopEnd = E; +    TopFA = &I->second; +    if (TopEnd >= EndOffs) +      return; +    unsigned diff = EndOffs.getOffset() - TopEnd.getOffset(); +    TopEnd = EndOffs; +    TopFA->RemoveLen += diff; +    ++I; +  } + +  while (I != FileEdits.end()) { +    FileEdit &FA = I->second; +    FileOffset B = I->first; +    FileOffset E = B.getWithOffset(FA.RemoveLen); + +    if (B >= TopEnd) +      break; + +    if (E <= TopEnd) { +      FileEdits.erase(I++); +      continue; +    } + +    if (B < TopEnd) { +      unsigned diff = E.getOffset() - TopEnd.getOffset(); +      TopEnd = E; +      TopFA->RemoveLen += diff; +      FileEdits.erase(I); +    } + +    break; +  } +} + +bool EditedSource::commit(const Commit &commit) { +  if (!commit.isCommitable()) +    return false; + +  for (edit::Commit::edit_iterator +         I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) { +    const edit::Commit::Edit &edit = *I; +    switch (edit.Kind) { +    case edit::Commit::Act_Insert: +      commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev); +      break; +    case edit::Commit::Act_InsertFromRange: +      commitInsertFromRange(edit.OrigLoc, edit.Offset, +                            edit.InsertFromRangeOffs, edit.Length, +                            edit.BeforePrev); +      break; +    case edit::Commit::Act_Remove: +      commitRemove(edit.OrigLoc, edit.Offset, edit.Length); +      break; +    } +  } + +  return true; +} + +static void applyRewrite(EditsReceiver &receiver, +                         StringRef text, FileOffset offs, unsigned len, +                         const SourceManager &SM) { +  assert(!offs.getFID().isInvalid()); +  SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID()); +  Loc = Loc.getLocWithOffset(offs.getOffset()); +  assert(Loc.isFileID()); +  CharSourceRange range = CharSourceRange::getCharRange(Loc, +                                                     Loc.getLocWithOffset(len)); + +  if (text.empty()) { +    assert(len); +    receiver.remove(range); +    return; +  } + +  if (len) +    receiver.replace(range, text); +  else +    receiver.insert(Loc, text); +} + +void EditedSource::applyRewrites(EditsReceiver &receiver) { +  llvm::SmallString<128> StrVec; +  FileOffset CurOffs, CurEnd; +  unsigned CurLen; + +  if (FileEdits.empty()) +    return; + +  FileEditsTy::iterator I = FileEdits.begin(); +  CurOffs = I->first; +  StrVec = I->second.Text; +  CurLen = I->second.RemoveLen; +  CurEnd = CurOffs.getWithOffset(CurLen); +  ++I; + +  for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) { +    FileOffset offs = I->first; +    FileEdit act = I->second; +    assert(offs >= CurEnd); + +    if (offs == CurEnd) { +      StrVec += act.Text; +      CurLen += act.RemoveLen; +      CurEnd.getWithOffset(act.RemoveLen); +      continue; +    } + +    applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr); +    CurOffs = offs; +    StrVec = act.Text; +    CurLen = act.RemoveLen; +    CurEnd = CurOffs.getWithOffset(CurLen); +  } + +  applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr); +} + +void EditedSource::clearRewrites() { +  FileEdits.clear(); +  StrAlloc.Reset(); +} + +StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs, +                                      bool &Invalid) { +  assert(BeginOffs.getFID() == EndOffs.getFID()); +  assert(BeginOffs <= EndOffs); +  SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID()); +  BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset()); +  assert(BLoc.isFileID()); +  SourceLocation +    ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset()); +  return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc), +                              SourceMgr, LangOpts, &Invalid); +} + +EditedSource::FileEditsTy::iterator +EditedSource::getActionForOffset(FileOffset Offs) { +  FileEditsTy::iterator I = FileEdits.upper_bound(Offs); +  if (I == FileEdits.begin()) +    return FileEdits.end(); +  --I; +  FileEdit &FA = I->second; +  FileOffset B = I->first; +  FileOffset E = B.getWithOffset(FA.RemoveLen); +  if (Offs >= B && Offs < E) +    return I; + +  return FileEdits.end(); +} diff --git a/clang/lib/Edit/Makefile b/clang/lib/Edit/Makefile new file mode 100644 index 0000000..92a67eb --- /dev/null +++ b/clang/lib/Edit/Makefile @@ -0,0 +1,14 @@ +##===- clang/lib/Edit/Makefile -----------------------------*- Makefile -*-===## +#  +#                     The LLVM Compiler Infrastructure +# +# This file is distributed under the University of Illinois Open Source +# License. See LICENSE.TXT for details. +#  +##===----------------------------------------------------------------------===## + +CLANG_LEVEL := ../.. +LIBRARYNAME := clangEdit + +include $(CLANG_LEVEL)/Makefile + diff --git a/clang/lib/Edit/RewriteObjCFoundationAPI.cpp b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp new file mode 100644 index 0000000..24a0db1 --- /dev/null +++ b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp @@ -0,0 +1,587 @@ +//===--- RewriteObjCFoundationAPI.cpp - Foundation API Rewriter -----------===// +// +//                     The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Rewrites legacy method calls to modern syntax. +// +//===----------------------------------------------------------------------===// + +#include "clang/Edit/Rewriters.h" +#include "clang/Edit/Commit.h" +#include "clang/Lex/Lexer.h" +#include "clang/AST/ExprObjC.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/NSAPI.h" + +using namespace clang; +using namespace edit; + +static bool checkForLiteralCreation(const ObjCMessageExpr *Msg, +                                    IdentifierInfo *&ClassId) { +  if (!Msg || Msg->isImplicit() || !Msg->getMethodDecl()) +    return false; + +  const ObjCInterfaceDecl *Receiver = Msg->getReceiverInterface(); +  if (!Receiver) +    return false; +  ClassId = Receiver->getIdentifier(); + +  if (Msg->getReceiverKind() == ObjCMessageExpr::Class) +    return true; + +  return false; +} + +//===----------------------------------------------------------------------===// +// rewriteObjCRedundantCallWithLiteral. +//===----------------------------------------------------------------------===// + +bool edit::rewriteObjCRedundantCallWithLiteral(const ObjCMessageExpr *Msg, +                                              const NSAPI &NS, Commit &commit) { +  IdentifierInfo *II = 0; +  if (!checkForLiteralCreation(Msg, II)) +    return false; +  if (Msg->getNumArgs() != 1) +    return false; + +  const Expr *Arg = Msg->getArg(0)->IgnoreParenImpCasts(); +  Selector Sel = Msg->getSelector(); + +  if ((isa<ObjCStringLiteral>(Arg) && +       NS.getNSClassId(NSAPI::ClassId_NSString) == II && +       NS.getNSStringSelector(NSAPI::NSStr_stringWithString) == Sel)    || + +      (isa<ObjCArrayLiteral>(Arg) && +       NS.getNSClassId(NSAPI::ClassId_NSArray) == II && +       NS.getNSArraySelector(NSAPI::NSArr_arrayWithArray) == Sel)      || + +      (isa<ObjCDictionaryLiteral>(Arg) && +       NS.getNSClassId(NSAPI::ClassId_NSDictionary) == II && +       NS.getNSDictionarySelector( +                              NSAPI::NSDict_dictionaryWithDictionary) == Sel)) { +     +    commit.replaceWithInner(Msg->getSourceRange(), +                           Msg->getArg(0)->getSourceRange()); +    return true; +  } + +  return false; +} + +//===----------------------------------------------------------------------===// +// rewriteToObjCSubscriptSyntax. +//===----------------------------------------------------------------------===// + +static void maybePutParensOnReceiver(const Expr *Receiver, Commit &commit) { +  Receiver = Receiver->IgnoreImpCasts(); +  if (isa<BinaryOperator>(Receiver) || isa<UnaryOperator>(Receiver)) { +    SourceRange RecRange = Receiver->getSourceRange(); +    commit.insertWrap("(", RecRange, ")"); +  } +} + +static bool rewriteToSubscriptGet(const ObjCMessageExpr *Msg, Commit &commit) { +  if (Msg->getNumArgs() != 1) +    return false; +  const Expr *Rec = Msg->getInstanceReceiver(); +  if (!Rec) +    return false; + +  SourceRange MsgRange = Msg->getSourceRange(); +  SourceRange RecRange = Rec->getSourceRange(); +  SourceRange ArgRange = Msg->getArg(0)->getSourceRange(); + +  commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(), +                                                       ArgRange.getBegin()), +                         CharSourceRange::getTokenRange(RecRange)); +  commit.replaceWithInner(SourceRange(ArgRange.getBegin(), MsgRange.getEnd()), +                         ArgRange); +  commit.insertWrap("[", ArgRange, "]"); +  maybePutParensOnReceiver(Rec, commit); +  return true; +} + +static bool rewriteToArraySubscriptSet(const ObjCMessageExpr *Msg, +                                       Commit &commit) { +  if (Msg->getNumArgs() != 2) +    return false; +  const Expr *Rec = Msg->getInstanceReceiver(); +  if (!Rec) +    return false; + +  SourceRange MsgRange = Msg->getSourceRange(); +  SourceRange RecRange = Rec->getSourceRange(); +  SourceRange Arg0Range = Msg->getArg(0)->getSourceRange(); +  SourceRange Arg1Range = Msg->getArg(1)->getSourceRange(); + +  commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(), +                                                       Arg0Range.getBegin()), +                         CharSourceRange::getTokenRange(RecRange)); +  commit.replaceWithInner(CharSourceRange::getCharRange(Arg0Range.getBegin(), +                                                       Arg1Range.getBegin()), +                         CharSourceRange::getTokenRange(Arg0Range)); +  commit.replaceWithInner(SourceRange(Arg1Range.getBegin(), MsgRange.getEnd()), +                         Arg1Range); +  commit.insertWrap("[", CharSourceRange::getCharRange(Arg0Range.getBegin(), +                                                       Arg1Range.getBegin()), +                    "] = "); +  maybePutParensOnReceiver(Rec, commit); +  return true; +} + +static bool rewriteToDictionarySubscriptSet(const ObjCMessageExpr *Msg, +                                            Commit &commit) { +  if (Msg->getNumArgs() != 2) +    return false; +  const Expr *Rec = Msg->getInstanceReceiver(); +  if (!Rec) +    return false; + +  SourceRange MsgRange = Msg->getSourceRange(); +  SourceRange RecRange = Rec->getSourceRange(); +  SourceRange Arg0Range = Msg->getArg(0)->getSourceRange(); +  SourceRange Arg1Range = Msg->getArg(1)->getSourceRange(); + +  SourceLocation LocBeforeVal = Arg0Range.getBegin(); +  commit.insertBefore(LocBeforeVal, "] = "); +  commit.insertFromRange(LocBeforeVal, Arg1Range, /*afterToken=*/false, +                         /*beforePreviousInsertions=*/true); +  commit.insertBefore(LocBeforeVal, "["); +  commit.replaceWithInner(CharSourceRange::getCharRange(MsgRange.getBegin(), +                                                       Arg0Range.getBegin()), +                         CharSourceRange::getTokenRange(RecRange)); +  commit.replaceWithInner(SourceRange(Arg0Range.getBegin(), MsgRange.getEnd()), +                         Arg0Range); +  maybePutParensOnReceiver(Rec, commit); +  return true; +} + +bool edit::rewriteToObjCSubscriptSyntax(const ObjCMessageExpr *Msg, +                                           const NSAPI &NS, Commit &commit) { +  if (!Msg || Msg->isImplicit() || +      Msg->getReceiverKind() != ObjCMessageExpr::Instance) +    return false; +  const ObjCMethodDecl *Method = Msg->getMethodDecl(); +  if (!Method) +    return false; + +  const ObjCInterfaceDecl * +    IFace = NS.getASTContext().getObjContainingInterface( +                                          const_cast<ObjCMethodDecl *>(Method)); +  if (!IFace) +    return false; +  IdentifierInfo *II = IFace->getIdentifier(); +  Selector Sel = Msg->getSelector(); + +  if ((II == NS.getNSClassId(NSAPI::ClassId_NSArray) && +       Sel == NS.getNSArraySelector(NSAPI::NSArr_objectAtIndex)) || +      (II == NS.getNSClassId(NSAPI::ClassId_NSDictionary) && +       Sel == NS.getNSDictionarySelector(NSAPI::NSDict_objectForKey))) +    return rewriteToSubscriptGet(Msg, commit); + +  if (Msg->getNumArgs() != 2) +    return false; + +  if (II == NS.getNSClassId(NSAPI::ClassId_NSMutableArray) && +      Sel == NS.getNSArraySelector(NSAPI::NSMutableArr_replaceObjectAtIndex)) +    return rewriteToArraySubscriptSet(Msg, commit); + +  if (II == NS.getNSClassId(NSAPI::ClassId_NSMutableDictionary) && +      Sel == NS.getNSDictionarySelector(NSAPI::NSMutableDict_setObjectForKey)) +    return rewriteToDictionarySubscriptSet(Msg, commit); + +  return false; +} + +//===----------------------------------------------------------------------===// +// rewriteToObjCLiteralSyntax. +//===----------------------------------------------------------------------===// + +static bool rewriteToArrayLiteral(const ObjCMessageExpr *Msg, +                                  const NSAPI &NS, Commit &commit); +static bool rewriteToDictionaryLiteral(const ObjCMessageExpr *Msg, +                                  const NSAPI &NS, Commit &commit); +static bool rewriteToNumberLiteral(const ObjCMessageExpr *Msg, +                                  const NSAPI &NS, Commit &commit); + +bool edit::rewriteToObjCLiteralSyntax(const ObjCMessageExpr *Msg, +                                      const NSAPI &NS, Commit &commit) { +  IdentifierInfo *II = 0; +  if (!checkForLiteralCreation(Msg, II)) +    return false; + +  if (II == NS.getNSClassId(NSAPI::ClassId_NSArray)) +    return rewriteToArrayLiteral(Msg, NS, commit); +  if (II == NS.getNSClassId(NSAPI::ClassId_NSDictionary)) +    return rewriteToDictionaryLiteral(Msg, NS, commit); +  if (II == NS.getNSClassId(NSAPI::ClassId_NSNumber)) +    return rewriteToNumberLiteral(Msg, NS, commit); + +  return false; +} + +//===----------------------------------------------------------------------===// +// rewriteToArrayLiteral. +//===----------------------------------------------------------------------===// + +static bool rewriteToArrayLiteral(const ObjCMessageExpr *Msg, +                                  const NSAPI &NS, Commit &commit) { +  Selector Sel = Msg->getSelector(); +  SourceRange MsgRange = Msg->getSourceRange(); + +  if (Sel == NS.getNSArraySelector(NSAPI::NSArr_array)) { +    if (Msg->getNumArgs() != 0) +      return false; +    commit.replace(MsgRange, "@[]"); +    return true; +  } + +  if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObject)) { +    if (Msg->getNumArgs() != 1) +      return false; +    SourceRange ArgRange = Msg->getArg(0)->getSourceRange(); +    commit.replaceWithInner(MsgRange, ArgRange); +    commit.insertWrap("@[", ArgRange, "]"); +    return true; +  } + +  if (Sel == NS.getNSArraySelector(NSAPI::NSArr_arrayWithObjects)) { +    if (Msg->getNumArgs() == 0) +      return false; +    const Expr *SentinelExpr = Msg->getArg(Msg->getNumArgs() - 1); +    if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr)) +      return false; + +    if (Msg->getNumArgs() == 1) { +      commit.replace(MsgRange, "@[]"); +      return true; +    } +    SourceRange ArgRange(Msg->getArg(0)->getLocStart(), +                         Msg->getArg(Msg->getNumArgs()-2)->getLocEnd()); +    commit.replaceWithInner(MsgRange, ArgRange); +    commit.insertWrap("@[", ArgRange, "]"); +    return true; +  } + +  return false; +} + +//===----------------------------------------------------------------------===// +// rewriteToDictionaryLiteral. +//===----------------------------------------------------------------------===// + +static bool rewriteToDictionaryLiteral(const ObjCMessageExpr *Msg, +                                       const NSAPI &NS, Commit &commit) { +  Selector Sel = Msg->getSelector(); +  SourceRange MsgRange = Msg->getSourceRange(); + +  if (Sel == NS.getNSDictionarySelector(NSAPI::NSDict_dictionary)) { +    if (Msg->getNumArgs() != 0) +      return false; +    commit.replace(MsgRange, "@{}"); +    return true; +  } + +  if (Sel == NS.getNSDictionarySelector( +                                    NSAPI::NSDict_dictionaryWithObjectForKey)) { +    if (Msg->getNumArgs() != 2) +      return false; +    SourceRange ValRange = Msg->getArg(0)->getSourceRange(); +    SourceRange KeyRange = Msg->getArg(1)->getSourceRange(); +    // Insert key before the value. +    commit.insertBefore(ValRange.getBegin(), ": "); +    commit.insertFromRange(ValRange.getBegin(), +                           CharSourceRange::getTokenRange(KeyRange), +                       /*afterToken=*/false, /*beforePreviousInsertions=*/true); +    commit.insertBefore(ValRange.getBegin(), "@{"); +    commit.insertAfterToken(ValRange.getEnd(), "}"); +    commit.replaceWithInner(MsgRange, ValRange); +    return true; +  } + +  if (Sel == NS.getNSDictionarySelector( +                                  NSAPI::NSDict_dictionaryWithObjectsAndKeys)) { +    if (Msg->getNumArgs() % 2 != 1) +      return false; +    unsigned SentinelIdx = Msg->getNumArgs() - 1; +    const Expr *SentinelExpr = Msg->getArg(SentinelIdx); +    if (!NS.getASTContext().isSentinelNullExpr(SentinelExpr)) +      return false; + +    if (Msg->getNumArgs() == 1) { +      commit.replace(MsgRange, "@{}"); +      return true; +    } + +    for (unsigned i = 0; i < SentinelIdx; i += 2) { +      SourceRange ValRange = Msg->getArg(i)->getSourceRange(); +      SourceRange KeyRange = Msg->getArg(i+1)->getSourceRange(); +      // Insert value after key. +      commit.insertAfterToken(KeyRange.getEnd(), ": "); +      commit.insertFromRange(KeyRange.getEnd(), ValRange, /*afterToken=*/true); +      commit.remove(CharSourceRange::getCharRange(ValRange.getBegin(), +                                                  KeyRange.getBegin())); +    } +    // Range of arguments up until and including the last key. +    // The sentinel and first value are cut off, the value will move after the +    // key. +    SourceRange ArgRange(Msg->getArg(1)->getLocStart(), +                         Msg->getArg(SentinelIdx-1)->getLocEnd()); +    commit.insertWrap("@{", ArgRange, "}"); +    commit.replaceWithInner(MsgRange, ArgRange); +    return true; +  } + +  return false; +} + +//===----------------------------------------------------------------------===// +// rewriteToNumberLiteral. +//===----------------------------------------------------------------------===// + +static bool rewriteToCharLiteral(const ObjCMessageExpr *Msg, +                                   const CharacterLiteral *Arg, +                                   const NSAPI &NS, Commit &commit) { +  if (Arg->getKind() != CharacterLiteral::Ascii) +    return false; +  if (NS.isNSNumberLiteralSelector(NSAPI::NSNumberWithChar, +                                   Msg->getSelector())) { +    SourceRange ArgRange = Arg->getSourceRange(); +    commit.replaceWithInner(Msg->getSourceRange(), ArgRange); +    commit.insert(ArgRange.getBegin(), "@"); +    return true; +  } + +  return false; +} + +static bool rewriteToBoolLiteral(const ObjCMessageExpr *Msg, +                                   const Expr *Arg, +                                   const NSAPI &NS, Commit &commit) { +  if (NS.isNSNumberLiteralSelector(NSAPI::NSNumberWithBool, +                                   Msg->getSelector())) { +    SourceRange ArgRange = Arg->getSourceRange(); +    commit.replaceWithInner(Msg->getSourceRange(), ArgRange); +    commit.insert(ArgRange.getBegin(), "@"); +    return true; +  } + +  return false; +} + +namespace { + +struct LiteralInfo { +  bool Hex, Octal; +  StringRef U, F, L, LL; +  CharSourceRange WithoutSuffRange; +}; + +} + +static bool getLiteralInfo(SourceRange literalRange, +                           bool isFloat, bool isIntZero, +                          ASTContext &Ctx, LiteralInfo &Info) { +  if (literalRange.getBegin().isMacroID() || +      literalRange.getEnd().isMacroID()) +    return false; +  StringRef text = Lexer::getSourceText( +                                  CharSourceRange::getTokenRange(literalRange), +                                  Ctx.getSourceManager(), Ctx.getLangOpts()); +  if (text.empty()) +    return false; + +  llvm::Optional<bool> UpperU, UpperL;  +  bool UpperF = false; + +  struct Suff { +    static bool has(StringRef suff, StringRef &text) { +      if (text.endswith(suff)) { +        text = text.substr(0, text.size()-suff.size()); +        return true; +      } +      return false; +    } +  }; + +  while (1) { +    if (Suff::has("u", text)) { +      UpperU = false; +    } else if (Suff::has("U", text)) { +      UpperU = true; +    } else if (Suff::has("ll", text)) { +      UpperL = false; +    } else if (Suff::has("LL", text)) { +      UpperL = true; +    } else if (Suff::has("l", text)) { +      UpperL = false; +    } else if (Suff::has("L", text)) { +      UpperL = true; +    } else if (isFloat && Suff::has("f", text)) { +      UpperF = false; +    } else if (isFloat && Suff::has("F", text)) { +      UpperF = true; +    } else +      break; +  } +   +  if (!UpperU.hasValue() && !UpperL.hasValue()) +    UpperU = UpperL = true; +  else if (UpperU.hasValue() && !UpperL.hasValue()) +    UpperL = UpperU; +  else if (UpperL.hasValue() && !UpperU.hasValue()) +    UpperU = UpperL; + +  Info.U = *UpperU ? "U" : "u"; +  Info.L = *UpperL ? "L" : "l"; +  Info.LL = *UpperL ? "LL" : "ll"; +  Info.F = UpperF ? "F" : "f"; +   +  Info.Hex = Info.Octal = false; +  if (text.startswith("0x")) +    Info.Hex = true; +  else if (!isFloat && !isIntZero && text.startswith("0")) +    Info.Octal = true; + +  SourceLocation B = literalRange.getBegin(); +  Info.WithoutSuffRange = +      CharSourceRange::getCharRange(B, B.getLocWithOffset(text.size())); +  return true; +} + +static bool rewriteToNumberLiteral(const ObjCMessageExpr *Msg, +                                   const NSAPI &NS, Commit &commit) { +  if (Msg->getNumArgs() != 1) +    return false; + +  const Expr *Arg = Msg->getArg(0)->IgnoreParenImpCasts(); +  if (const CharacterLiteral *CharE = dyn_cast<CharacterLiteral>(Arg)) +    return rewriteToCharLiteral(Msg, CharE, NS, commit); +  if (const ObjCBoolLiteralExpr *BE = dyn_cast<ObjCBoolLiteralExpr>(Arg)) +    return rewriteToBoolLiteral(Msg, BE, NS, commit); +  if (const CXXBoolLiteralExpr *BE = dyn_cast<CXXBoolLiteralExpr>(Arg)) +    return rewriteToBoolLiteral(Msg, BE, NS, commit); + +  const Expr *literalE = Arg; +  if (const UnaryOperator *UOE = dyn_cast<UnaryOperator>(literalE)) { +    if (UOE->getOpcode() == UO_Plus || UOE->getOpcode() == UO_Minus) +      literalE = UOE->getSubExpr(); +  } + +  // Only integer and floating literals; non-literals or imaginary literal +  // cannot be rewritten. +  if (!isa<IntegerLiteral>(literalE) && !isa<FloatingLiteral>(literalE)) +    return false; + +  ASTContext &Ctx = NS.getASTContext(); +  Selector Sel = Msg->getSelector(); +  llvm::Optional<NSAPI::NSNumberLiteralMethodKind> +    MKOpt = NS.getNSNumberLiteralMethodKind(Sel); +  if (!MKOpt) +    return false; +  NSAPI::NSNumberLiteralMethodKind MK = *MKOpt; + +  bool CallIsUnsigned = false, CallIsLong = false, CallIsLongLong = false; +  bool CallIsFloating = false, CallIsDouble = false; + +  switch (MK) { +  // We cannot have these calls with int/float literals. +  case NSAPI::NSNumberWithChar: +  case NSAPI::NSNumberWithUnsignedChar: +  case NSAPI::NSNumberWithShort: +  case NSAPI::NSNumberWithUnsignedShort: +  case NSAPI::NSNumberWithBool: +    return false; + +  case NSAPI::NSNumberWithUnsignedInt: +  case NSAPI::NSNumberWithUnsignedInteger: +    CallIsUnsigned = true; +  case NSAPI::NSNumberWithInt: +  case NSAPI::NSNumberWithInteger: +    break; + +  case NSAPI::NSNumberWithUnsignedLong: +    CallIsUnsigned = true; +  case NSAPI::NSNumberWithLong: +    CallIsLong = true; +    break; + +  case NSAPI::NSNumberWithUnsignedLongLong: +    CallIsUnsigned = true; +  case NSAPI::NSNumberWithLongLong: +    CallIsLongLong = true; +    break; + +  case NSAPI::NSNumberWithDouble: +    CallIsDouble = true; +  case NSAPI::NSNumberWithFloat: +    CallIsFloating = true; +    break; +  } + +  SourceRange ArgRange = Arg->getSourceRange(); +  QualType ArgTy = Arg->getType(); +  QualType CallTy = Msg->getArg(0)->getType(); + +  // Check for the easy case, the literal maps directly to the call. +  if (Ctx.hasSameType(ArgTy, CallTy)) { +    commit.replaceWithInner(Msg->getSourceRange(), ArgRange); +    commit.insert(ArgRange.getBegin(), "@"); +    return true; +  } + +  // We will need to modify the literal suffix to get the same type as the call. +  // Don't even try if it came from a macro. +  if (ArgRange.getBegin().isMacroID()) +    return false; + +  bool LitIsFloat = ArgTy->isFloatingType(); +  // For a float passed to integer call, don't try rewriting. It is difficult +  // and a very uncommon case anyway. +  if (LitIsFloat && !CallIsFloating) +    return false; + +  // Try to modify the literal make it the same type as the method call. +  // -Modify the suffix, and/or +  // -Change integer to float +   +  LiteralInfo LitInfo; +  bool isIntZero = false; +  if (const IntegerLiteral *IntE = dyn_cast<IntegerLiteral>(literalE)) +    isIntZero = !IntE->getValue().getBoolValue(); +  if (!getLiteralInfo(ArgRange, LitIsFloat, isIntZero, Ctx, LitInfo)) +    return false; + +  // Not easy to do int -> float with hex/octal and uncommon anyway. +  if (!LitIsFloat && CallIsFloating && (LitInfo.Hex || LitInfo.Octal)) +    return false; +   +  SourceLocation LitB = LitInfo.WithoutSuffRange.getBegin(); +  SourceLocation LitE = LitInfo.WithoutSuffRange.getEnd(); + +  commit.replaceWithInner(CharSourceRange::getTokenRange(Msg->getSourceRange()), +                         LitInfo.WithoutSuffRange); +  commit.insert(LitB, "@"); + +  if (!LitIsFloat && CallIsFloating) +    commit.insert(LitE, ".0"); + +  if (CallIsFloating) { +    if (!CallIsDouble) +      commit.insert(LitE, LitInfo.F); +  } else { +    if (CallIsUnsigned) +      commit.insert(LitE, LitInfo.U); +   +    if (CallIsLong) +      commit.insert(LitE, LitInfo.L); +    else if (CallIsLongLong) +      commit.insert(LitE, LitInfo.LL); +  } +  return true; +}  | 
