//===-- TransformActions.cpp - Migration to ARC mode ----------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "Internals.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Expr.h" #include "clang/Basic/SourceManager.h" #include "clang/Lex/Preprocessor.h" #include "llvm/ADT/DenseSet.h" #include using namespace clang; using namespace arcmt; namespace { /// Collects transformations and merges them before applying them with /// with applyRewrites(). E.g. if the same source range /// is requested to be removed twice, only one rewriter remove will be invoked. /// Rewrites happen in "transactions"; if one rewrite in the transaction cannot /// be done (e.g. it resides in a macro) all rewrites in the transaction are /// aborted. /// FIXME: "Transactional" rewrites support should be baked in the Rewriter. class TransformActionsImpl { CapturedDiagList &CapturedDiags; ASTContext &Ctx; Preprocessor &PP; bool IsInTransaction; enum ActionKind { Act_Insert, Act_InsertAfterToken, Act_Remove, Act_RemoveStmt, Act_Replace, Act_ReplaceText, Act_IncreaseIndentation, Act_ClearDiagnostic }; struct ActionData { ActionKind Kind; SourceLocation Loc; SourceRange R1, R2; StringRef Text1, Text2; Stmt *S; SmallVector DiagIDs; }; std::vector CachedActions; enum RangeComparison { Range_Before, Range_After, Range_Contains, Range_Contained, Range_ExtendsBegin, Range_ExtendsEnd }; /// A range to remove. It is a character range. struct CharRange { FullSourceLoc Begin, End; CharRange(CharSourceRange range, SourceManager &srcMgr, Preprocessor &PP) { SourceLocation beginLoc = range.getBegin(), endLoc = range.getEnd(); assert(beginLoc.isValid() && endLoc.isValid()); if (range.isTokenRange()) { Begin = FullSourceLoc(srcMgr.getExpansionLoc(beginLoc), srcMgr); End = FullSourceLoc(getLocForEndOfToken(endLoc, srcMgr, PP), srcMgr); } else { Begin = FullSourceLoc(srcMgr.getExpansionLoc(beginLoc), srcMgr); End = FullSourceLoc(srcMgr.getExpansionLoc(endLoc), srcMgr); } assert(Begin.isValid() && End.isValid()); } RangeComparison compareWith(const CharRange &RHS) const { if (End.isBeforeInTranslationUnitThan(RHS.Begin)) return Range_Before; if (RHS.End.isBeforeInTranslationUnitThan(Begin)) return Range_After; if (!Begin.isBeforeInTranslationUnitThan(RHS.Begin) && !RHS.End.isBeforeInTranslationUnitThan(End)) return Range_Contained; if (Begin.isBeforeInTranslationUnitThan(RHS.Begin) && RHS.End.isBeforeInTranslationUnitThan(End)) return Range_Contains; if (Begin.isBeforeInTranslationUnitThan(RHS.Begin)) return Range_ExtendsBegin; else return Range_ExtendsEnd; } static RangeComparison compare(SourceRange LHS, SourceRange RHS, SourceManager &SrcMgr, Preprocessor &PP) { return CharRange(CharSourceRange::getTokenRange(LHS), SrcMgr, PP) .compareWith(CharRange(CharSourceRange::getTokenRange(RHS), SrcMgr, PP)); } }; typedef SmallVector TextsVec; typedef std::map InsertsMap; InsertsMap Inserts; /// A list of ranges to remove. They are always sorted and they never /// intersect with each other. std::list Removals; llvm::DenseSet StmtRemovals; std::vector > IndentationRanges; /// Keeps text passed to transformation methods. llvm::StringMap UniqueText; public: TransformActionsImpl(CapturedDiagList &capturedDiags, ASTContext &ctx, Preprocessor &PP) : CapturedDiags(capturedDiags), Ctx(ctx), PP(PP), IsInTransaction(false) { } ASTContext &getASTContext() { return Ctx; } void startTransaction(); bool commitTransaction(); void abortTransaction(); bool isInTransaction() const { return IsInTransaction; } void insert(SourceLocation loc, StringRef text); void insertAfterToken(SourceLocation loc, StringRef text); void remove(SourceRange range); void removeStmt(Stmt *S); void replace(SourceRange range, StringRef text); void replace(SourceRange range, SourceRange replacementRange); void replaceStmt(Stmt *S, StringRef text); void replaceText(SourceLocation loc, StringRef text, StringRef replacementText); void increaseIndentation(SourceRange range, SourceLocation parentIndent); bool clearDiagnostic(ArrayRef IDs, SourceRange range); void applyRewrites(TransformActions::RewriteReceiver &receiver); private: bool canInsert(SourceLocation loc); bool canInsertAfterToken(SourceLocation loc); bool canRemoveRange(SourceRange range); bool canReplaceRange(SourceRange range, SourceRange replacementRange); bool canReplaceText(SourceLocation loc, StringRef text); void commitInsert(SourceLocation loc, StringRef text); void commitInsertAfterToken(SourceLocation loc, StringRef text); void commitRemove(SourceRange range); void commitRemoveStmt(Stmt *S); void commitReplace(SourceRange range, SourceRange replacementRange); void commitReplaceText(SourceLocation loc, StringRef text, StringRef replacementText); void commitIncreaseIndentation(SourceRange range,SourceLocation parentIndent); void commitClearDiagnostic(ArrayRef IDs, SourceRange range); void addRemoval(CharSourceRange range); void addInsertion(SourceLocation loc, StringRef text); /// Stores text passed to the transformation methods to keep the string /// "alive". Since the vast majority of text will be the same, we also unique /// the strings using a StringMap. StringRef getUniqueText(StringRef text); /// Computes the source location just past the end of the token at /// the given source location. If the location points at a macro, the whole /// macro expansion is skipped. static SourceLocation getLocForEndOfToken(SourceLocation loc, SourceManager &SM,Preprocessor &PP); }; } // anonymous namespace void TransformActionsImpl::startTransaction() { assert(!IsInTransaction && "Cannot start a transaction in the middle of another one"); IsInTransaction = true; } bool TransformActionsImpl::commitTransaction() { assert(IsInTransaction && "No transaction started"); if (CachedActions.empty()) { IsInTransaction = false; return false; } // Verify that all actions are possible otherwise abort the whole transaction. bool AllActionsPossible = true; for (unsigned i = 0, e = CachedActions.size(); i != e; ++i) { ActionData &act = CachedActions[i]; switch (act.Kind) { case Act_Insert: if (!canInsert(act.Loc)) AllActionsPossible = false; break; case Act_InsertAfterToken: if (!canInsertAfterToken(act.Loc)) AllActionsPossible = false; break; case Act_Remove: if (!canRemoveRange(act.R1)) AllActionsPossible = false; break; case Act_RemoveStmt: assert(act.S); if (!canRemoveRange(act.S->getSourceRange())) AllActionsPossible = false; break; case Act_Replace: if (!canReplaceRange(act.R1, act.R2)) AllActionsPossible = false; break; case Act_ReplaceText: if (!canReplaceText(act.Loc, act.Text1)) AllActionsPossible = false; break; case Act_IncreaseIndentation: // This is not important, we don't care if it will fail. break; case Act_ClearDiagnostic: // We are just checking source rewrites. break; } if (!AllActionsPossible) break; } if (!AllActionsPossible) { abortTransaction(); return true; } for (unsigned i = 0, e = CachedActions.size(); i != e; ++i) { ActionData &act = CachedActions[i]; switch (act.Kind) { case Act_Insert: commitInsert(act.Loc, act.Text1); break; case Act_InsertAfterToken: commitInsertAfterToken(act.Loc, act.Text1); break; case Act_Remove: commitRemove(act.R1); break; case Act_RemoveStmt: commitRemoveStmt(act.S); break; case Act_Replace: commitReplace(act.R1, act.R2); break; case Act_ReplaceText: commitReplaceText(act.Loc, act.Text1, act.Text2); break; case Act_IncreaseIndentation: commitIncreaseIndentation(act.R1, act.Loc); break; case Act_ClearDiagnostic: commitClearDiagnostic(act.DiagIDs, act.R1); break; } } CachedActions.clear(); IsInTransaction = false; return false; } void TransformActionsImpl::abortTransaction() { assert(IsInTransaction && "No transaction started"); CachedActions.clear(); IsInTransaction = false; } void TransformActionsImpl::insert(SourceLocation loc, StringRef text) { assert(IsInTransaction && "Actions only allowed during a transaction"); text = getUniqueText(text); ActionData data; data.Kind = Act_Insert; data.Loc = loc; data.Text1 = text; CachedActions.push_back(data); } void TransformActionsImpl::insertAfterToken(SourceLocation loc, StringRef text) { assert(IsInTransaction && "Actions only allowed during a transaction"); text = getUniqueText(text); ActionData data; data.Kind = Act_InsertAfterToken; data.Loc = loc; data.Text1 = text; CachedActions.push_back(data); } void TransformActionsImpl::remove(SourceRange range) { assert(IsInTransaction && "Actions only allowed during a transaction"); ActionData data; data.Kind = Act_Remove; data.R1 = range; CachedActions.push_back(data); } void TransformActionsImpl::removeStmt(Stmt *S) { assert(IsInTransaction && "Actions only allowed during a transaction"); ActionData data; data.Kind = Act_RemoveStmt; if (auto *E = dyn_cast(S)) S = E->IgnoreImplicit(); // important for uniquing data.S = S; CachedActions.push_back(data); } void TransformActionsImpl::replace(SourceRange range, StringRef text) { assert(IsInTransaction && "Actions only allowed during a transaction"); text = getUniqueText(text); remove(range); insert(range.getBegin(), text); } void TransformActionsImpl::replace(SourceRange range, SourceRange replacementRange) { assert(IsInTransaction && "Actions only allowed during a transaction"); ActionData data; data.Kind = Act_Replace; data.R1 = range; data.R2 = replacementRange; CachedActions.push_back(data); } void TransformActionsImpl::replaceText(SourceLocation loc, StringRef text, StringRef replacementText) { text = getUniqueText(text); replacementText = getUniqueText(replacementText); ActionData data; data.Kind = Act_ReplaceText; data.Loc = loc; data.Text1 = text; data.Text2 = replacementText; CachedActions.push_back(data); } void TransformActionsImpl::replaceStmt(Stmt *S, StringRef text) { assert(IsInTransaction && "Actions only allowed during a transaction"); text = getUniqueText(text); insert(S->getBeginLoc(), text); removeStmt(S); } void TransformActionsImpl::increaseIndentation(SourceRange range, SourceLocation parentIndent) { if (range.isInvalid()) return; assert(IsInTransaction && "Actions only allowed during a transaction"); ActionData data; data.Kind = Act_IncreaseIndentation; data.R1 = range; data.Loc = parentIndent; CachedActions.push_back(data); } bool TransformActionsImpl::clearDiagnostic(ArrayRef IDs, SourceRange range) { assert(IsInTransaction && "Actions only allowed during a transaction"); if (!CapturedDiags.hasDiagnostic(IDs, range)) return false; ActionData data; data.Kind = Act_ClearDiagnostic; data.R1 = range; data.DiagIDs.append(IDs.begin(), IDs.end()); CachedActions.push_back(data); return true; } bool TransformActionsImpl::canInsert(SourceLocation loc) { if (loc.isInvalid()) return false; SourceManager &SM = Ctx.getSourceManager(); if (SM.isInSystemHeader(SM.getExpansionLoc(loc))) return false; if (loc.isFileID()) return true; return PP.isAtStartOfMacroExpansion(loc); } bool TransformActionsImpl::canInsertAfterToken(SourceLocation loc) { if (loc.isInvalid()) return false; SourceManager &SM = Ctx.getSourceManager(); if (SM.isInSystemHeader(SM.getExpansionLoc(loc))) return false; if (loc.isFileID()) return true; return PP.isAtEndOfMacroExpansion(loc); } bool TransformActionsImpl::canRemoveRange(SourceRange range) { return canInsert(range.getBegin()) && canInsertAfterToken(range.getEnd()); } bool TransformActionsImpl::canReplaceRange(SourceRange range, SourceRange replacementRange) { return canRemoveRange(range) && canRemoveRange(replacementRange); } bool TransformActionsImpl::canReplaceText(SourceLocation loc, StringRef text) { if (!canInsert(loc)) return false; SourceManager &SM = Ctx.getSourceManager(); loc = SM.getExpansionLoc(loc); // Break down the source location. std::pair locInfo = SM.getDecomposedLoc(loc); // Try to load the file buffer. bool invalidTemp = false; StringRef file = SM.getBufferData(locInfo.first, &invalidTemp); if (invalidTemp) return false; return file.substr(locInfo.second).starts_with(text); } void TransformActionsImpl::commitInsert(SourceLocation loc, StringRef text) { addInsertion(loc, text); } void TransformActionsImpl::commitInsertAfterToken(SourceLocation loc, StringRef text) { addInsertion(getLocForEndOfToken(loc, Ctx.getSourceManager(), PP), text); } void TransformActionsImpl::commitRemove(SourceRange range) { addRemoval(CharSourceRange::getTokenRange(range)); } void TransformActionsImpl::commitRemoveStmt(Stmt *S) { assert(S); if (StmtRemovals.count(S)) return; // already removed. if (Expr *E = dyn_cast(S)) { commitRemove(E->getSourceRange()); commitInsert(E->getSourceRange().getBegin(), getARCMTMacroName()); } else commitRemove(S->getSourceRange()); StmtRemovals.insert(S); } void TransformActionsImpl::commitReplace(SourceRange range, SourceRange replacementRange) { RangeComparison comp = CharRange::compare(replacementRange, range, Ctx.getSourceManager(), PP); assert(comp == Range_Contained); if (comp != Range_Contained) return; // Although we asserted, be extra safe for release build. if (range.getBegin() != replacementRange.getBegin()) addRemoval(CharSourceRange::getCharRange(range.getBegin(), replacementRange.getBegin())); if (replacementRange.getEnd() != range.getEnd()) addRemoval(CharSourceRange::getTokenRange( getLocForEndOfToken(replacementRange.getEnd(), Ctx.getSourceManager(), PP), range.getEnd())); } void TransformActionsImpl::commitReplaceText(SourceLocation loc, StringRef text, StringRef replacementText) { SourceManager &SM = Ctx.getSourceManager(); loc = SM.getExpansionLoc(loc); // canReplaceText already checked if loc points at text. SourceLocation afterText = loc.getLocWithOffset(text.size()); addRemoval(CharSourceRange::getCharRange(loc, afterText)); commitInsert(loc, replacementText); } void TransformActionsImpl::commitIncreaseIndentation(SourceRange range, SourceLocation parentIndent) { SourceManager &SM = Ctx.getSourceManager(); IndentationRanges.push_back( std::make_pair(CharRange(CharSourceRange::getTokenRange(range), SM, PP), SM.getExpansionLoc(parentIndent))); } void TransformActionsImpl::commitClearDiagnostic(ArrayRef IDs, SourceRange range) { CapturedDiags.clearDiagnostic(IDs, range); } void TransformActionsImpl::addInsertion(SourceLocation loc, StringRef text) { SourceManager &SM = Ctx.getSourceManager(); loc = SM.getExpansionLoc(loc); for (const CharRange &I : llvm::reverse(Removals)) { if (!SM.isBeforeInTranslationUnit(loc, I.End)) break; if (I.Begin.isBeforeInTranslationUnitThan(loc)) return; } Inserts[FullSourceLoc(loc, SM)].push_back(text); } void TransformActionsImpl::addRemoval(CharSourceRange range) { CharRange newRange(range, Ctx.getSourceManager(), PP); if (newRange.Begin == newRange.End) return; Inserts.erase(Inserts.upper_bound(newRange.Begin), Inserts.lower_bound(newRange.End)); std::list::iterator I = Removals.end(); while (I != Removals.begin()) { std::list::iterator RI = I; --RI; RangeComparison comp = newRange.compareWith(*RI); switch (comp) { case Range_Before: --I; break; case Range_After: Removals.insert(I, newRange); return; case Range_Contained: return; case Range_Contains: RI->End = newRange.End; [[fallthrough]]; case Range_ExtendsBegin: newRange.End = RI->End; Removals.erase(RI); break; case Range_ExtendsEnd: RI->End = newRange.End; return; } } Removals.insert(Removals.begin(), newRange); } void TransformActionsImpl::applyRewrites( TransformActions::RewriteReceiver &receiver) { for (InsertsMap::iterator I = Inserts.begin(), E = Inserts.end(); I!=E; ++I) { SourceLocation loc = I->first; for (TextsVec::iterator TI = I->second.begin(), TE = I->second.end(); TI != TE; ++TI) { receiver.insert(loc, *TI); } } for (std::vector >::iterator I = IndentationRanges.begin(), E = IndentationRanges.end(); I!=E; ++I) { CharSourceRange range = CharSourceRange::getCharRange(I->first.Begin, I->first.End); receiver.increaseIndentation(range, I->second); } for (std::list::iterator I = Removals.begin(), E = Removals.end(); I != E; ++I) { CharSourceRange range = CharSourceRange::getCharRange(I->Begin, I->End); receiver.remove(range); } } /// Stores text passed to the transformation methods to keep the string /// "alive". Since the vast majority of text will be the same, we also unique /// the strings using a StringMap. StringRef TransformActionsImpl::getUniqueText(StringRef text) { return UniqueText.insert(std::make_pair(text, false)).first->first(); } /// Computes the source location just past the end of the token at /// the given source location. If the location points at a macro, the whole /// macro expansion is skipped. SourceLocation TransformActionsImpl::getLocForEndOfToken(SourceLocation loc, SourceManager &SM, Preprocessor &PP) { if (loc.isMacroID()) { CharSourceRange Exp = SM.getExpansionRange(loc); if (Exp.isCharRange()) return Exp.getEnd(); loc = Exp.getEnd(); } return PP.getLocForEndOfToken(loc); } TransformActions::RewriteReceiver::~RewriteReceiver() { } TransformActions::TransformActions(DiagnosticsEngine &diag, CapturedDiagList &capturedDiags, ASTContext &ctx, Preprocessor &PP) : Diags(diag), CapturedDiags(capturedDiags) { Impl = new TransformActionsImpl(capturedDiags, ctx, PP); } TransformActions::~TransformActions() { delete static_cast(Impl); } void TransformActions::startTransaction() { static_cast(Impl)->startTransaction(); } bool TransformActions::commitTransaction() { return static_cast(Impl)->commitTransaction(); } void TransformActions::abortTransaction() { static_cast(Impl)->abortTransaction(); } void TransformActions::insert(SourceLocation loc, StringRef text) { static_cast(Impl)->insert(loc, text); } void TransformActions::insertAfterToken(SourceLocation loc, StringRef text) { static_cast(Impl)->insertAfterToken(loc, text); } void TransformActions::remove(SourceRange range) { static_cast(Impl)->remove(range); } void TransformActions::removeStmt(Stmt *S) { static_cast(Impl)->removeStmt(S); } void TransformActions::replace(SourceRange range, StringRef text) { static_cast(Impl)->replace(range, text); } void TransformActions::replace(SourceRange range, SourceRange replacementRange) { static_cast(Impl)->replace(range, replacementRange); } void TransformActions::replaceStmt(Stmt *S, StringRef text) { static_cast(Impl)->replaceStmt(S, text); } void TransformActions::replaceText(SourceLocation loc, StringRef text, StringRef replacementText) { static_cast(Impl)->replaceText(loc, text, replacementText); } void TransformActions::increaseIndentation(SourceRange range, SourceLocation parentIndent) { static_cast(Impl)->increaseIndentation(range, parentIndent); } bool TransformActions::clearDiagnostic(ArrayRef IDs, SourceRange range) { return static_cast(Impl)->clearDiagnostic(IDs, range); } void TransformActions::applyRewrites(RewriteReceiver &receiver) { static_cast(Impl)->applyRewrites(receiver); } DiagnosticBuilder TransformActions::report(SourceLocation loc, unsigned diagId, SourceRange range) { assert(!static_cast(Impl)->isInTransaction() && "Errors should be emitted out of a transaction"); return Diags.Report(loc, diagId) << range; } void TransformActions::reportError(StringRef message, SourceLocation loc, SourceRange range) { report(loc, diag::err_mt_message, range) << message; } void TransformActions::reportWarning(StringRef message, SourceLocation loc, SourceRange range) { report(loc, diag::warn_mt_message, range) << message; } void TransformActions::reportNote(StringRef message, SourceLocation loc, SourceRange range) { report(loc, diag::note_mt_message, range) << message; }